Skip to content

Commit 2c0f75f

Browse files
authored
Add Figure.vlines for plotting vertical lines (#3726)
1 parent d890e91 commit 2c0f75f

11 files changed

+263
-1
lines changed

doc/api/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Plotting map elements
3636
Figure.solar
3737
Figure.text
3838
Figure.timestamp
39+
Figure.vlines
3940

4041
Plotting tabular data
4142
~~~~~~~~~~~~~~~~~~~~~

pygmt/figure.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,7 @@ def _repr_html_(self) -> str:
437437
tilemap,
438438
timestamp,
439439
velo,
440+
vlines,
440441
wiggle,
441442
)
442443

pygmt/src/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
from pygmt.src.timestamp import timestamp
5858
from pygmt.src.triangulate import triangulate
5959
from pygmt.src.velo import velo
60+
from pygmt.src.vlines import vlines
6061
from pygmt.src.which import which
6162
from pygmt.src.wiggle import wiggle
6263
from pygmt.src.x2sys_cross import x2sys_cross

pygmt/src/hlines.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def hlines(
4848
Y-coordinates to plot the lines. It can be a single value (for a single line)
4949
or a sequence of values (for multiple lines).
5050
xmin/xmax
51-
X-coordinates of the start/end point of the line(s). If ``None``, defaults to
51+
X-coordinates of the start/end point(s) of the line(s). If ``None``, defaults to
5252
the X-limits of the current plot. ``xmin`` and ``xmax`` can be either a single
5353
value or a sequence of values. If a single value is provided, it is applied to
5454
all lines. If a sequence is provided, the length of ``xmin`` and ``xmax`` must

pygmt/src/vlines.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
"""
2+
vlines - Plot vertical 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__ = ["vlines"]
11+
12+
13+
def vlines(
14+
self,
15+
x: float | Sequence[float],
16+
ymin: float | Sequence[float] | None = None,
17+
ymax: 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 vertical line(s).
25+
26+
This method is a high-level wrapper around :meth:`pygmt.Figure.plot` that focuses on
27+
plotting vertical lines at X-coordinates specified by the ``x`` parameter. The ``x``
28+
parameter can be a single value (for a single vertical line) or a sequence of values
29+
(for multiple vertical lines).
30+
31+
By default, the Y-coordinates of the start and end points of the lines are set to be
32+
the Y-limits of the current plot, but this can be overridden by specifying the
33+
``ymin`` and ``ymax`` parameters. ``ymin`` and ``ymax`` can be either a single value
34+
or a sequence of values. If a single value is provided, it is applied to all lines.
35+
If a sequence is provided, the length of ``ymin`` and ``ymax`` must match the length
36+
of ``x``.
37+
38+
The term "vertical" 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 straight lines along radius.
43+
- **Geographic** projection: lines are plotted as meridians along constant
44+
longitude.
45+
46+
Parameters
47+
----------
48+
x
49+
X-coordinates to plot the lines. It can be a single value (for a single line)
50+
or a sequence of values (for multiple lines).
51+
ymin/ymax
52+
Y-coordinates of the start/end point(s) of the line(s). If ``None``, defaults to
53+
the Y-limits of the current plot. ``ymin`` and ``ymax`` can either be a single
54+
value or a sequence of values. If a single value is provided, it is applied to
55+
all lines. If a sequence is provided, the length of ``ymin`` and ``ymax`` must
56+
match the length of ``x``.
57+
pen
58+
Pen attributes for the line(s), in the format of *width,color,style*.
59+
label
60+
Label for the line(s), to be displayed in the legend.
61+
no_clip
62+
If ``True``, do not clip lines outside the plot region. Only makes sense in the
63+
Cartesian coordinate system.
64+
perspective
65+
Select perspective view and set the azimuth and elevation angle of the
66+
viewpoint. Refer to :meth:`pygmt.Figure.plot` for details.
67+
68+
Examples
69+
--------
70+
>>> import pygmt
71+
>>> fig = pygmt.Figure()
72+
>>> fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True)
73+
>>> fig.vlines(x=1, pen="1p,black", label="Line at x=1")
74+
>>> fig.vlines(x=2, ymin=2, ymax=8, pen="1p,red,-", label="Line at x=2")
75+
>>> fig.vlines(x=[3, 4], ymin=3, ymax=7, pen="1p,black,.", label="Lines at x=3,4")
76+
>>> fig.vlines(x=[5, 6], ymin=4, ymax=9, pen="1p,red", label="Lines at x=5,6")
77+
>>> fig.vlines(
78+
... x=[7, 8], ymin=[0, 1], ymax=[7, 8], pen="1p,blue", label="Lines at x=7,8"
79+
... )
80+
>>> fig.legend()
81+
>>> fig.show()
82+
"""
83+
self._preprocess()
84+
85+
# Determine the y limits from the current plot region if not specified.
86+
if ymin is None or ymax is None:
87+
ylimits = self.region[2:]
88+
if ymin is None:
89+
ymin = ylimits[0]
90+
if ymax is None:
91+
ymax = ylimits[1]
92+
93+
# Ensure x/ymin/ymax are 1-D arrays.
94+
_x = np.atleast_1d(x)
95+
_ymin = np.atleast_1d(ymin)
96+
_ymax = np.atleast_1d(ymax)
97+
98+
nlines = len(_x) # Number of lines to plot.
99+
100+
# Check if ymin/ymax are scalars or have the expected length.
101+
if _ymin.size not in {1, nlines} or _ymax.size not in {1, nlines}:
102+
msg = (
103+
f"'ymin' and 'ymax' are expected to be scalars or have lengths '{nlines}', "
104+
f"but lengths '{_ymin.size}' and '{_ymax.size}' are given."
105+
)
106+
raise GMTInvalidInput(msg)
107+
108+
# Repeat ymin/ymax to match the length of x if they are scalars.
109+
if nlines != 1:
110+
if _ymin.size == 1:
111+
_ymin = np.repeat(_ymin, nlines)
112+
if _ymax.size == 1:
113+
_ymax = np.repeat(_ymax, nlines)
114+
115+
# Call the Figure.plot method to plot the lines.
116+
for i in range(nlines):
117+
# Special handling for label.
118+
# 1. Only specify a label when plotting the first line.
119+
# 2. The -l option can accept comma-separated labels for labeling multiple lines
120+
# with auto-coloring enabled. We don't need this feature here, so we need to
121+
# replace comma with \054 if the label contains commas.
122+
_label = label.replace(",", "\\054") if label and i == 0 else None
123+
124+
self.plot(
125+
x=[_x[i], _x[i]],
126+
y=[_ymin[i], _ymax[i]],
127+
pen=pen,
128+
label=_label,
129+
no_clip=no_clip,
130+
perspective=perspective,
131+
straight_line="y",
132+
)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
outs:
2+
- md5: 4eb9c7fd7e3a803dcc3cde1409ad7fa7
3+
size: 7361
4+
hash: md5
5+
path: test_vlines_clip.png
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
outs:
2+
- md5: 3fb4a271c670e4cbe647838b6fee5a8c
3+
size: 67128
4+
hash: md5
5+
path: test_vlines_geographic_global.png
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
outs:
2+
- md5: 499b2d08832247673f208b1c0a282c4c
3+
size: 13874
4+
hash: md5
5+
path: test_vlines_multiple_lines.png
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
outs:
2+
- md5: 2cd30ad55fc660123c67e6a684a5ea21
3+
size: 13589
4+
hash: md5
5+
path: test_vlines_one_line.png
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
outs:
2+
- md5: 1981df3bd9c57cd975b6e74946496175
3+
size: 44621
4+
hash: md5
5+
path: test_vlines_polar_projection.png

0 commit comments

Comments
 (0)