From 7ed3b49ac610ba24a5356f7e62bea5dec52b3e47 Mon Sep 17 00:00:00 2001 From: Michael Grund Date: Wed, 17 Feb 2021 19:28:53 +0100 Subject: [PATCH 01/43] Add new modules to plot horizontal and vertical lines As discussed in #670 here's a new module (**hlines**) to plot a single or a set of horizontal lines with only defining the desired y-value(s). For discussion I only add the module for horizontal lines at the moment, however, the adjustments to prepare the same for vertical lines is done very quickly. --- pygmt/src/hlines.py | 155 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 pygmt/src/hlines.py diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py new file mode 100644 index 00000000000..37a69dcd11f --- /dev/null +++ b/pygmt/src/hlines.py @@ -0,0 +1,155 @@ +""" +hlines - Plot horizontal lines. +""" +import numpy as np +from pygmt.clib import Session +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import build_arg_string, fmt_docstring, use_alias, kwargs_to_strings, data_kind + +@fmt_docstring +@use_alias( + B="frame", + C="cmap", + D="offset", + J="projection", + N="no_clip", + R="region", + U="timestamp", + V="verbose", + W="pen", + X="xshift", + Y="yshift", + Z="zvalue", + l="label", + p="perspective", + t="transparency", + ) +@kwargs_to_strings(R="sequence", p="sequence") +def hlines(self, y=None, xmin=None, xmax=None, pen=None, label=None, **kwargs): + """" + Plot one or a collection of horizontal lines + + Takes a single y value or a list of individual y values and optionally + lower and upper x value limits as input. + + Must provide *y*. + + If y values are given without x limits then the current map boundaries are + used as lower and upper limits. If only a single set of x limits is given then + all lines will have the same length, otherwise give x limits for each individual + line. If only a single label is given then all lines are grouped under this label + in the legend (if shown). If each line should appear as a single entry in the + legend, give corresponding labels for all lines (same for **pen**). + + Parameters + ---------- + y : float or 1d array + The y coordinates or an array of y coordinates of the + horizontal lines to plot. + {J} + {R} + {B} + {CPT} + offset : str + ``dx/dy``. + Offset the line locations by the given amounts + *dx/dy* [Default is no offset]. If *dy* is not given it is set + equal to *dx*. + no_clip : bool or str + ``'[c|r]'``. + Do NOT clip lines that fall outside map border [Default plots + lines whose coordinates are strictly inside the map border only]. + The option does not apply to lines which are always + clipped to the map region. For periodic (360-longitude) maps we + must plot all lines twice in case they are clipped by the + repeating boundary. ``no_clip=True`` will turn off clipping and not + plot repeating lines. Use ``no_clip="r"`` to turn off clipping + but retain the plotting of such repeating lines, or use + ``no_clip="c"`` to retain clipping but turn off plotting of + repeating lines. + {W} + {U} + {V} + {XY} + zvalue : str or float + ``value``. + Instead of specifying a line color via **pen**, give it a *value* + via **zvalue** and a color lookup table via **cmap**. Requires appending + **+z** to **pen** (e.g. ``pen = "5p,+z"``, ``zvalue = 0.8``, + * ``cmap = "viridis"``). + label : str + Add a legend entry for the line being plotted. + {p} + {t} + *transparency* can also be a 1d array to set varying transparency + for lines. + + """ + + kwargs = self._preprocess(**kwargs) + + list_length = len(np.atleast_1d(y)) + + # prepare x vals + if xmin is None and xmax is None: + # get limits from current map boundings if not given via xmin, xmax + with Session() as lib: + mapbnds = lib.extract_region() + x = np.array([[mapbnds[0]], [mapbnds[1]]]) + x = np.repeat(x, list_length, axis=1) + elif xmin is None or xmax is None: + raise GMTInvalidInput("Must provide both, xmin and xmax if limits are not set automatically.") + + else: + # if only a single xmin and xmax without [], repeat to fit size of y + if isinstance(xmin, int) or isinstance(xmin, float): + x = np.array([[xmin], [xmax]]) + x = np.repeat(x, list_length, axis=1) + else: + if len(xmin) != len(xmax): + GMTInvalidInput("Must provide same length for xmin and xmax.") + else: + x = np.array([xmin, xmax]) + + # prepare labels + if "l" in kwargs: + # if several lines belong to the same label, first take the label, + # then set all to None and reset the first entry to the given label + if not isinstance(kwargs["l"], list): + label2use = kwargs["l"] + kwargs["l"] = np.repeat(None, list_length) + kwargs["l"][0] = label2use + else: + kwargs["l"] = np.repeat(None, list_length) + + + # prepare pens + if "W" in kwargs: + # select pen, no series + if not isinstance(kwargs["W"], list): + pen2use = kwargs["W"] + kwargs["W"] = np.repeat(pen2use, list_length) + else: # use as default if no pen is given (neither single nor series) + kwargs["W"] = np.repeat("1p,black", list_length) + + # loop over entries + kwargs_copy = kwargs.copy() + + for index in range(list_length): + y2plt = [np.atleast_1d(y)[index], np.atleast_1d(y)[index]] + x2plt = [np.atleast_1d(x)[0][index], np.atleast_1d(x)[1][index]] + kind = data_kind(None, x2plt, y2plt) + + with Session() as lib: + if kind == "vectors": + file_context = lib.virtualfile_from_vectors( + np.atleast_1d(x2plt), np.atleast_1d(y2plt)) + else: + raise GMTInvalidInput("Unrecognized data type.") + + kwargs["l"] = kwargs_copy["l"][index] + kwargs["W"] = kwargs_copy["W"][index] + + with file_context as fname: + arg_str = " ".join([fname, build_arg_string(kwargs)]) + lib.call_module("plot", arg_str) From 096b9de601c68bb17d3ac3e2e9796e881679b600 Mon Sep 17 00:00:00 2001 From: Michael Grund Date: Wed, 17 Feb 2021 19:42:07 +0100 Subject: [PATCH 02/43] added further content --- doc/api/index.rst | 1 + examples/gallery/line/horizontal-lines.py | 29 ++++ pygmt/figure.py | 1 + pygmt/src/__init__.py | 1 + pygmt/src/hlines.py | 161 ++++++++++++---------- 5 files changed, 117 insertions(+), 76 deletions(-) create mode 100644 examples/gallery/line/horizontal-lines.py diff --git a/doc/api/index.rst b/doc/api/index.rst index 0ef0b359b39..f56e3d0b3e1 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -31,6 +31,7 @@ Plotting data and laying out the map: Figure.grdview Figure.image Figure.inset + Figure.hlines Figure.legend Figure.logo Figure.meca diff --git a/examples/gallery/line/horizontal-lines.py b/examples/gallery/line/horizontal-lines.py new file mode 100644 index 00000000000..cfbe3066586 --- /dev/null +++ b/examples/gallery/line/horizontal-lines.py @@ -0,0 +1,29 @@ +""" +Plot horizontal lines +--------------------- + +The :meth:`pygmt.Figure.hlines` method can plot horizontal lines based on +a given y value. Optionally, the lower and upper limits of the lines can be +defined, otherwise the current map boundaries are taken. + +""" + +import pygmt + +fig = pygmt.Figure() +fig.basemap(region=[0, 10, 0, 11], projection="X10c/10c", frame=True) + +fig.hlines(1, label="line1") +fig.hlines([2, 3], pen="2p,dodgerblue4", label="line2") +fig.hlines([4, 5], xmin=2, xmax=8, pen="2p,red3", label="line3") +fig.hlines([6, 7], xmin=[1, 3], xmax=[8, 7], pen="3p,seagreen", label="line4") +fig.hlines( + [8, 9, 10], + xmin=[1.3, 3, 2], + xmax=[6.5, 7, 5], + pen=["4p,darkmagenta", "2p,gold,--", "3.5p,blue,."], + label=["line5", "line6", "line7"], +) + +fig.legend() +fig.show() diff --git a/pygmt/figure.py b/pygmt/figure.py index 92699cd8632..72df6e40c4a 100644 --- a/pygmt/figure.py +++ b/pygmt/figure.py @@ -383,6 +383,7 @@ def _repr_html_(self): grdcontour, grdimage, grdview, + hlines, image, inset, legend, diff --git a/pygmt/src/__init__.py b/pygmt/src/__init__.py index 3ef9af36070..2b0e0c2329b 100644 --- a/pygmt/src/__init__.py +++ b/pygmt/src/__init__.py @@ -15,6 +15,7 @@ from pygmt.src.grdinfo import grdinfo from pygmt.src.grdtrack import grdtrack from pygmt.src.grdview import grdview +from pygmt.src.hlines import hlines from pygmt.src.image import image from pygmt.src.info import info from pygmt.src.inset import inset diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index 37a69dcd11f..31d6250d6a8 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -4,42 +4,50 @@ import numpy as np from pygmt.clib import Session from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import build_arg_string, fmt_docstring, use_alias, kwargs_to_strings, data_kind +from pygmt.helpers import ( + build_arg_string, + data_kind, + fmt_docstring, + kwargs_to_strings, + use_alias, +) + @fmt_docstring @use_alias( - B="frame", - C="cmap", - D="offset", - J="projection", - N="no_clip", - R="region", - U="timestamp", - V="verbose", - W="pen", - X="xshift", - Y="yshift", - Z="zvalue", - l="label", - p="perspective", - t="transparency", - ) + B="frame", + C="cmap", + D="offset", + J="projection", + N="no_clip", + R="region", + U="timestamp", + V="verbose", + W="pen", + X="xshift", + Y="yshift", + Z="zvalue", + l="label", + p="perspective", + t="transparency", +) @kwargs_to_strings(R="sequence", p="sequence") def hlines(self, y=None, xmin=None, xmax=None, pen=None, label=None, **kwargs): - """" - Plot one or a collection of horizontal lines + """ + " Plot one or a collection of horizontal lines. - Takes a single y value or a list of individual y values and optionally + Takes a single y value or a list of individual y values and optionally lower and upper x value limits as input. Must provide *y*. - If y values are given without x limits then the current map boundaries are - used as lower and upper limits. If only a single set of x limits is given then - all lines will have the same length, otherwise give x limits for each individual - line. If only a single label is given then all lines are grouped under this label - in the legend (if shown). If each line should appear as a single entry in the - legend, give corresponding labels for all lines (same for **pen**). + If y values are given without x limits then the current map boundaries are + used as lower and upper limits. If only a single set of x limits is given + then all lines will have the same length, otherwise give x limits for each + individual line. If only a single label is given then all lines are grouped + under this label in the legend (if shown). If each line should appear as a + single entry in the legend, give corresponding labels for all lines + (same for **pen**). Parameters ---------- @@ -73,83 +81,84 @@ def hlines(self, y=None, xmin=None, xmax=None, pen=None, label=None, **kwargs): {XY} zvalue : str or float ``value``. - Instead of specifying a line color via **pen**, give it a *value* - via **zvalue** and a color lookup table via **cmap**. Requires appending - **+z** to **pen** (e.g. ``pen = "5p,+z"``, ``zvalue = 0.8``, - * ``cmap = "viridis"``). + Instead of specifying a line color via **pen**, give it a *value* + via **zvalue** and a color lookup table via **cmap**. Requires + appending **+z** to **pen** (e.g. ``pen = "5p,+z"``, + ``zvalue = 0.8``, ``cmap = "viridis"``). label : str Add a legend entry for the line being plotted. {p} {t} *transparency* can also be a 1d array to set varying transparency for lines. - """ - + kwargs = self._preprocess(**kwargs) - + list_length = len(np.atleast_1d(y)) - + # prepare x vals - if xmin is None and xmax is None: + if xmin is None and xmax is None: # get limits from current map boundings if not given via xmin, xmax - with Session() as lib: - mapbnds = lib.extract_region() - x = np.array([[mapbnds[0]], [mapbnds[1]]]) - x = np.repeat(x, list_length, axis=1) + with Session() as lib: + mapbnds = lib.extract_region() + x = np.array([[mapbnds[0]], [mapbnds[1]]]) + x = np.repeat(x, list_length, axis=1) elif xmin is None or xmax is None: - raise GMTInvalidInput("Must provide both, xmin and xmax if limits are not set automatically.") - - else: + raise GMTInvalidInput( + "Must provide both, xmin and xmax if limits are not set automatically." + ) + + else: # if only a single xmin and xmax without [], repeat to fit size of y - if isinstance(xmin, int) or isinstance(xmin, float): - x = np.array([[xmin], [xmax]]) - x = np.repeat(x, list_length, axis=1) + if isinstance(xmin, int) or isinstance(xmin, float): + x = np.array([[xmin], [xmax]]) + x = np.repeat(x, list_length, axis=1) else: if len(xmin) != len(xmax): - GMTInvalidInput("Must provide same length for xmin and xmax.") - else: - x = np.array([xmin, xmax]) - - # prepare labels + GMTInvalidInput("Must provide same length for xmin and xmax.") + else: + x = np.array([xmin, xmax]) + + # prepare labels if "l" in kwargs: # if several lines belong to the same label, first take the label, # then set all to None and reset the first entry to the given label if not isinstance(kwargs["l"], list): label2use = kwargs["l"] kwargs["l"] = np.repeat(None, list_length) - kwargs["l"][0] = label2use + kwargs["l"][0] = label2use else: - kwargs["l"] = np.repeat(None, list_length) - - - # prepare pens + kwargs["l"] = np.repeat(None, list_length) + + # prepare pens if "W" in kwargs: # select pen, no series if not isinstance(kwargs["W"], list): pen2use = kwargs["W"] - kwargs["W"] = np.repeat(pen2use, list_length) - else: # use as default if no pen is given (neither single nor series) + kwargs["W"] = np.repeat(pen2use, list_length) + else: # use as default if no pen is given (neither single nor series) kwargs["W"] = np.repeat("1p,black", list_length) - # loop over entries + # loop over entries kwargs_copy = kwargs.copy() - + for index in range(list_length): - y2plt = [np.atleast_1d(y)[index], np.atleast_1d(y)[index]] - x2plt = [np.atleast_1d(x)[0][index], np.atleast_1d(x)[1][index]] - kind = data_kind(None, x2plt, y2plt) - - with Session() as lib: - if kind == "vectors": - file_context = lib.virtualfile_from_vectors( - np.atleast_1d(x2plt), np.atleast_1d(y2plt)) - else: - raise GMTInvalidInput("Unrecognized data type.") - - kwargs["l"] = kwargs_copy["l"][index] - kwargs["W"] = kwargs_copy["W"][index] - - with file_context as fname: - arg_str = " ".join([fname, build_arg_string(kwargs)]) - lib.call_module("plot", arg_str) + y2plt = [np.atleast_1d(y)[index], np.atleast_1d(y)[index]] + x2plt = [np.atleast_1d(x)[0][index], np.atleast_1d(x)[1][index]] + kind = data_kind(None, x2plt, y2plt) + + with Session() as lib: + if kind == "vectors": + file_context = lib.virtualfile_from_vectors( + np.atleast_1d(x2plt), np.atleast_1d(y2plt) + ) + else: + raise GMTInvalidInput("Unrecognized data type.") + + kwargs["l"] = kwargs_copy["l"][index] + kwargs["W"] = kwargs_copy["W"][index] + + with file_context as fname: + arg_str = " ".join([fname, build_arg_string(kwargs)]) + lib.call_module("plot", arg_str) From 92fa9c078315c0323e247c7de54bdb3f98899c03 Mon Sep 17 00:00:00 2001 From: Michael Grund Date: Wed, 17 Feb 2021 19:49:19 +0100 Subject: [PATCH 03/43] added some first tests --- pygmt/tests/test_hlines.py | 73 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 pygmt/tests/test_hlines.py diff --git a/pygmt/tests/test_hlines.py b/pygmt/tests/test_hlines.py new file mode 100644 index 00000000000..021e936ba53 --- /dev/null +++ b/pygmt/tests/test_hlines.py @@ -0,0 +1,73 @@ +""" +Tests for hline. +""" +from pygmt import Figure +from pygmt.helpers.testing import check_figures_equal + + +@check_figures_equal() +def test_hlines_value_sets(): + """ + Passing sets of y, xmin and xmax. + """ + + fig_ref, fig_test = Figure(), Figure() + + fig_ref.hlines( + region=[0, 10, 0, 20], + projection="X10c/10c", + frame=True, + y=[5.5, 10, 6, 11], + xmin=[3.1, 6, 0, 1], + xmax=[5.5, 7.8, 10, 9], + label="test2", + pen="4p,green", + ) + + fig_test.hlines( + region="0/10/0/20", + projection="X10c/10c", + frame=True, + y=[5.5, 10, 6, 11], + xmin=[3.1, 6, 0, 1], + xmax=[5.5, 7.8, 10, 9], + label="test2", + pen="4p,green", + ) + + return fig_ref, fig_test + + +@check_figures_equal() +def test_hlines_zvalue(): + """ + Passing in color via zvalue. + """ + + fig_ref, fig_test = Figure(), Figure() + + fig_ref.hlines( + region=[0, 10, 0, 20], + projection="X10c/10c", + frame=True, + y=[9.9], + xmin=2, + xmax=6, + pen="5p,+z", + zvalue="0.5", + cmap="magma", + ) + + fig_test.hlines( + region="0/10/0/20", + projection="X10c/10c", + frame=True, + y=[9.9], + xmin=2, + xmax=6, + pen="5p,+z", + zvalue="0.5", + cmap="magma", + ) + + return fig_ref, fig_test From 975b3050c81fc85c7329fd93ebcf12df5f31f616 Mon Sep 17 00:00:00 2001 From: Michael Grund Date: Wed, 17 Feb 2021 19:58:06 +0100 Subject: [PATCH 04/43] some formatting --- pygmt/src/hlines.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index 31d6250d6a8..058b83c0af6 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -32,7 +32,7 @@ t="transparency", ) @kwargs_to_strings(R="sequence", p="sequence") -def hlines(self, y=None, xmin=None, xmax=None, pen=None, label=None, **kwargs): +def hlines(self, y=None, xmin=None, xmax=None, **kwargs): """ " Plot one or a collection of horizontal lines. @@ -111,7 +111,7 @@ def hlines(self, y=None, xmin=None, xmax=None, pen=None, label=None, **kwargs): else: # if only a single xmin and xmax without [], repeat to fit size of y - if isinstance(xmin, int) or isinstance(xmin, float): + if isinstance(xmin, (int, float)): x = np.array([[xmin], [xmax]]) x = np.repeat(x, list_length, axis=1) else: From f25a89c4d0a38fedfffeb5cf9da98ef16ff7e17f Mon Sep 17 00:00:00 2001 From: Michael Grund Date: Wed, 17 Feb 2021 20:07:07 +0100 Subject: [PATCH 05/43] corrected typo --- pygmt/src/hlines.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index 058b83c0af6..95a6ca43f64 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -34,7 +34,7 @@ @kwargs_to_strings(R="sequence", p="sequence") def hlines(self, y=None, xmin=None, xmax=None, **kwargs): """ - " Plot one or a collection of horizontal lines. + Plot one or a collection of horizontal lines. Takes a single y value or a list of individual y values and optionally lower and upper x value limits as input. From d0bc35c0a3528f95540b6ab7c6a0163f1769b372 Mon Sep 17 00:00:00 2001 From: Michael Grund Date: Wed, 7 Apr 2021 18:57:57 +0200 Subject: [PATCH 06/43] moved gallery example to new subfolder /lines --- examples/gallery/{line => lines}/horizontal-lines.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/gallery/{line => lines}/horizontal-lines.py (100%) diff --git a/examples/gallery/line/horizontal-lines.py b/examples/gallery/lines/horizontal-lines.py similarity index 100% rename from examples/gallery/line/horizontal-lines.py rename to examples/gallery/lines/horizontal-lines.py From f75fbe1797a2ac24dc51ba79dbb65e2768b83a73 Mon Sep 17 00:00:00 2001 From: Michael Grund Date: Wed, 7 Apr 2021 19:04:43 +0200 Subject: [PATCH 07/43] disable pylint warnings --- pygmt/src/hlines.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index 95a6ca43f64..c2f96819bb2 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -93,7 +93,7 @@ def hlines(self, y=None, xmin=None, xmax=None, **kwargs): for lines. """ - kwargs = self._preprocess(**kwargs) + kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access list_length = len(np.atleast_1d(y)) From a375a778482f9b4ddfbb0cb32300a0d4f9985a33 Mon Sep 17 00:00:00 2001 From: Michael Grund Date: Wed, 7 Apr 2021 19:08:43 +0200 Subject: [PATCH 08/43] replace hyphen by underscore in gallery example file name --- .../gallery/lines/{horizontal-lines.py => horizontal_lines.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/gallery/lines/{horizontal-lines.py => horizontal_lines.py} (100%) diff --git a/examples/gallery/lines/horizontal-lines.py b/examples/gallery/lines/horizontal_lines.py similarity index 100% rename from examples/gallery/lines/horizontal-lines.py rename to examples/gallery/lines/horizontal_lines.py From 71113dd38aa9d9b57fc71dc5d7c90c65dc04a9cf Mon Sep 17 00:00:00 2001 From: Michael Grund Date: Wed, 7 Apr 2021 19:26:44 +0200 Subject: [PATCH 09/43] disable pylint warnings --- pygmt/src/hlines.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index c2f96819bb2..245ee35d613 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -93,6 +93,10 @@ def hlines(self, y=None, xmin=None, xmax=None, **kwargs): for lines. """ + # appearing pylint warnings that need to be fixed + # pylint: disable=too-many-locals + # pylint: disable=too-many-branches + kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access list_length = len(np.atleast_1d(y)) From 1438d4340e5911f927189ad20fb86a1713e1c23b Mon Sep 17 00:00:00 2001 From: Michael Grund Date: Wed, 7 Apr 2021 19:29:23 +0200 Subject: [PATCH 10/43] formatting --- pygmt/src/hlines.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index 245ee35d613..7f4d8caae05 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -97,7 +97,7 @@ def hlines(self, y=None, xmin=None, xmax=None, **kwargs): # pylint: disable=too-many-locals # pylint: disable=too-many-branches - kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access + kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access list_length = len(np.atleast_1d(y)) From 65eb85d2bcd92d21a2663cc4af0d2566562c6ce3 Mon Sep 17 00:00:00 2001 From: Michael Grund Date: Tue, 10 Aug 2021 21:03:17 +0200 Subject: [PATCH 11/43] update hlines module --- examples/gallery/lines/horizontal_lines.py | 15 ++- pygmt/src/hlines.py | 139 ++++++++------------- 2 files changed, 61 insertions(+), 93 deletions(-) diff --git a/examples/gallery/lines/horizontal_lines.py b/examples/gallery/lines/horizontal_lines.py index cfbe3066586..5e027f2fe20 100644 --- a/examples/gallery/lines/horizontal_lines.py +++ b/examples/gallery/lines/horizontal_lines.py @@ -17,13 +17,12 @@ fig.hlines([2, 3], pen="2p,dodgerblue4", label="line2") fig.hlines([4, 5], xmin=2, xmax=8, pen="2p,red3", label="line3") fig.hlines([6, 7], xmin=[1, 3], xmax=[8, 7], pen="3p,seagreen", label="line4") -fig.hlines( - [8, 9, 10], - xmin=[1.3, 3, 2], - xmax=[6.5, 7, 5], - pen=["4p,darkmagenta", "2p,gold,--", "3.5p,blue,."], - label=["line5", "line6", "line7"], -) +fig.hlines([8, 9, 10], + xmin=[1.3, 3, 2], + xmax=[6.5, 7, 5], + pen=["4p,darkmagenta","2p,gold,--","3.5p,blue,."], + label=["line5", "line6", "line7"], + transparency = 50) fig.legend() -fig.show() +fig.show() \ No newline at end of file diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index 7f4d8caae05..54ecf756936 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -12,35 +12,22 @@ use_alias, ) - @fmt_docstring @use_alias( - B="frame", - C="cmap", - D="offset", - J="projection", N="no_clip", - R="region", - U="timestamp", V="verbose", W="pen", - X="xshift", - Y="yshift", - Z="zvalue", l="label", p="perspective", t="transparency", -) -@kwargs_to_strings(R="sequence", p="sequence") + ) +@kwargs_to_strings(p="sequence") def hlines(self, y=None, xmin=None, xmax=None, **kwargs): """ Plot one or a collection of horizontal lines. - Takes a single y value or a list of individual y values and optionally lower and upper x value limits as input. - Must provide *y*. - If y values are given without x limits then the current map boundaries are used as lower and upper limits. If only a single set of x limits is given then all lines will have the same length, otherwise give x limits for each @@ -48,21 +35,11 @@ def hlines(self, y=None, xmin=None, xmax=None, **kwargs): under this label in the legend (if shown). If each line should appear as a single entry in the legend, give corresponding labels for all lines (same for **pen**). - Parameters ---------- y : float or 1d array The y coordinates or an array of y coordinates of the horizontal lines to plot. - {J} - {R} - {B} - {CPT} - offset : str - ``dx/dy``. - Offset the line locations by the given amounts - *dx/dy* [Default is no offset]. If *dy* is not given it is set - equal to *dx*. no_clip : bool or str ``'[c|r]'``. Do NOT clip lines that fall outside map border [Default plots @@ -76,87 +53,80 @@ def hlines(self, y=None, xmin=None, xmax=None, **kwargs): ``no_clip="c"`` to retain clipping but turn off plotting of repeating lines. {W} - {U} {V} - {XY} - zvalue : str or float - ``value``. - Instead of specifying a line color via **pen**, give it a *value* - via **zvalue** and a color lookup table via **cmap**. Requires - appending **+z** to **pen** (e.g. ``pen = "5p,+z"``, - ``zvalue = 0.8``, ``cmap = "viridis"``). label : str Add a legend entry for the line being plotted. {p} {t} *transparency* can also be a 1d array to set varying transparency for lines. - """ - # appearing pylint warnings that need to be fixed - # pylint: disable=too-many-locals - # pylint: disable=too-many-branches + """ kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access - list_length = len(np.atleast_1d(y)) + y = np.atleast_1d(y) + list_length = len(y) + + # prepare x values + def prep_data(xmin, xmax, list_length): + + if xmin is None and xmax is None: + with Session() as lib: + # get limits from current map boundings if not given via xmin, xmax + x = np.array([[lib.extract_region()[0]], [lib.extract_region()[1]]]) + x = np.repeat(x, list_length, axis=1) + elif xmin is None or xmax is None: + raise GMTInvalidInput( + "Must provide both, xmin and xmax if limits are not set automatically." + ) - # prepare x vals - if xmin is None and xmax is None: - # get limits from current map boundings if not given via xmin, xmax - with Session() as lib: - mapbnds = lib.extract_region() - x = np.array([[mapbnds[0]], [mapbnds[1]]]) - x = np.repeat(x, list_length, axis=1) - elif xmin is None or xmax is None: - raise GMTInvalidInput( - "Must provide both, xmin and xmax if limits are not set automatically." - ) - - else: - # if only a single xmin and xmax without [], repeat to fit size of y - if isinstance(xmin, (int, float)): - x = np.array([[xmin], [xmax]]) - x = np.repeat(x, list_length, axis=1) else: - if len(xmin) != len(xmax): - GMTInvalidInput("Must provide same length for xmin and xmax.") + # if only a single xmin and xmax without [], repeat to fit size of y + if isinstance(xmin, (int, float)): + x = np.array([[xmin], [xmax]]) + x = np.repeat(x, list_length, axis=1) else: - x = np.array([xmin, xmax]) - - # prepare labels - if "l" in kwargs: - # if several lines belong to the same label, first take the label, - # then set all to None and reset the first entry to the given label - if not isinstance(kwargs["l"], list): - label2use = kwargs["l"] + if len(xmin) != len(xmax): + GMTInvalidInput("Must provide same length for xmin and xmax.") + else: + x = np.array([xmin, xmax]) + + return np.atleast_1d(x) + + def prep_style(kwargs, list_length): + + # prepare labels + if "l" in kwargs: + # if several lines belong to the same label, first set all to the + # label given via "l", then reset all entries except the first + if not isinstance(kwargs["l"], list): + kwargs["l"] = np.repeat(kwargs["l"], list_length) + kwargs["l"][1:list_length] = None + else: kwargs["l"] = np.repeat(None, list_length) - kwargs["l"][0] = label2use - else: - kwargs["l"] = np.repeat(None, list_length) - - # prepare pens - if "W" in kwargs: - # select pen, no series - if not isinstance(kwargs["W"], list): - pen2use = kwargs["W"] - kwargs["W"] = np.repeat(pen2use, list_length) - else: # use as default if no pen is given (neither single nor series) - kwargs["W"] = np.repeat("1p,black", list_length) + + # prepare pens + if "W" in kwargs: + # select pen, no series + if not isinstance(kwargs["W"], list): + kwargs["W"] = np.repeat(kwargs["W"], list_length) + else: # use as default if no pen is given (neither single nor series) + kwargs["W"] = np.repeat("1p,black", list_length) + + return kwargs # loop over entries + x = prep_data(xmin, xmax, list_length) + kwargs = prep_style(kwargs, list_length) kwargs_copy = kwargs.copy() for index in range(list_length): - y2plt = [np.atleast_1d(y)[index], np.atleast_1d(y)[index]] - x2plt = [np.atleast_1d(x)[0][index], np.atleast_1d(x)[1][index]] - kind = data_kind(None, x2plt, y2plt) with Session() as lib: - if kind == "vectors": + if data_kind(None, [x[0][index], x[1][index]], [y[index], y[index]]) == "vectors": file_context = lib.virtualfile_from_vectors( - np.atleast_1d(x2plt), np.atleast_1d(y2plt) - ) + np.atleast_1d([x[0][index], x[1][index]]), [y[index], y[index]]) else: raise GMTInvalidInput("Unrecognized data type.") @@ -164,5 +134,4 @@ def hlines(self, y=None, xmin=None, xmax=None, **kwargs): kwargs["W"] = kwargs_copy["W"][index] with file_context as fname: - arg_str = " ".join([fname, build_arg_string(kwargs)]) - lib.call_module("plot", arg_str) + lib.call_module("plot", " ".join([fname, build_arg_string(kwargs)])) From 49008eda7da1536188be47024a5d185a52d4c8c0 Mon Sep 17 00:00:00 2001 From: Michael Grund Date: Tue, 10 Aug 2021 21:08:20 +0200 Subject: [PATCH 12/43] formatting --- examples/gallery/lines/horizontal_lines.py | 16 +++++++++------- pygmt/figure.py | 2 +- pygmt/src/__init__.py | 2 +- pygmt/src/hlines.py | 11 ++++++++--- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/examples/gallery/lines/horizontal_lines.py b/examples/gallery/lines/horizontal_lines.py index 5e027f2fe20..0ce5490c7ee 100644 --- a/examples/gallery/lines/horizontal_lines.py +++ b/examples/gallery/lines/horizontal_lines.py @@ -17,12 +17,14 @@ fig.hlines([2, 3], pen="2p,dodgerblue4", label="line2") fig.hlines([4, 5], xmin=2, xmax=8, pen="2p,red3", label="line3") fig.hlines([6, 7], xmin=[1, 3], xmax=[8, 7], pen="3p,seagreen", label="line4") -fig.hlines([8, 9, 10], - xmin=[1.3, 3, 2], - xmax=[6.5, 7, 5], - pen=["4p,darkmagenta","2p,gold,--","3.5p,blue,."], - label=["line5", "line6", "line7"], - transparency = 50) +fig.hlines( + [8, 9, 10], + xmin=[1.3, 3, 2], + xmax=[6.5, 7, 5], + pen=["4p,darkmagenta", "2p,gold,--", "3.5p,blue,."], + label=["line5", "line6", "line7"], + transparency=50, +) fig.legend() -fig.show() \ No newline at end of file +fig.show() diff --git a/pygmt/figure.py b/pygmt/figure.py index 759b2c1f48d..9092f977684 100644 --- a/pygmt/figure.py +++ b/pygmt/figure.py @@ -422,8 +422,8 @@ def _repr_html_(self): grdcontour, grdimage, grdview, - hlines, histogram, + hlines, image, inset, legend, diff --git a/pygmt/src/__init__.py b/pygmt/src/__init__.py index 2aebe291ca1..936b6875e47 100644 --- a/pygmt/src/__init__.py +++ b/pygmt/src/__init__.py @@ -22,8 +22,8 @@ from pygmt.src.grdsample import grdsample from pygmt.src.grdtrack import grdtrack from pygmt.src.grdview import grdview -from pygmt.src.hlines import hlines from pygmt.src.histogram import histogram +from pygmt.src.hlines import hlines from pygmt.src.image import image from pygmt.src.info import info from pygmt.src.inset import inset diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index 54ecf756936..106ac5e45e8 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -12,6 +12,7 @@ use_alias, ) + @fmt_docstring @use_alias( N="no_clip", @@ -20,7 +21,7 @@ l="label", p="perspective", t="transparency", - ) +) @kwargs_to_strings(p="sequence") def hlines(self, y=None, xmin=None, xmax=None, **kwargs): """ @@ -124,9 +125,13 @@ def prep_style(kwargs, list_length): for index in range(list_length): with Session() as lib: - if data_kind(None, [x[0][index], x[1][index]], [y[index], y[index]]) == "vectors": + if ( + data_kind(None, [x[0][index], x[1][index]], [y[index], y[index]]) + == "vectors" + ): file_context = lib.virtualfile_from_vectors( - np.atleast_1d([x[0][index], x[1][index]]), [y[index], y[index]]) + np.atleast_1d([x[0][index], x[1][index]]), [y[index], y[index]] + ) else: raise GMTInvalidInput("Unrecognized data type.") From 457bf72ddc4519c95b006640bf62161cc75aa6f3 Mon Sep 17 00:00:00 2001 From: Michael Grund Date: Tue, 10 Aug 2021 21:12:29 +0200 Subject: [PATCH 13/43] adjust docstring --- pygmt/src/hlines.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index 106ac5e45e8..6c1caae43bb 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -74,7 +74,8 @@ def prep_data(xmin, xmax, list_length): if xmin is None and xmax is None: with Session() as lib: - # get limits from current map boundings if not given via xmin, xmax + # get limits from current map boundings if not given + # via xmin, xmax x = np.array([[lib.extract_region()[0]], [lib.extract_region()[1]]]) x = np.repeat(x, list_length, axis=1) elif xmin is None or xmax is None: @@ -83,7 +84,8 @@ def prep_data(xmin, xmax, list_length): ) else: - # if only a single xmin and xmax without [], repeat to fit size of y + # if only a single xmin and xmax without [], repeat to fit size + # of y if isinstance(xmin, (int, float)): x = np.array([[xmin], [xmax]]) x = np.repeat(x, list_length, axis=1) From f2c84a7bf8e1cf6c1ab779a3dab14a4ea73f5a1a Mon Sep 17 00:00:00 2001 From: Michael Grund Date: Tue, 10 Aug 2021 21:26:51 +0200 Subject: [PATCH 14/43] adjust priliminary tests --- pygmt/tests/test_hlines.py | 43 ++------------------------------------ 1 file changed, 2 insertions(+), 41 deletions(-) diff --git a/pygmt/tests/test_hlines.py b/pygmt/tests/test_hlines.py index 021e936ba53..896aa68b14a 100644 --- a/pygmt/tests/test_hlines.py +++ b/pygmt/tests/test_hlines.py @@ -13,10 +13,8 @@ def test_hlines_value_sets(): fig_ref, fig_test = Figure(), Figure() + fig_ref.basemap(region=[0, 10, 0, 20], projection="X10c/10c", frame=True) fig_ref.hlines( - region=[0, 10, 0, 20], - projection="X10c/10c", - frame=True, y=[5.5, 10, 6, 11], xmin=[3.1, 6, 0, 1], xmax=[5.5, 7.8, 10, 9], @@ -24,10 +22,8 @@ def test_hlines_value_sets(): pen="4p,green", ) + fig_test.basemap(region="0/10/0/20", projection="X10c/10c", frame=True) fig_test.hlines( - region="0/10/0/20", - projection="X10c/10c", - frame=True, y=[5.5, 10, 6, 11], xmin=[3.1, 6, 0, 1], xmax=[5.5, 7.8, 10, 9], @@ -36,38 +32,3 @@ def test_hlines_value_sets(): ) return fig_ref, fig_test - - -@check_figures_equal() -def test_hlines_zvalue(): - """ - Passing in color via zvalue. - """ - - fig_ref, fig_test = Figure(), Figure() - - fig_ref.hlines( - region=[0, 10, 0, 20], - projection="X10c/10c", - frame=True, - y=[9.9], - xmin=2, - xmax=6, - pen="5p,+z", - zvalue="0.5", - cmap="magma", - ) - - fig_test.hlines( - region="0/10/0/20", - projection="X10c/10c", - frame=True, - y=[9.9], - xmin=2, - xmax=6, - pen="5p,+z", - zvalue="0.5", - cmap="magma", - ) - - return fig_ref, fig_test From e7726c0d172c84469a464f1c9868b4a7b5f0448a Mon Sep 17 00:00:00 2001 From: Michael Grund Date: Wed, 11 Aug 2021 18:32:05 +0200 Subject: [PATCH 15/43] update --- pygmt/src/hlines.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index 6c1caae43bb..0c9e3153ee4 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -74,8 +74,7 @@ def prep_data(xmin, xmax, list_length): if xmin is None and xmax is None: with Session() as lib: - # get limits from current map boundings if not given - # via xmin, xmax + # get limits from current map boundings if not given via xmin, xmax x = np.array([[lib.extract_region()[0]], [lib.extract_region()[1]]]) x = np.repeat(x, list_length, axis=1) elif xmin is None or xmax is None: @@ -84,8 +83,7 @@ def prep_data(xmin, xmax, list_length): ) else: - # if only a single xmin and xmax without [], repeat to fit size - # of y + # if only a single xmin and xmax without [], repeat to fit size of y if isinstance(xmin, (int, float)): x = np.array([[xmin], [xmax]]) x = np.repeat(x, list_length, axis=1) @@ -101,11 +99,13 @@ def prep_style(kwargs, list_length): # prepare labels if "l" in kwargs: - # if several lines belong to the same label, first set all to the - # label given via "l", then reset all entries except the first + # if several lines belong to the same label, first set all to None + # then replace first entry by the label given via "l" if not isinstance(kwargs["l"], list): - kwargs["l"] = np.repeat(kwargs["l"], list_length) - kwargs["l"][1:list_length] = None + label2use = kwargs["l"] + kwargs["l"] = np.repeat(None, list_length) + kwargs["l"][0] = label2use + else: kwargs["l"] = np.repeat(None, list_length) From 46f87023467de92dbe2e0cd6de60c39add2af316 Mon Sep 17 00:00:00 2001 From: Michael Grund Date: Wed, 11 Aug 2021 18:35:52 +0200 Subject: [PATCH 16/43] formatting --- pygmt/src/hlines.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index 0c9e3153ee4..80160846859 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -74,7 +74,8 @@ def prep_data(xmin, xmax, list_length): if xmin is None and xmax is None: with Session() as lib: - # get limits from current map boundings if not given via xmin, xmax + # get limits from current map boundings if not given + # via xmin, xmax x = np.array([[lib.extract_region()[0]], [lib.extract_region()[1]]]) x = np.repeat(x, list_length, axis=1) elif xmin is None or xmax is None: @@ -83,7 +84,8 @@ def prep_data(xmin, xmax, list_length): ) else: - # if only a single xmin and xmax without [], repeat to fit size of y + # if only a single xmin and xmax without [], repeat to fit + # size of y if isinstance(xmin, (int, float)): x = np.array([[xmin], [xmax]]) x = np.repeat(x, list_length, axis=1) From d493a1c1b26cfc2efada79743ca8583d5c632cfd Mon Sep 17 00:00:00 2001 From: Michael Grund <23025878+michaelgrund@users.noreply.github.com> Date: Wed, 4 Sep 2024 09:42:50 +0200 Subject: [PATCH 17/43] Update pygmt/src/hlines.py Co-authored-by: Dongdong Tian --- pygmt/src/hlines.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index 80160846859..a8d3f237272 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -73,11 +73,9 @@ def hlines(self, y=None, xmin=None, xmax=None, **kwargs): def prep_data(xmin, xmax, list_length): if xmin is None and xmax is None: - with Session() as lib: - # get limits from current map boundings if not given - # via xmin, xmax - x = np.array([[lib.extract_region()[0]], [lib.extract_region()[1]]]) - x = np.repeat(x, list_length, axis=1) + # get limits from current map boundings if not given via xmin, xmax + xmin, xmax = fig.region[0:2] + x = np.repeat([[xmin], [xmax]], list_length, axis=1) elif xmin is None or xmax is None: raise GMTInvalidInput( "Must provide both, xmin and xmax if limits are not set automatically." From ed7de940a2413c22c5e0a262487d5d33afebd05e Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 20 Nov 2024 16:19:48 +0800 Subject: [PATCH 18/43] Fix styling --- examples/gallery/lines/horizontal_lines.py | 4 ++-- pygmt/src/hlines.py | 28 ++++++++++------------ pygmt/tests/test_hlines.py | 1 + 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/examples/gallery/lines/horizontal_lines.py b/examples/gallery/lines/horizontal_lines.py index 0ce5490c7ee..349742516e1 100644 --- a/examples/gallery/lines/horizontal_lines.py +++ b/examples/gallery/lines/horizontal_lines.py @@ -2,9 +2,9 @@ Plot horizontal lines --------------------- -The :meth:`pygmt.Figure.hlines` method can plot horizontal lines based on +The :meth:`pygmt.Figure.hlines` method can plot horizontal lines based on a given y value. Optionally, the lower and upper limits of the lines can be -defined, otherwise the current map boundaries are taken. +defined, otherwise the current map boundaries are taken. """ diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index a8d3f237272..f23f985fe08 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -1,6 +1,7 @@ """ hlines - Plot horizontal lines. """ + import numpy as np from pygmt.clib import Session from pygmt.exceptions import GMTInvalidInput @@ -36,6 +37,7 @@ def hlines(self, y=None, xmin=None, xmax=None, **kwargs): under this label in the legend (if shown). If each line should appear as a single entry in the legend, give corresponding labels for all lines (same for **pen**). + Parameters ---------- y : float or 1d array @@ -63,40 +65,35 @@ def hlines(self, y=None, xmin=None, xmax=None, **kwargs): for lines. """ - - kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access + kwargs = self._preprocess(**kwargs) y = np.atleast_1d(y) list_length = len(y) # prepare x values def prep_data(xmin, xmax, list_length): - if xmin is None and xmax is None: # get limits from current map boundings if not given via xmin, xmax - xmin, xmax = fig.region[0:2] + xmin, xmax = self.region[0:2] x = np.repeat([[xmin], [xmax]], list_length, axis=1) elif xmin is None or xmax is None: raise GMTInvalidInput( "Must provide both, xmin and xmax if limits are not set automatically." ) + # if only a single xmin and xmax without [], repeat to fit + # size of y + elif isinstance(xmin, int | float): + x = np.array([[xmin], [xmax]]) + x = np.repeat(x, list_length, axis=1) + elif len(xmin) != len(xmax): + GMTInvalidInput("Must provide same length for xmin and xmax.") else: - # if only a single xmin and xmax without [], repeat to fit - # size of y - if isinstance(xmin, (int, float)): - x = np.array([[xmin], [xmax]]) - x = np.repeat(x, list_length, axis=1) - else: - if len(xmin) != len(xmax): - GMTInvalidInput("Must provide same length for xmin and xmax.") - else: - x = np.array([xmin, xmax]) + x = np.array([xmin, xmax]) return np.atleast_1d(x) def prep_style(kwargs, list_length): - # prepare labels if "l" in kwargs: # if several lines belong to the same label, first set all to None @@ -125,7 +122,6 @@ def prep_style(kwargs, list_length): kwargs_copy = kwargs.copy() for index in range(list_length): - with Session() as lib: if ( data_kind(None, [x[0][index], x[1][index]], [y[index], y[index]]) diff --git a/pygmt/tests/test_hlines.py b/pygmt/tests/test_hlines.py index 896aa68b14a..f7d39789b2e 100644 --- a/pygmt/tests/test_hlines.py +++ b/pygmt/tests/test_hlines.py @@ -1,6 +1,7 @@ """ Tests for hline. """ + from pygmt import Figure from pygmt.helpers.testing import check_figures_equal From 1ae727a41803b088076d0ac4186fa175f881a52a Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 20 Nov 2024 18:31:47 +0800 Subject: [PATCH 19/43] Simplify the logic of codes --- pygmt/src/hlines.py | 222 +++++++++++++++++++------------------------- 1 file changed, 94 insertions(+), 128 deletions(-) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index f23f985fe08..19a7741bfcc 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -2,139 +2,105 @@ hlines - Plot horizontal lines. """ +from collections.abc import Sequence + import numpy as np -from pygmt.clib import Session from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import ( - build_arg_string, - data_kind, - fmt_docstring, - kwargs_to_strings, - use_alias, -) +from pygmt.helpers import is_nonstr_iter + + +def hlines( + self, + y: int | float | Sequence[int | float], + xmin=None, + xmax=None, + pen=None, + label=None, + transparency=None, + no_clip: bool = False, + perspective=None, +): + """ + Plot one or multiple horizontal line(s) at specified y-coordinates. + This method is a high-level wrapper around :meth:`pygmt.Figure.plot`, to plot one or + multiple horizontal lines at specified y-coordinates. By default, the lines are + plotted between the x-limits of the current plot, but this can be overridden by + specifying the ``xmin`` and ``xmax`` parameters to set the x-coordinates of the + start and end points of the lines. -@fmt_docstring -@use_alias( - N="no_clip", - V="verbose", - W="pen", - l="label", - p="perspective", - t="transparency", -) -@kwargs_to_strings(p="sequence") -def hlines(self, y=None, xmin=None, xmax=None, **kwargs): - """ - Plot one or a collection of horizontal lines. - Takes a single y value or a list of individual y values and optionally - lower and upper x value limits as input. - Must provide *y*. - If y values are given without x limits then the current map boundaries are - used as lower and upper limits. If only a single set of x limits is given - then all lines will have the same length, otherwise give x limits for each - individual line. If only a single label is given then all lines are grouped - under this label in the legend (if shown). If each line should appear as a - single entry in the legend, give corresponding labels for all lines - (same for **pen**). + ``y`` can be a single value or a sequence of values. If a single value, the line is + plotted between ``xmin`` and ``xmax``. Similarly, ``xmin`` and ``xmax`` can be a + single value or a sequence of values. If a sequence, the length of ``xmin`` and + ``xmax`` must match the length of ``y``. Parameters ---------- - y : float or 1d array - The y coordinates or an array of y coordinates of the - horizontal lines to plot. - no_clip : bool or str - ``'[c|r]'``. - Do NOT clip lines that fall outside map border [Default plots - lines whose coordinates are strictly inside the map border only]. - The option does not apply to lines which are always - clipped to the map region. For periodic (360-longitude) maps we - must plot all lines twice in case they are clipped by the - repeating boundary. ``no_clip=True`` will turn off clipping and not - plot repeating lines. Use ``no_clip="r"`` to turn off clipping - but retain the plotting of such repeating lines, or use - ``no_clip="c"`` to retain clipping but turn off plotting of - repeating lines. - {W} - {V} - label : str - Add a legend entry for the line being plotted. - {p} - {t} - *transparency* can also be a 1d array to set varying transparency - for lines. - + y + Y-coordinates to plot the lines. It can be a single value or a sequence of + values. + xmin + X-coordinates of the start point of the line(s). If ``None``, defaults to the + minimum x-value of the current plot. + xmax + X-coordinates of the end point of the line(s). If ``None``, defaults to the + maximum x-value of the current plot. + pen + Pen attributes for the line(s). + label + Label for the line(s), to be displayed in the legend. + transparency + Transparency level for the lines, in [0-100] percent range. Defaults to 0, i.e., + opaque. Only visible when saving figures in PDF or raster formats. + no_clip + If ``True``, do not clip the lines outside the plot region. + perspective + Select perspective view and set the azimuth and elevation angle of the + viewpoint. Refer to :method:`pygmt.Figure.plot` for details. + + Examples + -------- + >>> import pygmt + >>> fig = pygmt.Figure() + >>> fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True) + >>> fig.hlines(y=1, pen="1p,black", label="Line 1") + >>> fig.hlines(y=2, xmin=2, xmax=8, pen="1p,red,-", label="Line 2") + >>> fig.hlines(y=[3, 4], xmin=3, xmax=7, pen="1p,black,.", label="Line 3") + >>> fig.hlines(y=[5, 6], xmin=4, xmax=9, pen="1p,red", label="Line 4") + >>> fig.hlines(y=[7, 8], xmin=[0, 1], xmax=[7, 8], pen="1p,blue", label="Line 5") + >>> fig.legend() + >>> fig.show() """ - kwargs = self._preprocess(**kwargs) - - y = np.atleast_1d(y) - list_length = len(y) - - # prepare x values - def prep_data(xmin, xmax, list_length): - if xmin is None and xmax is None: - # get limits from current map boundings if not given via xmin, xmax - xmin, xmax = self.region[0:2] - x = np.repeat([[xmin], [xmax]], list_length, axis=1) - elif xmin is None or xmax is None: - raise GMTInvalidInput( - "Must provide both, xmin and xmax if limits are not set automatically." - ) - - # if only a single xmin and xmax without [], repeat to fit - # size of y - elif isinstance(xmin, int | float): - x = np.array([[xmin], [xmax]]) - x = np.repeat(x, list_length, axis=1) - elif len(xmin) != len(xmax): - GMTInvalidInput("Must provide same length for xmin and xmax.") - else: - x = np.array([xmin, xmax]) - - return np.atleast_1d(x) - - def prep_style(kwargs, list_length): - # prepare labels - if "l" in kwargs: - # if several lines belong to the same label, first set all to None - # then replace first entry by the label given via "l" - if not isinstance(kwargs["l"], list): - label2use = kwargs["l"] - kwargs["l"] = np.repeat(None, list_length) - kwargs["l"][0] = label2use - - else: - kwargs["l"] = np.repeat(None, list_length) - - # prepare pens - if "W" in kwargs: - # select pen, no series - if not isinstance(kwargs["W"], list): - kwargs["W"] = np.repeat(kwargs["W"], list_length) - else: # use as default if no pen is given (neither single nor series) - kwargs["W"] = np.repeat("1p,black", list_length) - - return kwargs - - # loop over entries - x = prep_data(xmin, xmax, list_length) - kwargs = prep_style(kwargs, list_length) - kwargs_copy = kwargs.copy() - - for index in range(list_length): - with Session() as lib: - if ( - data_kind(None, [x[0][index], x[1][index]], [y[index], y[index]]) - == "vectors" - ): - file_context = lib.virtualfile_from_vectors( - np.atleast_1d([x[0][index], x[1][index]]), [y[index], y[index]] - ) - else: - raise GMTInvalidInput("Unrecognized data type.") - - kwargs["l"] = kwargs_copy["l"][index] - kwargs["W"] = kwargs_copy["W"][index] - - with file_context as fname: - lib.call_module("plot", " ".join([fname, build_arg_string(kwargs)])) + self._preprocess() + + # Determine the x limits. + if xmin is None and xmax is None: + _xmin, _xmax = self.region[0:2] # Get x limits from current plot region + if xmin is None: + xmin = _xmin + if xmax is None: + xmax = _xmax + + _y = np.atleast_1d(y) + nlines = len(_y) # Number of lines to plot. + # Repeat xmin and xmax to match the length of y if they are scalars. + _xmin = np.atleast_1d(xmin) if is_nonstr_iter(xmin) else np.repeat(xmin, nlines) + _xmax = np.atleast_1d(xmax) if is_nonstr_iter(xmax) else np.repeat(xmax, nlines) + + # Validate the xmin and xmax arguments. + if _xmin.size != nlines or _xmax.size != nlines: + msg = f"'xmin' and 'xmax' are expected to be scalars or have a length of {nlines}." + raise GMTInvalidInput(msg) + + # Loop over horizontal lines + for i in range(nlines): + _label = label if i == 0 else None + self.plot( + x=[_xmin[i], _xmax[i]], + y=[_y[i], _y[i]], + pen=pen, + label=_label, + transparency=transparency, + no_clip=no_clip, + perspective=perspective, + ) From 8fee6a85bfcbb3441fb8e2c097c53ea315ce5018 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 20 Nov 2024 18:42:35 +0800 Subject: [PATCH 20/43] Remove the gallery example --- examples/gallery/lines/horizontal_lines.py | 30 ---------------------- 1 file changed, 30 deletions(-) delete mode 100644 examples/gallery/lines/horizontal_lines.py diff --git a/examples/gallery/lines/horizontal_lines.py b/examples/gallery/lines/horizontal_lines.py deleted file mode 100644 index 349742516e1..00000000000 --- a/examples/gallery/lines/horizontal_lines.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -Plot horizontal lines ---------------------- - -The :meth:`pygmt.Figure.hlines` method can plot horizontal lines based on -a given y value. Optionally, the lower and upper limits of the lines can be -defined, otherwise the current map boundaries are taken. - -""" - -import pygmt - -fig = pygmt.Figure() -fig.basemap(region=[0, 10, 0, 11], projection="X10c/10c", frame=True) - -fig.hlines(1, label="line1") -fig.hlines([2, 3], pen="2p,dodgerblue4", label="line2") -fig.hlines([4, 5], xmin=2, xmax=8, pen="2p,red3", label="line3") -fig.hlines([6, 7], xmin=[1, 3], xmax=[8, 7], pen="3p,seagreen", label="line4") -fig.hlines( - [8, 9, 10], - xmin=[1.3, 3, 2], - xmax=[6.5, 7, 5], - pen=["4p,darkmagenta", "2p,gold,--", "3.5p,blue,."], - label=["line5", "line6", "line7"], - transparency=50, -) - -fig.legend() -fig.show() From 67b14971d3658d5f8941ec31a88fba3fa066b3f4 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 20 Nov 2024 22:56:09 +0800 Subject: [PATCH 21/43] Finalize hlines source code --- pygmt/src/hlines.py | 83 ++++++++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 34 deletions(-) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index 19a7741bfcc..acf017ab43a 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -6,7 +6,6 @@ import numpy as np from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import is_nonstr_iter def hlines( @@ -21,39 +20,42 @@ def hlines( perspective=None, ): """ - Plot one or multiple horizontal line(s) at specified y-coordinates. + Plot one or multiple horizontal line(s). - This method is a high-level wrapper around :meth:`pygmt.Figure.plot`, to plot one or - multiple horizontal lines at specified y-coordinates. By default, the lines are - plotted between the x-limits of the current plot, but this can be overridden by - specifying the ``xmin`` and ``xmax`` parameters to set the x-coordinates of the - start and end points of the lines. + This method is a high-level wrapper around :meth:`pygmt.Figure.plot`, focusing on + plotting horizontal lines at Y-coordinates specified by the ``y`` parameter. The + ``y`` parameter can be a single value (for a single horizontal line) or a sequence + of values (for multiple horizontal lines). - ``y`` can be a single value or a sequence of values. If a single value, the line is - plotted between ``xmin`` and ``xmax``. Similarly, ``xmin`` and ``xmax`` can be a - single value or a sequence of values. If a sequence, the length of ``xmin`` and - ``xmax`` must match the length of ``y``. + By default, the X-coordinates of the start and end points of the lines are set to + be the X-limits of the current plot, but this can be overridden by specifying the + ``xmin`` and ``xmax`` parameters. ``xmin`` and ``xmax`` can either be a single + value or a sequence of values. If a single value is provided, it is applied to all + lines. If a sequence is provided, the length of ``xmin`` and ``xmax`` must match + the length of ``y``. + + Currently, it only works for Cartesian coordinate system. Parameters ---------- y - Y-coordinates to plot the lines. It can be a single value or a sequence of - values. + Y-coordinates to plot the lines. It can be a single value (for a single line) + or a sequence of values (for multiple lines). xmin X-coordinates of the start point of the line(s). If ``None``, defaults to the - minimum x-value of the current plot. + minimum X-limit of the current plot. xmax X-coordinates of the end point of the line(s). If ``None``, defaults to the - maximum x-value of the current plot. + maximum X-limit of the current plot. pen - Pen attributes for the line(s). + Pen attributes for the line(s), in the format of *width,color,style*. label Label for the line(s), to be displayed in the legend. transparency Transparency level for the lines, in [0-100] percent range. Defaults to 0, i.e., opaque. Only visible when saving figures in PDF or raster formats. no_clip - If ``True``, do not clip the lines outside the plot region. + If ``True``, do not clip lines outside the plot region. perspective Select perspective view and set the azimuth and elevation angle of the viewpoint. Refer to :method:`pygmt.Figure.plot` for details. @@ -73,31 +75,44 @@ def hlines( """ self._preprocess() - # Determine the x limits. - if xmin is None and xmax is None: - _xmin, _xmax = self.region[0:2] # Get x limits from current plot region + # Determine the x limits if not specified. + if xmin is None or xmax is None: + xlimits = self.region[:2] # Get x limits from current plot region if xmin is None: - xmin = _xmin + xmin = xlimits[0] if xmax is None: - xmax = _xmax + xmax = xlimits[1] + + # Prepare the y, xmin, and xmax arrays. + y = np.atleast_1d(y) + xmin = np.atleast_1d(xmin) + xmax = np.atleast_1d(xmax) - _y = np.atleast_1d(y) - nlines = len(_y) # Number of lines to plot. - # Repeat xmin and xmax to match the length of y if they are scalars. - _xmin = np.atleast_1d(xmin) if is_nonstr_iter(xmin) else np.repeat(xmin, nlines) - _xmax = np.atleast_1d(xmax) if is_nonstr_iter(xmax) else np.repeat(xmax, nlines) + # Check if xmin/xmax are scalars or have the same length. + if xmin.size != xmax.size: + msg = "'xmin' and 'xmax' are expected to be scalars or have the same length." + raise GMTInvalidInput(msg) - # Validate the xmin and xmax arguments. - if _xmin.size != nlines or _xmax.size != nlines: - msg = f"'xmin' and 'xmax' are expected to be scalars or have a length of {nlines}." + nlines = len(y) # Number of lines to plot. + # Ensure _xmin/_xmax match the _y length if they're scalars or have length 1. + if xmin.size == 1 and xmax.size == 1: + xmin = np.repeat(xmin, nlines) + xmax = np.repeat(xmax, nlines) + + # Check if _xmin/_xmax match the _y length. + if xmin.size != nlines or xmax.size != nlines: + msg = ( + f"'xmin' and 'xmax' are expected to have length '{nlines}' but " + f"have length '{xmin.size}' and '{xmax.size}'." + ) raise GMTInvalidInput(msg) - # Loop over horizontal lines + # Call the plot method to plot the lines. for i in range(nlines): - _label = label if i == 0 else None + _label = label if label else None # One label for multiple lines self.plot( - x=[_xmin[i], _xmax[i]], - y=[_y[i], _y[i]], + x=[xmin[i], xmax[i]], + y=[y[i], y[i]], pen=pen, label=_label, transparency=transparency, From cd0ce35590e71370c9508e829c2a39f82fb28eb7 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 20 Nov 2024 23:30:06 +0800 Subject: [PATCH 22/43] Fix hlines --- pygmt/src/hlines.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index acf017ab43a..3f9692e35f8 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -75,16 +75,18 @@ def hlines( """ self._preprocess() - # Determine the x limits if not specified. + # Ensure y is a 1D array. + y = np.atleast_1d(y) + nlines = len(y) # Number of lines to plot. + + # Ensure xmin and xmax are 1D arrays. + # First, determine the x limits if not specified. if xmin is None or xmax is None: xlimits = self.region[:2] # Get x limits from current plot region if xmin is None: - xmin = xlimits[0] + xmin = np.full(nlines, xlimits[0]) if xmax is None: - xmax = xlimits[1] - - # Prepare the y, xmin, and xmax arrays. - y = np.atleast_1d(y) + xmax = np.full(nlines, xlimits[1]) xmin = np.atleast_1d(xmin) xmax = np.atleast_1d(xmax) @@ -93,7 +95,6 @@ def hlines( msg = "'xmin' and 'xmax' are expected to be scalars or have the same length." raise GMTInvalidInput(msg) - nlines = len(y) # Number of lines to plot. # Ensure _xmin/_xmax match the _y length if they're scalars or have length 1. if xmin.size == 1 and xmax.size == 1: xmin = np.repeat(xmin, nlines) @@ -109,7 +110,12 @@ def hlines( # Call the plot method to plot the lines. for i in range(nlines): - _label = label if label else None # One label for multiple lines + # Special handling for label. + # 1. Only specify label when plotting the first line. + # 2. The -l option can accept comma-separated labels for labeling multiple lines + # with auto-coloring enabled. We don't need this feature here, so we need to + # replace comma with \054 if the label contains commas. + _label = label.replace(",", "\\054") if label and i == 0 else None self.plot( x=[xmin[i], xmax[i]], y=[y[i], y[i]], From e60b0645af4dc61c96385540a5b57368e55ee7dd Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 20 Nov 2024 23:30:19 +0800 Subject: [PATCH 23/43] Add tests for Figure.hlines --- .../test_hlines_multiple_lines.png.dvc | 5 ++ .../baseline/test_hlines_one_line.png.dvc | 5 ++ pygmt/tests/test_hlines.py | 70 +++++++++++++------ 3 files changed, 58 insertions(+), 22 deletions(-) create mode 100644 pygmt/tests/baseline/test_hlines_multiple_lines.png.dvc create mode 100644 pygmt/tests/baseline/test_hlines_one_line.png.dvc diff --git a/pygmt/tests/baseline/test_hlines_multiple_lines.png.dvc b/pygmt/tests/baseline/test_hlines_multiple_lines.png.dvc new file mode 100644 index 00000000000..2498a0a19b5 --- /dev/null +++ b/pygmt/tests/baseline/test_hlines_multiple_lines.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: a197938e93553232447205b03c9132b3 + size: 13801 + hash: md5 + path: test_hlines_multiple_lines.png diff --git a/pygmt/tests/baseline/test_hlines_one_line.png.dvc b/pygmt/tests/baseline/test_hlines_one_line.png.dvc new file mode 100644 index 00000000000..aa42ce3f492 --- /dev/null +++ b/pygmt/tests/baseline/test_hlines_one_line.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: 121970f75d34c552e632cacc692f09e9 + size: 13685 + hash: md5 + path: test_hlines_one_line.png diff --git a/pygmt/tests/test_hlines.py b/pygmt/tests/test_hlines.py index f7d39789b2e..1427bfabb5e 100644 --- a/pygmt/tests/test_hlines.py +++ b/pygmt/tests/test_hlines.py @@ -1,35 +1,61 @@ """ -Tests for hline. +Tests for Figure.hlines. """ +import pytest from pygmt import Figure -from pygmt.helpers.testing import check_figures_equal +from pygmt.exceptions import GMTInvalidInput -@check_figures_equal() -def test_hlines_value_sets(): +@pytest.mark.mpl_image_compare +def test_hlines_one_line(): """ - Passing sets of y, xmin and xmax. + Plot one horizontal line. """ + fig = Figure() + fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True) + fig.hlines(1) + fig.hlines(2, xmin=1) + fig.hlines(3, xmax=9) + fig.hlines(4, xmin=3, xmax=8) + fig.hlines(5, xmin=4, xmax=8, pen="1p,blue", label="Line at y=5") + fig.hlines(6, xmin=5, xmax=7, pen="1p,red", label="Line at y=6") + fig.legend() + return fig - fig_ref, fig_test = Figure(), Figure() - fig_ref.basemap(region=[0, 10, 0, 20], projection="X10c/10c", frame=True) - fig_ref.hlines( - y=[5.5, 10, 6, 11], - xmin=[3.1, 6, 0, 1], - xmax=[5.5, 7.8, 10, 9], - label="test2", - pen="4p,green", +@pytest.mark.mpl_image_compare +def test_hlines_multiple_lines(): + """ + Plot multiple horizontal lines. + """ + fig = Figure() + fig.basemap(region=[0, 10, 0, 16], projection="X10c/10c", frame=True) + fig.hlines([1, 2]) + fig.hlines([3, 4, 5], xmin=[1, 2, 3]) + fig.hlines([6, 7, 8], xmax=[7, 8, 9]) + fig.hlines([9, 10], xmin=[1, 2], xmax=[9, 10]) + fig.hlines([11, 12], xmin=1, xmax=9, pen="1p,blue", label="Line at y=11,12") + fig.hlines( + [13, 14], xmin=[3, 4], xmax=[8, 9], pen="1p,red", label="Line at y=13,14" ) + fig.legend() + return fig - fig_test.basemap(region="0/10/0/20", projection="X10c/10c", frame=True) - fig_test.hlines( - y=[5.5, 10, 6, 11], - xmin=[3.1, 6, 0, 1], - xmax=[5.5, 7.8, 10, 9], - label="test2", - pen="4p,green", - ) - return fig_ref, fig_test +def test_hlines_invalid_input(): + """ + Test invalid input for hlines. + """ + fig = Figure() + fig.basemap(region=[0, 10, 0, 6], projection="X10c/6c", frame=True) + with pytest.raises(GMTInvalidInput): + fig.hlines(1, xmin=2, xmax=[3, 4]) + with pytest.raises(GMTInvalidInput): + fig.hlines(1, xmin=[2, 3], xmax=4) + with pytest.raises(GMTInvalidInput): + fig.hlines(1, xmin=[2, 3], xmax=[4, 5]) + with pytest.raises(GMTInvalidInput): + fig.hlines([1, 2], xmin=[2, 3, 4], xmax=3) + with pytest.raises(GMTInvalidInput): + fig.hlines([1, 2], xmin=[2, 3], xmax=[4, 5, 6]) From f28dce0288354759b73fffbf835eddcb91864717 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 20 Nov 2024 23:32:40 +0800 Subject: [PATCH 24/43] Fix the order in the API docs --- doc/api/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/index.rst b/doc/api/index.rst index 2fb2e833820..40749561689 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -29,8 +29,8 @@ Plotting map elements Figure.basemap Figure.coast Figure.colorbar - Figure.inset Figure.hlines + Figure.inset Figure.legend Figure.logo Figure.solar From 4816dbb743298f1e9f95892cb0bedbeb00fb87b8 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 20 Nov 2024 23:33:35 +0800 Subject: [PATCH 25/43] Skip doctest --- pygmt/src/hlines.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index 3f9692e35f8..22a299e5964 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -7,6 +7,8 @@ import numpy as np from pygmt.exceptions import GMTInvalidInput +__doctest_skip__ = ["hlines"] + def hlines( self, From 49c2e97e76cb4f42081e1e433d69d215ab741bd4 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 20 Nov 2024 23:42:59 +0800 Subject: [PATCH 26/43] Improve type hints --- pygmt/src/hlines.py | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index 22a299e5964..f470e95745a 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -13,13 +13,13 @@ def hlines( self, y: int | float | Sequence[int | float], - xmin=None, - xmax=None, - pen=None, - label=None, - transparency=None, + xmin: int | float | Sequence[int | float] | None = None, + xmax: int | float | Sequence[int | float] | None = None, + pen: str | None = None, + label: str | None = None, + transparency: int | float | None = None, no_clip: bool = False, - perspective=None, + perspective: str | bool | None = None, ): """ Plot one or multiple horizontal line(s). @@ -78,35 +78,31 @@ def hlines( self._preprocess() # Ensure y is a 1D array. - y = np.atleast_1d(y) - nlines = len(y) # Number of lines to plot. + _y = np.atleast_1d(y) + nlines = len(_y) # Number of lines to plot. # Ensure xmin and xmax are 1D arrays. # First, determine the x limits if not specified. if xmin is None or xmax is None: xlimits = self.region[:2] # Get x limits from current plot region - if xmin is None: - xmin = np.full(nlines, xlimits[0]) - if xmax is None: - xmax = np.full(nlines, xlimits[1]) - xmin = np.atleast_1d(xmin) - xmax = np.atleast_1d(xmax) + _xmin = np.full(nlines, xlimits[0]) if xmin is None else np.atleast_1d(xmin) + _xmax = np.full(nlines, xlimits[1]) if xmax is None else np.atleast_1d(xmax) # Check if xmin/xmax are scalars or have the same length. - if xmin.size != xmax.size: + if _xmin.size != _xmax.size: msg = "'xmin' and 'xmax' are expected to be scalars or have the same length." raise GMTInvalidInput(msg) # Ensure _xmin/_xmax match the _y length if they're scalars or have length 1. - if xmin.size == 1 and xmax.size == 1: - xmin = np.repeat(xmin, nlines) - xmax = np.repeat(xmax, nlines) + if _xmin.size == 1 and _xmax.size == 1: + _xmin = np.repeat(_xmin, nlines) + _xmax = np.repeat(_xmax, nlines) # Check if _xmin/_xmax match the _y length. - if xmin.size != nlines or xmax.size != nlines: + if _xmin.size != nlines or _xmax.size != nlines: msg = ( f"'xmin' and 'xmax' are expected to have length '{nlines}' but " - f"have length '{xmin.size}' and '{xmax.size}'." + f"have length '{_xmin.size}' and '{_xmax.size}'." ) raise GMTInvalidInput(msg) @@ -119,8 +115,8 @@ def hlines( # replace comma with \054 if the label contains commas. _label = label.replace(",", "\\054") if label and i == 0 else None self.plot( - x=[xmin[i], xmax[i]], - y=[y[i], y[i]], + x=[_xmin[i], _xmax[i]], + y=[_y[i], _y[i]], pen=pen, label=_label, transparency=transparency, From a40ff007025140ad0ce389eaddf62580aa7d83fe Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 21 Nov 2024 16:01:09 +0800 Subject: [PATCH 27/43] Simplify type hints --- pygmt/src/hlines.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index f470e95745a..b8c5ad9ac12 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -12,12 +12,12 @@ def hlines( self, - y: int | float | Sequence[int | float], - xmin: int | float | Sequence[int | float] | None = None, - xmax: int | float | Sequence[int | float] | None = None, + y: float | Sequence[float], + xmin: float | Sequence[float] | None = None, + xmax: float | Sequence[float] | None = None, pen: str | None = None, label: str | None = None, - transparency: int | float | None = None, + transparency: float | None = None, no_clip: bool = False, perspective: str | bool | None = None, ): From d657fe3b7720a31d9f631789948612ff0e874711 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 21 Nov 2024 20:47:24 +0800 Subject: [PATCH 28/43] Support for horizontal lines in geographic projections --- pygmt/src/hlines.py | 12 ++++++-- .../test_hlines_geographic_global_d.png.dvc | 5 ++++ .../test_hlines_geographic_global_g.png.dvc | 5 ++++ pygmt/tests/test_hlines.py | 30 +++++++++++++++++++ 4 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 pygmt/tests/baseline/test_hlines_geographic_global_d.png.dvc create mode 100644 pygmt/tests/baseline/test_hlines_geographic_global_g.png.dvc diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index b8c5ad9ac12..616e4440f9d 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -114,12 +114,20 @@ def hlines( # with auto-coloring enabled. We don't need this feature here, so we need to # replace comma with \054 if the label contains commas. _label = label.replace(",", "\\054") if label and i == 0 else None + + # By default, points are connected as great circle arcs in geographic coordinate + # system and straight lines in Cartesian coordinate system (including polar + # projection). To plot "horizontal" lines along constant latitude (in geographic + # coordinate system) or constant radius (in polar projection), we need to + # resample the line to at least 4 points. + npoints = 4 # 2 for Cartesian, 4 for geographic and polar projections. self.plot( - x=[_xmin[i], _xmax[i]], - y=[_y[i], _y[i]], + x=np.linspace(_xmin[i], _xmax[i], npoints), + y=[_y[i]] * npoints, pen=pen, label=_label, transparency=transparency, no_clip=no_clip, perspective=perspective, + straight_line="m", # Any one of "m", "p", "r", "t", "x", and "y" works. ) diff --git a/pygmt/tests/baseline/test_hlines_geographic_global_d.png.dvc b/pygmt/tests/baseline/test_hlines_geographic_global_d.png.dvc new file mode 100644 index 00000000000..960f3a05fdc --- /dev/null +++ b/pygmt/tests/baseline/test_hlines_geographic_global_d.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: b7055f03ff5bc152c0f6b72f2d39f32c + size: 29336 + hash: md5 + path: test_hlines_geographic_global_d.png diff --git a/pygmt/tests/baseline/test_hlines_geographic_global_g.png.dvc b/pygmt/tests/baseline/test_hlines_geographic_global_g.png.dvc new file mode 100644 index 00000000000..29b83d3b44f --- /dev/null +++ b/pygmt/tests/baseline/test_hlines_geographic_global_g.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: ab2e7717cad6ac4132fd3e3af1fefa89 + size: 29798 + hash: md5 + path: test_hlines_geographic_global_g.png diff --git a/pygmt/tests/test_hlines.py b/pygmt/tests/test_hlines.py index 1427bfabb5e..2af634a8259 100644 --- a/pygmt/tests/test_hlines.py +++ b/pygmt/tests/test_hlines.py @@ -43,6 +43,36 @@ def test_hlines_multiple_lines(): return fig +@pytest.mark.mpl_image_compare +@pytest.mark.parametrize("region", ["g", "d"]) +def test_hlines_geographic_global(region): + """ + Plot horizontal lines in geographic coordinates. + """ + fig = Figure() + fig.basemap(region=region, projection="R15c", frame=True) + # Plot lines with longitude range of 0 to 360. + fig.hlines(10, pen="1p") + fig.hlines(20, xmin=0, xmax=360, pen="1p") + fig.hlines(30, xmin=0, xmax=180, pen="1p") + fig.hlines(40, xmin=180, xmax=360, pen="1p") + fig.hlines(50, xmin=0, xmax=90, pen="1p") + fig.hlines(60, xmin=90, xmax=180, pen="1p") + fig.hlines(70, xmin=180, xmax=270, pen="1p") + fig.hlines(80, xmin=270, xmax=360, pen="1p") + + # Plot lines with longitude range of -180 to 180. + fig.hlines(-10, pen="1p,red") + fig.hlines(-20, xmin=-180, xmax=180, pen="1p,red") + fig.hlines(-30, xmin=-180, xmax=0, pen="1p,red") + fig.hlines(-40, xmin=0, xmax=180, pen="1p,red") + fig.hlines(-50, xmin=-180, xmax=-90, pen="1p,red") + fig.hlines(-60, xmin=-90, xmax=0, pen="1p,red") + fig.hlines(-70, xmin=0, xmax=90, pen="1p,red") + fig.hlines(-80, xmin=90, xmax=180, pen="1p,red") + return fig + + def test_hlines_invalid_input(): """ Test invalid input for hlines. From a8bd1a0d62c8726dd700caefd2032b04dad82fbb Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 21 Nov 2024 20:51:45 +0800 Subject: [PATCH 29/43] Add tests for polar projection --- .../test_hlines_polar_projection.png.dvc | 5 +++++ pygmt/tests/test_hlines.py | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 pygmt/tests/baseline/test_hlines_polar_projection.png.dvc diff --git a/pygmt/tests/baseline/test_hlines_polar_projection.png.dvc b/pygmt/tests/baseline/test_hlines_polar_projection.png.dvc new file mode 100644 index 00000000000..07abc2fe14d --- /dev/null +++ b/pygmt/tests/baseline/test_hlines_polar_projection.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: 418875901a0585abab075970e3fae4e8 + size: 48651 + hash: md5 + path: test_hlines_polar_projection.png diff --git a/pygmt/tests/test_hlines.py b/pygmt/tests/test_hlines.py index 2af634a8259..24d0ed19bba 100644 --- a/pygmt/tests/test_hlines.py +++ b/pygmt/tests/test_hlines.py @@ -73,6 +73,24 @@ def test_hlines_geographic_global(region): return fig +@pytest.mark.mpl_image_compare +def test_hlines_polar_projection(): + """ + Plot horizontal lines in polar projection. + """ + fig = Figure() + fig.basemap(region=[0, 360, 0, 2], projection="P15c", frame=True) + fig.hlines(0.1, pen="1p") + fig.hlines(0.2, xmin=0, xmax=360, pen="1p") + fig.hlines(0.3, xmin=0, xmax=180, pen="1p") + fig.hlines(0.4, xmin=180, xmax=360, pen="1p") + fig.hlines(0.5, xmin=0, xmax=90, pen="1p") + fig.hlines(0.6, xmin=90, xmax=180, pen="1p") + fig.hlines(0.7, xmin=180, xmax=270, pen="1p") + fig.hlines(0.8, xmin=270, xmax=360, pen="1p") + return fig + + def test_hlines_invalid_input(): """ Test invalid input for hlines. From 457e8ebb4e8631dd484ef5dc46f156bb267d4898 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 21 Nov 2024 20:55:06 +0800 Subject: [PATCH 30/43] Fix tests for polar --- pygmt/tests/baseline/test_hlines_polar_projection.png.dvc | 4 ++-- pygmt/tests/test_hlines.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pygmt/tests/baseline/test_hlines_polar_projection.png.dvc b/pygmt/tests/baseline/test_hlines_polar_projection.png.dvc index 07abc2fe14d..4e5bef96dc6 100644 --- a/pygmt/tests/baseline/test_hlines_polar_projection.png.dvc +++ b/pygmt/tests/baseline/test_hlines_polar_projection.png.dvc @@ -1,5 +1,5 @@ outs: -- md5: 418875901a0585abab075970e3fae4e8 - size: 48651 +- md5: 0c0eeb160dd6beb06bb6d3dcc264127a + size: 57789 hash: md5 path: test_hlines_polar_projection.png diff --git a/pygmt/tests/test_hlines.py b/pygmt/tests/test_hlines.py index 24d0ed19bba..2503671d9c4 100644 --- a/pygmt/tests/test_hlines.py +++ b/pygmt/tests/test_hlines.py @@ -79,7 +79,7 @@ def test_hlines_polar_projection(): Plot horizontal lines in polar projection. """ fig = Figure() - fig.basemap(region=[0, 360, 0, 2], projection="P15c", frame=True) + fig.basemap(region=[0, 360, 0, 1], projection="P15c", frame=True) fig.hlines(0.1, pen="1p") fig.hlines(0.2, xmin=0, xmax=360, pen="1p") fig.hlines(0.3, xmin=0, xmax=180, pen="1p") From a2b72236c5c8f33722628debbb24a438bb5428dd Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 21 Nov 2024 21:06:48 +0800 Subject: [PATCH 31/43] Add tests for no_clip --- pygmt/tests/baseline/test_hlines_clip.png.dvc | 5 +++++ pygmt/tests/test_hlines.py | 12 ++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 pygmt/tests/baseline/test_hlines_clip.png.dvc diff --git a/pygmt/tests/baseline/test_hlines_clip.png.dvc b/pygmt/tests/baseline/test_hlines_clip.png.dvc new file mode 100644 index 00000000000..1c24bb1c16d --- /dev/null +++ b/pygmt/tests/baseline/test_hlines_clip.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: e87ea1b80ae5d32d49e9ad94a5c25f96 + size: 7199 + hash: md5 + path: test_hlines_clip.png diff --git a/pygmt/tests/test_hlines.py b/pygmt/tests/test_hlines.py index 2503671d9c4..5303e7e061e 100644 --- a/pygmt/tests/test_hlines.py +++ b/pygmt/tests/test_hlines.py @@ -43,6 +43,18 @@ def test_hlines_multiple_lines(): return fig +@pytest.mark.mpl_image_compare +def test_hlines_clip(): + """ + Plot horizontal lines with clipping or not. + """ + fig = Figure() + fig.basemap(region=[0, 10, 0, 4], projection="X10c/4c", frame=True) + fig.hlines(1, xmin=-2, xmax=12) + fig.hlines(2, xmin=-2, xmax=12, no_clip=True) + return fig + + @pytest.mark.mpl_image_compare @pytest.mark.parametrize("region", ["g", "d"]) def test_hlines_geographic_global(region): From 258a64fdec09617238706b04466c4c364ccac9ee Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 21 Nov 2024 21:17:46 +0800 Subject: [PATCH 32/43] Improve docstrings --- pygmt/src/hlines.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index 616e4440f9d..6e570503f1a 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -36,19 +36,24 @@ def hlines( lines. If a sequence is provided, the length of ``xmin`` and ``xmax`` must match the length of ``y``. - Currently, it only works for Cartesian coordinate system. + The term "horizontal" lines can be interpreted differently in different coordinate + systems: + + - *Cartesian* coordinate system: lines are plotted as straight lines. + - *Geographic* projection: lines are plotted as parallels along constant latitude. + - *Polar* projection: lines are plotted as arcs along constant radius. Parameters ---------- y Y-coordinates to plot the lines. It can be a single value (for a single line) or a sequence of values (for multiple lines). - xmin - X-coordinates of the start point of the line(s). If ``None``, defaults to the - minimum X-limit of the current plot. - xmax - X-coordinates of the end point of the line(s). If ``None``, defaults to the - maximum X-limit of the current plot. + xmin/xmax + X-coordinates of the start/end point of the line(s). If ``None``, defaults to + the X-limits of the current plot. ``xmin`` and ``xmax`` can either be a single + value or a sequence of values. If a single value is provided, it is applied to + all lines. If a sequence is provided, the length of ``xmin`` and ``xmax`` must + match the length of ``y``. pen Pen attributes for the line(s), in the format of *width,color,style*. label From 9e17bdf0035b20b3cc686953153e8005e3a37328 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 22 Nov 2024 10:11:36 +0800 Subject: [PATCH 33/43] Use straight_line='p' although it makes no difference --- pygmt/src/hlines.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index 6e570503f1a..9733580cecd 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -134,5 +134,5 @@ def hlines( transparency=transparency, no_clip=no_clip, perspective=perspective, - straight_line="m", # Any one of "m", "p", "r", "t", "x", and "y" works. + straight_line="p", # Any one of "m"/"p"/"r"/"t"/"x"/"y" works. ) From 25cbeb46dc75745f8f837ca6c7d1f2280e5ff91b Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 22 Nov 2024 10:12:10 +0800 Subject: [PATCH 34/43] Transparency should be set by 'pen' instead --- pygmt/src/hlines.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index 9733580cecd..07f9ca4bee4 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -17,7 +17,6 @@ def hlines( xmax: float | Sequence[float] | None = None, pen: str | None = None, label: str | None = None, - transparency: float | None = None, no_clip: bool = False, perspective: str | bool | None = None, ): @@ -58,9 +57,6 @@ def hlines( Pen attributes for the line(s), in the format of *width,color,style*. label Label for the line(s), to be displayed in the legend. - transparency - Transparency level for the lines, in [0-100] percent range. Defaults to 0, i.e., - opaque. Only visible when saving figures in PDF or raster formats. no_clip If ``True``, do not clip lines outside the plot region. perspective @@ -131,7 +127,6 @@ def hlines( y=[_y[i]] * npoints, pen=pen, label=_label, - transparency=transparency, no_clip=no_clip, perspective=perspective, straight_line="p", # Any one of "m"/"p"/"r"/"t"/"x"/"y" works. From 65565003cbbe44a8d0f480256ba37801cc02f913 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 23 Nov 2024 21:01:13 +0800 Subject: [PATCH 35/43] Refactor the codes for handling y/xmin/xmax --- pygmt/src/hlines.py | 52 +++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index 07f9ca4bee4..e9e6aca07d0 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -38,9 +38,9 @@ def hlines( The term "horizontal" lines can be interpreted differently in different coordinate systems: - - *Cartesian* coordinate system: lines are plotted as straight lines. + - **Cartesian** coordinate system: lines are plotted as straight lines. + - **Polar** projection: lines are plotted as arcs along constant radius. - *Geographic* projection: lines are plotted as parallels along constant latitude. - - *Polar* projection: lines are plotted as arcs along constant radius. Parameters ---------- @@ -58,7 +58,8 @@ def hlines( label Label for the line(s), to be displayed in the legend. no_clip - If ``True``, do not clip lines outside the plot region. + If ``True``, do not clip lines outside the plot region. Only makes sense in + Cartesian coordinate system. perspective Select perspective view and set the azimuth and elevation angle of the viewpoint. Refer to :method:`pygmt.Figure.plot` for details. @@ -78,36 +79,41 @@ def hlines( """ self._preprocess() - # Ensure y is a 1D array. + # Determine the x limits from the current plot region if not specified. + if xmin is None or xmax is None: + xlimits = self.region[:2] + if xmin is None: + xmin = xlimits[0] + if xmax is None: + xmax = xlimits[1] + + # Ensure y/xmin/xmax are 1-D arrays. _y = np.atleast_1d(y) - nlines = len(_y) # Number of lines to plot. + _xmin = np.atleast_1d(xmin) + _xmax = np.atleast_1d(xmax) - # Ensure xmin and xmax are 1D arrays. - # First, determine the x limits if not specified. - if xmin is None or xmax is None: - xlimits = self.region[:2] # Get x limits from current plot region - _xmin = np.full(nlines, xlimits[0]) if xmin is None else np.atleast_1d(xmin) - _xmax = np.full(nlines, xlimits[1]) if xmax is None else np.atleast_1d(xmax) + nlines = len(_y) # Number of lines to plot. - # Check if xmin/xmax are scalars or have the same length. + # Check if xmin/xmax have the same length or are scalars. if _xmin.size != _xmax.size: msg = "'xmin' and 'xmax' are expected to be scalars or have the same length." raise GMTInvalidInput(msg) - # Ensure _xmin/_xmax match the _y length if they're scalars or have length 1. - if _xmin.size == 1 and _xmax.size == 1: - _xmin = np.repeat(_xmin, nlines) - _xmax = np.repeat(_xmax, nlines) - - # Check if _xmin/_xmax match the _y length. - if _xmin.size != nlines or _xmax.size != nlines: + # Check if xmin/xmax have the expected length. + # Only check xmin, since we already know that xmin/xmax have the same length. + if _xmin.size not in {1, nlines}: msg = ( - f"'xmin' and 'xmax' are expected to have length '{nlines}' but " - f"have length '{_xmin.size}' and '{_xmax.size}'." + f"'xmin' and 'xmax' are expected to be scalars or have lengths '{nlines}', " + f"but lengths '{_xmin.size}' and '{_xmax.size}' are given." ) raise GMTInvalidInput(msg) - # Call the plot method to plot the lines. + # Ensure xmin/xmax have the same length as y. + if _xmin.size == 1 and nlines != 1: + _xmin = np.repeat(_xmin, nlines) + _xmax = np.repeat(_xmax, nlines) + + # Call the Figure.plot method to plot the lines. for i in range(nlines): # Special handling for label. # 1. Only specify label when plotting the first line. @@ -121,7 +127,7 @@ def hlines( # projection). To plot "horizontal" lines along constant latitude (in geographic # coordinate system) or constant radius (in polar projection), we need to # resample the line to at least 4 points. - npoints = 4 # 2 for Cartesian, 4 for geographic and polar projections. + npoints = 4 # 2 for Cartesian, at least 4 for geographic and polar projections. self.plot( x=np.linspace(_xmin[i], _xmax[i], npoints), y=[_y[i]] * npoints, From fc1898cc2ff672fbae7a4a53b94e9bb2414f535b Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 23 Nov 2024 22:10:20 +0800 Subject: [PATCH 36/43] Simplify the checking --- pygmt/src/hlines.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index e9e6aca07d0..d351203f8b4 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -94,24 +94,20 @@ def hlines( nlines = len(_y) # Number of lines to plot. - # Check if xmin/xmax have the same length or are scalars. - if _xmin.size != _xmax.size: - msg = "'xmin' and 'xmax' are expected to be scalars or have the same length." - raise GMTInvalidInput(msg) - - # Check if xmin/xmax have the expected length. - # Only check xmin, since we already know that xmin/xmax have the same length. - if _xmin.size not in {1, nlines}: + # Check if xmin/xmax are scalars or have the expected length. + if _xmin.size not in {1, nlines} or _xmax.size not in {1, nlines}: msg = ( f"'xmin' and 'xmax' are expected to be scalars or have lengths '{nlines}', " f"but lengths '{_xmin.size}' and '{_xmax.size}' are given." ) raise GMTInvalidInput(msg) - # Ensure xmin/xmax have the same length as y. - if _xmin.size == 1 and nlines != 1: - _xmin = np.repeat(_xmin, nlines) - _xmax = np.repeat(_xmax, nlines) + # Repeat xmin/xmax to match the length of y if they are scalars. + if nlines != 1: + if _xmin.size == 1: + _xmin = np.repeat(_xmin, nlines) + if _xmax.size == 1: + _xmax = np.repeat(_xmax, nlines) # Call the Figure.plot method to plot the lines. for i in range(nlines): From 7ef0406acbcdcc7a2797538fd1753c29d36366e7 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 23 Nov 2024 22:29:18 +0800 Subject: [PATCH 37/43] Fix reference to Figure.plot --- pygmt/src/hlines.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index d351203f8b4..0d445771a36 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -62,7 +62,7 @@ def hlines( Cartesian coordinate system. perspective Select perspective view and set the azimuth and elevation angle of the - viewpoint. Refer to :method:`pygmt.Figure.plot` for details. + viewpoint. Refer to :meth:`pygmt.Figure.plot` for details. Examples -------- From 762bf4e6da6dd498bc82743d3eebc9719b879c43 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 29 Nov 2024 00:59:02 +0800 Subject: [PATCH 38/43] Fix a typo [skip ci] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Yvonne Fröhlich <94163266+yvonnefroehlich@users.noreply.github.com> --- pygmt/src/hlines.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index 0d445771a36..cd0e10e2b9f 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -40,7 +40,7 @@ def hlines( - **Cartesian** coordinate system: lines are plotted as straight lines. - **Polar** projection: lines are plotted as arcs along constant radius. - - *Geographic* projection: lines are plotted as parallels along constant latitude. + - **Geographic** projection: lines are plotted as parallels along constant latitude. Parameters ---------- From 5440bf8904443d4f45c46292ba11e0ab96c7d543 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Mon, 2 Dec 2024 18:05:37 +0800 Subject: [PATCH 39/43] Fix typos Co-authored-by: Michael Grund <23025878+michaelgrund@users.noreply.github.com> --- pygmt/src/hlines.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index cd0e10e2b9f..df31fc40d28 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -39,7 +39,7 @@ def hlines( systems: - **Cartesian** coordinate system: lines are plotted as straight lines. - - **Polar** projection: lines are plotted as arcs along constant radius. + - **Polar** projection: lines are plotted as arcs along a constant radius. - **Geographic** projection: lines are plotted as parallels along constant latitude. Parameters @@ -112,16 +112,16 @@ def hlines( # Call the Figure.plot method to plot the lines. for i in range(nlines): # Special handling for label. - # 1. Only specify label when plotting the first line. + # 1. Only specify a label when plotting the first line. # 2. The -l option can accept comma-separated labels for labeling multiple lines # with auto-coloring enabled. We don't need this feature here, so we need to # replace comma with \054 if the label contains commas. _label = label.replace(",", "\\054") if label and i == 0 else None # By default, points are connected as great circle arcs in geographic coordinate - # system and straight lines in Cartesian coordinate system (including polar + # systems and straight lines in Cartesian coordinate systems (including polar # projection). To plot "horizontal" lines along constant latitude (in geographic - # coordinate system) or constant radius (in polar projection), we need to + # coordinate systems) or constant radius (in polar projection), we need to # resample the line to at least 4 points. npoints = 4 # 2 for Cartesian, at least 4 for geographic and polar projections. self.plot( From 5a0989fd60ff950494dfd4b12a8b9fa86384bd37 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 25 Dec 2024 14:27:54 +0800 Subject: [PATCH 40/43] Change straight_line='p' to 'x' --- pygmt/src/hlines.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index df31fc40d28..04fbeccac5e 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -131,5 +131,5 @@ def hlines( label=_label, no_clip=no_clip, perspective=perspective, - straight_line="p", # Any one of "m"/"p"/"r"/"t"/"x"/"y" works. + straight_line="x", ) From cc6c83f2d873f0d0fbcdc6cd8c5e5c84e3a4d589 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 26 Dec 2024 20:57:13 +0800 Subject: [PATCH 41/43] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Yvonne Fröhlich <94163266+yvonnefroehlich@users.noreply.github.com> --- pygmt/src/hlines.py | 18 +++++++++--------- pygmt/tests/test_hlines.py | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index 04fbeccac5e..00f08aa54ab 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -23,14 +23,14 @@ def hlines( """ Plot one or multiple horizontal line(s). - This method is a high-level wrapper around :meth:`pygmt.Figure.plot`, focusing on + This method is a high-level wrapper around :meth:`pygmt.Figure.plot` that focuses on plotting horizontal lines at Y-coordinates specified by the ``y`` parameter. The ``y`` parameter can be a single value (for a single horizontal line) or a sequence of values (for multiple horizontal lines). By default, the X-coordinates of the start and end points of the lines are set to be the X-limits of the current plot, but this can be overridden by specifying the - ``xmin`` and ``xmax`` parameters. ``xmin`` and ``xmax`` can either be a single + ``xmin`` and ``xmax`` parameters. ``xmin`` and ``xmax`` can be either a single value or a sequence of values. If a single value is provided, it is applied to all lines. If a sequence is provided, the length of ``xmin`` and ``xmax`` must match the length of ``y``. @@ -49,7 +49,7 @@ def hlines( or a sequence of values (for multiple lines). xmin/xmax X-coordinates of the start/end point of the line(s). If ``None``, defaults to - the X-limits of the current plot. ``xmin`` and ``xmax`` can either be a single + the X-limits of the current plot. ``xmin`` and ``xmax`` can be either a single value or a sequence of values. If a single value is provided, it is applied to all lines. If a sequence is provided, the length of ``xmin`` and ``xmax`` must match the length of ``y``. @@ -58,7 +58,7 @@ def hlines( label Label for the line(s), to be displayed in the legend. no_clip - If ``True``, do not clip lines outside the plot region. Only makes sense in + If ``True``, do not clip lines outside the plot region. Only makes sense in the Cartesian coordinate system. perspective Select perspective view and set the azimuth and elevation angle of the @@ -69,11 +69,11 @@ def hlines( >>> import pygmt >>> fig = pygmt.Figure() >>> fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True) - >>> fig.hlines(y=1, pen="1p,black", label="Line 1") - >>> fig.hlines(y=2, xmin=2, xmax=8, pen="1p,red,-", label="Line 2") - >>> fig.hlines(y=[3, 4], xmin=3, xmax=7, pen="1p,black,.", label="Line 3") - >>> fig.hlines(y=[5, 6], xmin=4, xmax=9, pen="1p,red", label="Line 4") - >>> fig.hlines(y=[7, 8], xmin=[0, 1], xmax=[7, 8], pen="1p,blue", label="Line 5") + >>> fig.hlines(y=1, pen="1p,black", label="Line at y=1") + >>> fig.hlines(y=2, xmin=2, xmax=8, pen="1p,red,-", label="Line at y=2") + >>> fig.hlines(y=[3, 4], xmin=3, xmax=7, pen="1p,black,.", label="Lines at y=3,4") + >>> fig.hlines(y=[5, 6], xmin=4, xmax=9, pen="1p,red", label="Lines at y=5,6") + >>> fig.hlines(y=[7, 8], xmin=[0, 1], xmax=[7, 8], pen="1p,blue", label="Lines at y=7,8") >>> fig.legend() >>> fig.show() """ diff --git a/pygmt/tests/test_hlines.py b/pygmt/tests/test_hlines.py index 5303e7e061e..aaaddad4f08 100644 --- a/pygmt/tests/test_hlines.py +++ b/pygmt/tests/test_hlines.py @@ -35,9 +35,9 @@ def test_hlines_multiple_lines(): fig.hlines([3, 4, 5], xmin=[1, 2, 3]) fig.hlines([6, 7, 8], xmax=[7, 8, 9]) fig.hlines([9, 10], xmin=[1, 2], xmax=[9, 10]) - fig.hlines([11, 12], xmin=1, xmax=9, pen="1p,blue", label="Line at y=11,12") + fig.hlines([11, 12], xmin=1, xmax=9, pen="1p,blue", label="Lines at y=11,12") fig.hlines( - [13, 14], xmin=[3, 4], xmax=[8, 9], pen="1p,red", label="Line at y=13,14" + [13, 14], xmin=[3, 4], xmax=[8, 9], pen="1p,red", label="Lines at y=13,14" ) fig.legend() return fig From 635d8e60dc9b7d58c731950f678623debfdb1db9 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 26 Dec 2024 20:59:01 +0800 Subject: [PATCH 42/43] Update baseline image --- pygmt/tests/baseline/test_hlines_multiple_lines.png.dvc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/tests/baseline/test_hlines_multiple_lines.png.dvc b/pygmt/tests/baseline/test_hlines_multiple_lines.png.dvc index 2498a0a19b5..e915a5a65f0 100644 --- a/pygmt/tests/baseline/test_hlines_multiple_lines.png.dvc +++ b/pygmt/tests/baseline/test_hlines_multiple_lines.png.dvc @@ -1,5 +1,5 @@ outs: -- md5: a197938e93553232447205b03c9132b3 - size: 13801 +- md5: 70c8decbffd37fc48b2eb9ff84442ec0 + size: 14139 hash: md5 path: test_hlines_multiple_lines.png From 1e643c14a54c8756027c2b52a66332cda906f884 Mon Sep 17 00:00:00 2001 From: actions-bot <58130806+actions-bot@users.noreply.github.com> Date: Thu, 26 Dec 2024 13:15:07 +0000 Subject: [PATCH 43/43] [format-command] fixes --- pygmt/src/hlines.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index 00f08aa54ab..8871bd3a825 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -73,7 +73,9 @@ def hlines( >>> fig.hlines(y=2, xmin=2, xmax=8, pen="1p,red,-", label="Line at y=2") >>> fig.hlines(y=[3, 4], xmin=3, xmax=7, pen="1p,black,.", label="Lines at y=3,4") >>> fig.hlines(y=[5, 6], xmin=4, xmax=9, pen="1p,red", label="Lines at y=5,6") - >>> fig.hlines(y=[7, 8], xmin=[0, 1], xmax=[7, 8], pen="1p,blue", label="Lines at y=7,8") + >>> fig.hlines( + ... y=[7, 8], xmin=[0, 1], xmax=[7, 8], pen="1p,blue", label="Lines at y=7,8" + ... ) >>> fig.legend() >>> fig.show() """