From 7ed3b49ac610ba24a5356f7e62bea5dec52b3e47 Mon Sep 17 00:00:00 2001 From: Michael Grund Date: Wed, 17 Feb 2021 19:28:53 +0100 Subject: [PATCH] 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)