Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion pygmt/src/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from typing import Any, ClassVar, Literal

from pygmt.exceptions import GMTParameterError, GMTTypeError, GMTValueError
from pygmt.helpers import is_given
from pygmt.helpers import is_given, is_nonstr_iter
from pygmt.params.position import Position
from pygmt.src.which import which

Expand Down Expand Up @@ -395,3 +395,51 @@ def _parse_position(
),
)
return position


def _parse_series(
series: float | str | Sequence[float] | None = None,
) -> str | Sequence[float] | None:
"""
Parse the "series" parameter for array creation.

The rules are:

- A list/tuple of three values is interpreted as *min*/*max*/*inc* (joined by "/")
- Other iterables are interpreted as comma-separated values, and should be passed
via a virtual file, because GMT can't deal with very long CLI arguments
- Other values are converted to a string

Parameters
----------
series
The ``series`` parameter to parse.

Returns
-------
series
A string to be passed to GMT, the sequence if the values must be passed via a
virtual file, or ``None`` if not given.

Examples
--------
>>> _parse_series()
>>> _parse_series(1)
'1'
>>> _parse_series("0/9/1")
'0/9/1'
>>> _parse_series([0, 9, 1])
'0/9/1'
>>> _parse_series((0, 9, 1))
'0/9/1'
>>> _parse_series([0, 1, 2, 3, 5])
[0, 1, 2, 3, 5]
>>> import numpy as np
>>> _parse_series(np.arange(1, 10, 1))
array([1, 2, 3, 4, 5, 6, 7, 8, 9])
"""
if isinstance(series, (list, tuple)) and len(series) == 3:
return "/".join(str(item) for item in series)
if is_nonstr_iter(series):
return series # type:ignore[return-value]
return str(series) if series is not None else None
37 changes: 22 additions & 15 deletions pygmt/src/histogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
fmt_docstring,
is_given,
is_nonstr_iter,
kwargs_to_strings,
use_alias,
)
from pygmt.params import Axis, Frame, Pattern
from pygmt.src._common import _parse_series

__doctest_skip__ = ["histogram"]

Expand Down Expand Up @@ -188,16 +188,7 @@ def _alias_option_N( # ruff: ignore[invalid-function-name]
# TODO(PyGMT>=0.22.0): Remove the deprecated "annotate" parameter.
@deprecate_parameter("extreme", "out_range", "0.20.0", remove_version="0.22.0")
@deprecate_parameter("annotate", "annot", "0.20.0", remove_version="0.22.0")
@use_alias(
T="series",
b="binary",
d="nodata",
e="find",
h="header",
l="label",
w="wrap",
)
@kwargs_to_strings(T="sequence")
@use_alias(b="binary", d="nodata", e="find", h="header", l="label", w="wrap")
def histogram(
self,
data: PathLike | TableLike,
Expand All @@ -207,6 +198,7 @@ def histogram(
] = "count",
bar_width: float | str | None = None,
bar_offset: float | str | None = None,
series: float | str | Sequence[float] | None = None,
cmap: str | bool = False,
pen: str | None = None,
fill: str | Pattern | Literal["position", "value"] | None = None,
Expand Down Expand Up @@ -254,6 +246,7 @@ def histogram(
- Q = cumulative
- R = region
- S = stairs
- T = series
- V = verbose
- W = pen
- Z = histtype, **+w**: weights
Expand All @@ -267,6 +260,15 @@ def histogram(
data
Pass in either a file name to an ASCII data table, a Python list, a 2-D
$table_classes.
series
Set the histogram binning. It can take one of four forms:

- A list/tuple of three values to set the minimum, maximum, and increment for
the binning.
- A single float value to set the bin increment only, with the minimum and
maximum automatically determined from the data or ``region``.
- A sequence of any other length to set the bin boundaries explicitly.
- A string for any GMT CLI syntax.
weights
Use weighted counts instead of pure counts [Default is ``False``, i.e., pure
counts are used]. It can be:
Expand Down Expand Up @@ -344,9 +346,6 @@ def histogram(
Plot the histogram horizontally from x = 0 [Default is vertically from y = 0].
The plot dimensions remain the same, but the two axes are flipped, i.e., the
x-axis is plotted vertically and the y-axis is plotted horizontally.
series : int, str, or list
[*min*\ /*max*\ /]\ *inc*\ [**+n**\ ].
Set the interval for the width of each bar in the histogram.
distribution
Draw the equivalent normal distribution. Select which central location and scale
to use:
Expand Down Expand Up @@ -491,7 +490,15 @@ def histogram(
self._activate_figure()
with Session() as lib:
vfargs = {"x": data, "y": weights} if _weight_is_array else {"data": data}
with lib.virtualfile_in(check_kind="vector", **vfargs) as vintbl:
with (
lib.virtualfile_in(check_kind="vector", **vfargs) as vintbl,
lib.virtualfile_in(
check_kind="vector",
data=_parse_series(series),
required=False,
) as vseries,
):
aliasdict["T"] = vseries
lib.call_module(
module="histogram", args=build_arg_list(aliasdict, infile=vintbl)
)
17 changes: 17 additions & 0 deletions pygmt/tests/test_histogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,23 @@ def test_histogram(data):
return fig


@pytest.mark.mpl_image_compare(filename="test_histogram.png")
def test_histogram_series_nparray(data):
"""
Test plotting a histogram with bin boundaries passed as a numpy array.
"""
fig = Figure()
fig.histogram(
data=data,
projection="X10c/10c",
region=[0, 9, 0, 6],
series=np.arange(0, 10),
frame=Axis(annot=True),
fill="green",
)
return fig


def test_histogram_baroffset(data):
"""
Test passing bar_offset requires bar_width.
Expand Down
Loading