Skip to content
Draft
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
return str(series) if series is not None else None
29 changes: 21 additions & 8 deletions pygmt/src/histogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
build_arg_list,
deprecate_parameter,
fmt_docstring,
kwargs_to_strings,
use_alias,
)
from pygmt.params import Axis, Frame
from pygmt.src._common import _parse_series

__doctest_skip__ = ["histogram"]

Expand All @@ -27,7 +27,6 @@
@use_alias(
D="annotate",
N="distribution",
T="series",
Z="histtype",
b="binary",
d="nodata",
Expand All @@ -36,12 +35,12 @@
l="label",
w="wrap",
)
@kwargs_to_strings(T="sequence")
def histogram(
self,
data: PathLike | TableLike,
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 | None = None,
Expand Down Expand Up @@ -77,6 +76,7 @@ def histogram(
- Q = cumulative
- R = region
- S = stairs
- T = series
- V = verbose
- W = pen
- c = panel
Expand All @@ -89,6 +89,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.
$cmap
pen
Draw bar outline (or stair-case curve) using the specified pen thickness
Expand Down Expand Up @@ -139,9 +148,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.
histtype : int or str
[*type*][**+w**].
Choose between 6 types of histograms:
Expand Down Expand Up @@ -186,7 +192,6 @@ def histogram(
raise GMTParameterError(
required="bar_width", reason="Required when 'bar_offset' is set."
)

aliasdict = AliasSystem(
A=Alias(horizontal, name="horizontal"),
C=Alias(cmap, name="cmap"),
Expand Down Expand Up @@ -218,7 +223,15 @@ def histogram(

self._activate_figure()
with Session() as lib:
with lib.virtualfile_in(check_kind="vector", data=data) as vintbl:
with (
lib.virtualfile_in(check_kind="vector", data=data) 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)
)
18 changes: 18 additions & 0 deletions pygmt/tests/test_histogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Test Figure.histogram.
"""

import numpy as np
import pandas as pd
import pytest
from pygmt import Figure
Expand Down Expand Up @@ -36,6 +37,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