|
| 1 | +""" |
| 2 | +hlines - Plot horizontal lines. |
| 3 | +""" |
| 4 | + |
| 5 | +from collections.abc import Sequence |
| 6 | + |
| 7 | +import numpy as np |
| 8 | +from pygmt.exceptions import GMTInvalidInput |
| 9 | + |
| 10 | +__doctest_skip__ = ["hlines"] |
| 11 | + |
| 12 | + |
| 13 | +def hlines( |
| 14 | + self, |
| 15 | + y: float | Sequence[float], |
| 16 | + xmin: float | Sequence[float] | None = None, |
| 17 | + xmax: float | Sequence[float] | None = None, |
| 18 | + pen: str | None = None, |
| 19 | + label: str | None = None, |
| 20 | + no_clip: bool = False, |
| 21 | + perspective: str | bool | None = None, |
| 22 | +): |
| 23 | + """ |
| 24 | + Plot one or multiple horizontal line(s). |
| 25 | +
|
| 26 | + This method is a high-level wrapper around :meth:`pygmt.Figure.plot` that focuses on |
| 27 | + plotting horizontal lines at Y-coordinates specified by the ``y`` parameter. The |
| 28 | + ``y`` parameter can be a single value (for a single horizontal line) or a sequence |
| 29 | + of values (for multiple horizontal lines). |
| 30 | +
|
| 31 | + By default, the X-coordinates of the start and end points of the lines are set to |
| 32 | + be the X-limits of the current plot, but this can be overridden by specifying the |
| 33 | + ``xmin`` and ``xmax`` parameters. ``xmin`` and ``xmax`` can be either a single |
| 34 | + value or a sequence of values. If a single value is provided, it is applied to all |
| 35 | + lines. If a sequence is provided, the length of ``xmin`` and ``xmax`` must match |
| 36 | + the length of ``y``. |
| 37 | +
|
| 38 | + The term "horizontal" lines can be interpreted differently in different coordinate |
| 39 | + systems: |
| 40 | +
|
| 41 | + - **Cartesian** coordinate system: lines are plotted as straight lines. |
| 42 | + - **Polar** projection: lines are plotted as arcs along a constant radius. |
| 43 | + - **Geographic** projection: lines are plotted as parallels along constant latitude. |
| 44 | +
|
| 45 | + Parameters |
| 46 | + ---------- |
| 47 | + y |
| 48 | + Y-coordinates to plot the lines. It can be a single value (for a single line) |
| 49 | + or a sequence of values (for multiple lines). |
| 50 | + xmin/xmax |
| 51 | + X-coordinates of the start/end point of the line(s). If ``None``, defaults to |
| 52 | + the X-limits of the current plot. ``xmin`` and ``xmax`` can be either a single |
| 53 | + value or a sequence of values. If a single value is provided, it is applied to |
| 54 | + all lines. If a sequence is provided, the length of ``xmin`` and ``xmax`` must |
| 55 | + match the length of ``y``. |
| 56 | + pen |
| 57 | + Pen attributes for the line(s), in the format of *width,color,style*. |
| 58 | + label |
| 59 | + Label for the line(s), to be displayed in the legend. |
| 60 | + no_clip |
| 61 | + If ``True``, do not clip lines outside the plot region. Only makes sense in the |
| 62 | + Cartesian coordinate system. |
| 63 | + perspective |
| 64 | + Select perspective view and set the azimuth and elevation angle of the |
| 65 | + viewpoint. Refer to :meth:`pygmt.Figure.plot` for details. |
| 66 | +
|
| 67 | + Examples |
| 68 | + -------- |
| 69 | + >>> import pygmt |
| 70 | + >>> fig = pygmt.Figure() |
| 71 | + >>> fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True) |
| 72 | + >>> fig.hlines(y=1, pen="1p,black", label="Line at y=1") |
| 73 | + >>> fig.hlines(y=2, xmin=2, xmax=8, pen="1p,red,-", label="Line at y=2") |
| 74 | + >>> fig.hlines(y=[3, 4], xmin=3, xmax=7, pen="1p,black,.", label="Lines at y=3,4") |
| 75 | + >>> fig.hlines(y=[5, 6], xmin=4, xmax=9, pen="1p,red", label="Lines at y=5,6") |
| 76 | + >>> fig.hlines( |
| 77 | + ... y=[7, 8], xmin=[0, 1], xmax=[7, 8], pen="1p,blue", label="Lines at y=7,8" |
| 78 | + ... ) |
| 79 | + >>> fig.legend() |
| 80 | + >>> fig.show() |
| 81 | + """ |
| 82 | + self._preprocess() |
| 83 | + |
| 84 | + # Determine the x limits from the current plot region if not specified. |
| 85 | + if xmin is None or xmax is None: |
| 86 | + xlimits = self.region[:2] |
| 87 | + if xmin is None: |
| 88 | + xmin = xlimits[0] |
| 89 | + if xmax is None: |
| 90 | + xmax = xlimits[1] |
| 91 | + |
| 92 | + # Ensure y/xmin/xmax are 1-D arrays. |
| 93 | + _y = np.atleast_1d(y) |
| 94 | + _xmin = np.atleast_1d(xmin) |
| 95 | + _xmax = np.atleast_1d(xmax) |
| 96 | + |
| 97 | + nlines = len(_y) # Number of lines to plot. |
| 98 | + |
| 99 | + # Check if xmin/xmax are scalars or have the expected length. |
| 100 | + if _xmin.size not in {1, nlines} or _xmax.size not in {1, nlines}: |
| 101 | + msg = ( |
| 102 | + f"'xmin' and 'xmax' are expected to be scalars or have lengths '{nlines}', " |
| 103 | + f"but lengths '{_xmin.size}' and '{_xmax.size}' are given." |
| 104 | + ) |
| 105 | + raise GMTInvalidInput(msg) |
| 106 | + |
| 107 | + # Repeat xmin/xmax to match the length of y if they are scalars. |
| 108 | + if nlines != 1: |
| 109 | + if _xmin.size == 1: |
| 110 | + _xmin = np.repeat(_xmin, nlines) |
| 111 | + if _xmax.size == 1: |
| 112 | + _xmax = np.repeat(_xmax, nlines) |
| 113 | + |
| 114 | + # Call the Figure.plot method to plot the lines. |
| 115 | + for i in range(nlines): |
| 116 | + # Special handling for label. |
| 117 | + # 1. Only specify a label when plotting the first line. |
| 118 | + # 2. The -l option can accept comma-separated labels for labeling multiple lines |
| 119 | + # with auto-coloring enabled. We don't need this feature here, so we need to |
| 120 | + # replace comma with \054 if the label contains commas. |
| 121 | + _label = label.replace(",", "\\054") if label and i == 0 else None |
| 122 | + |
| 123 | + # By default, points are connected as great circle arcs in geographic coordinate |
| 124 | + # systems and straight lines in Cartesian coordinate systems (including polar |
| 125 | + # projection). To plot "horizontal" lines along constant latitude (in geographic |
| 126 | + # coordinate systems) or constant radius (in polar projection), we need to |
| 127 | + # resample the line to at least 4 points. |
| 128 | + npoints = 4 # 2 for Cartesian, at least 4 for geographic and polar projections. |
| 129 | + self.plot( |
| 130 | + x=np.linspace(_xmin[i], _xmax[i], npoints), |
| 131 | + y=[_y[i]] * npoints, |
| 132 | + pen=pen, |
| 133 | + label=_label, |
| 134 | + no_clip=no_clip, |
| 135 | + perspective=perspective, |
| 136 | + straight_line="x", |
| 137 | + ) |
0 commit comments