Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Figure.hlines for plotting horizontal lines #923

Merged
merged 77 commits into from
Dec 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
7ed3b49
Add new modules to plot horizontal and vertical lines
michaelgrund Feb 17, 2021
096b9de
added further content
michaelgrund Feb 17, 2021
92fa9c0
added some first tests
michaelgrund Feb 17, 2021
975b305
some formatting
michaelgrund Feb 17, 2021
f25a89c
corrected typo
michaelgrund Feb 17, 2021
3cee081
Merge branch 'master' into hlines-module
michaelgrund Feb 18, 2021
a0c29fc
Merge branch 'master' into hlines-module
michaelgrund Feb 20, 2021
51dcb6a
Merge branch 'master' into hlines-module
michaelgrund Feb 20, 2021
4fdc984
Merge branch 'master' into hlines-module
michaelgrund Feb 23, 2021
154d05e
Merge branch 'master' into hlines-module
michaelgrund Apr 3, 2021
ddd04b4
Merge branch 'master' into hlines-module
michaelgrund Apr 5, 2021
d0bc35c
moved gallery example to new subfolder /lines
michaelgrund Apr 7, 2021
450d8de
Merge branch 'master' into hlines-module
michaelgrund Apr 7, 2021
f75fbe1
disable pylint warnings
michaelgrund Apr 7, 2021
a375a77
replace hyphen by underscore in gallery example file name
michaelgrund Apr 7, 2021
71113dd
disable pylint warnings
michaelgrund Apr 7, 2021
1438d43
formatting
michaelgrund Apr 7, 2021
e3a564e
Merge branch 'master' into hlines-module
michaelgrund Apr 9, 2021
d6a8fa5
Merge branch 'master' into hlines-module
michaelgrund Apr 19, 2021
ff13180
Merge branch 'master' into hlines-module
michaelgrund Apr 21, 2021
1881e95
Merge branch 'master' into hlines-module
michaelgrund Apr 22, 2021
7222e9b
Merge branch 'main' into hlines-module
michaelgrund Aug 10, 2021
65eb85d
update hlines module
michaelgrund Aug 10, 2021
2353587
Merge branch 'hlines-module' of https://github.com/GenericMappingTool…
michaelgrund Aug 10, 2021
49008ed
formatting
michaelgrund Aug 10, 2021
457bf72
adjust docstring
michaelgrund Aug 10, 2021
f2c84a7
adjust priliminary tests
michaelgrund Aug 10, 2021
50dccf9
Merge branch 'main' into hlines-module
michaelgrund Aug 11, 2021
e7726c0
update
michaelgrund Aug 11, 2021
60d6e55
Merge branch 'hlines-module' of https://github.com/GenericMappingTool…
michaelgrund Aug 11, 2021
46f8702
formatting
michaelgrund Aug 11, 2021
5648778
Merge branch 'main' into hlines-module
michaelgrund Aug 17, 2021
d553e28
Merge branch 'main' into hlines-module
michaelgrund Aug 20, 2021
d493a1c
Update pygmt/src/hlines.py
michaelgrund Sep 4, 2024
92bfca5
Merge branch 'main' into hlines-module
seisman Nov 20, 2024
ed7de94
Fix styling
seisman Nov 20, 2024
1ae727a
Simplify the logic of codes
seisman Nov 20, 2024
8fee6a8
Remove the gallery example
seisman Nov 20, 2024
67b1497
Finalize hlines source code
seisman Nov 20, 2024
cd0ce35
Fix hlines
seisman Nov 20, 2024
e60b064
Add tests for Figure.hlines
seisman Nov 20, 2024
f28dce0
Fix the order in the API docs
seisman Nov 20, 2024
4816dbb
Skip doctest
seisman Nov 20, 2024
49c2e97
Improve type hints
seisman Nov 20, 2024
a40ff00
Simplify type hints
seisman Nov 21, 2024
d657fe3
Support for horizontal lines in geographic projections
seisman Nov 21, 2024
a8bd1a0
Add tests for polar projection
seisman Nov 21, 2024
457e8eb
Fix tests for polar
seisman Nov 21, 2024
a2b7223
Add tests for no_clip
seisman Nov 21, 2024
258a64f
Improve docstrings
seisman Nov 21, 2024
ad09ece
Merge branch 'main' into hlines-module
seisman Nov 21, 2024
4e26a6f
Merge branch 'main' into hlines-module
seisman Nov 21, 2024
9e17bdf
Use straight_line='p' although it makes no difference
seisman Nov 22, 2024
25cbeb4
Transparency should be set by 'pen' instead
seisman Nov 22, 2024
afe7592
Merge branch 'main' into hlines-module
seisman Nov 22, 2024
6556500
Refactor the codes for handling y/xmin/xmax
seisman Nov 23, 2024
fc1898c
Simplify the checking
seisman Nov 23, 2024
7ef0406
Fix reference to Figure.plot
seisman Nov 23, 2024
354e090
Merge branch 'main' into hlines-module
seisman Nov 25, 2024
7a20b8c
Merge branch 'main' into hlines-module
seisman Nov 28, 2024
762bf4e
Fix a typo [skip ci]
seisman Nov 28, 2024
d88b9c2
Merge branch 'main' into hlines-module
seisman Dec 1, 2024
982112f
Merge branch 'main' into hlines-module
seisman Dec 2, 2024
5440bf8
Fix typos
seisman Dec 2, 2024
00662cf
Merge branch 'main' into hlines-module
seisman Dec 6, 2024
7299827
Merge branch 'main' into hlines-module
seisman Dec 9, 2024
118baf7
Merge branch 'main' into hlines-module
seisman Dec 12, 2024
5c99205
Merge branch 'main' into hlines-module
seisman Dec 18, 2024
e2cddd2
Merge branch 'main' into hlines-module
seisman Dec 19, 2024
d79c2b6
Merge branch 'main' into hlines-module
seisman Dec 23, 2024
5a0989f
Change straight_line='p' to 'x'
seisman Dec 25, 2024
af63f37
Merge branch 'main' into hlines-module
seisman Dec 25, 2024
f673b76
Merge branch 'main' into hlines-module
yvonnefroehlich Dec 26, 2024
cc6c83f
Apply suggestions from code review
seisman Dec 26, 2024
635d8e6
Update baseline image
seisman Dec 26, 2024
edd804e
Merge branch 'main' into hlines-module
seisman Dec 26, 2024
1e643c1
[format-command] fixes
actions-bot Dec 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Plotting map elements
Figure.basemap
Figure.coast
Figure.colorbar
Figure.hlines
Figure.inset
Figure.legend
Figure.logo
Expand Down
1 change: 1 addition & 0 deletions pygmt/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ def _repr_html_(self) -> str:
grdimage,
grdview,
histogram,
hlines,
image,
inset,
legend,
Expand Down
1 change: 1 addition & 0 deletions pygmt/src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from pygmt.src.grdview import grdview
from pygmt.src.grdvolume import grdvolume
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
Expand Down
137 changes: 137 additions & 0 deletions pygmt/src/hlines.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
"""
hlines - Plot horizontal lines.
"""

from collections.abc import Sequence

import numpy as np
from pygmt.exceptions import GMTInvalidInput

__doctest_skip__ = ["hlines"]


def hlines(
self,
y: float | Sequence[float],
xmin: float | Sequence[float] | None = None,
xmax: float | Sequence[float] | None = None,
pen: str | None = None,
label: str | None = None,
no_clip: bool = False,
perspective: str | bool | None = None,
):
"""
Plot one or multiple horizontal line(s).

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 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``.

The term "horizontal" lines can be interpreted differently in different coordinate
systems:

- **Cartesian** coordinate system: lines are plotted as straight lines.
- **Polar** projection: lines are plotted as arcs along a constant radius.
- **Geographic** projection: lines are plotted as parallels along constant latitude.

Parameters
seisman marked this conversation as resolved.
Show resolved Hide resolved
----------
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/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 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``.
pen
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.
no_clip
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
viewpoint. Refer to :meth:`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 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()
"""
self._preprocess()

# 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)
_xmin = np.atleast_1d(xmin)
_xmax = np.atleast_1d(xmax)

nlines = len(_y) # Number of lines to plot.

# 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)

# 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):
# Special handling for label.
# 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
# systems and straight lines in Cartesian coordinate systems (including polar
# projection). To plot "horizontal" lines along constant latitude (in geographic
# 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(
x=np.linspace(_xmin[i], _xmax[i], npoints),
y=[_y[i]] * npoints,
pen=pen,
label=_label,
no_clip=no_clip,
perspective=perspective,
straight_line="x",
)
5 changes: 5 additions & 0 deletions pygmt/tests/baseline/test_hlines_clip.png.dvc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
outs:
- md5: e87ea1b80ae5d32d49e9ad94a5c25f96
size: 7199
hash: md5
path: test_hlines_clip.png
5 changes: 5 additions & 0 deletions pygmt/tests/baseline/test_hlines_geographic_global_d.png.dvc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
outs:
- md5: b7055f03ff5bc152c0f6b72f2d39f32c
size: 29336
hash: md5
path: test_hlines_geographic_global_d.png
5 changes: 5 additions & 0 deletions pygmt/tests/baseline/test_hlines_geographic_global_g.png.dvc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
outs:
- md5: ab2e7717cad6ac4132fd3e3af1fefa89
size: 29798
hash: md5
path: test_hlines_geographic_global_g.png
5 changes: 5 additions & 0 deletions pygmt/tests/baseline/test_hlines_multiple_lines.png.dvc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
outs:
- md5: 70c8decbffd37fc48b2eb9ff84442ec0
size: 14139
hash: md5
path: test_hlines_multiple_lines.png
5 changes: 5 additions & 0 deletions pygmt/tests/baseline/test_hlines_one_line.png.dvc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
outs:
- md5: 121970f75d34c552e632cacc692f09e9
size: 13685
hash: md5
path: test_hlines_one_line.png
5 changes: 5 additions & 0 deletions pygmt/tests/baseline/test_hlines_polar_projection.png.dvc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
outs:
- md5: 0c0eeb160dd6beb06bb6d3dcc264127a
size: 57789
hash: md5
path: test_hlines_polar_projection.png
121 changes: 121 additions & 0 deletions pygmt/tests/test_hlines.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
"""
Tests for Figure.hlines.
"""

import pytest
from pygmt import Figure
from pygmt.exceptions import GMTInvalidInput


@pytest.mark.mpl_image_compare
def test_hlines_one_line():
"""
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


@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="Lines at y=11,12")
fig.hlines(
[13, 14], xmin=[3, 4], xmax=[8, 9], pen="1p,red", label="Lines at y=13,14"
)
fig.legend()
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):
"""
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


@pytest.mark.mpl_image_compare
def test_hlines_polar_projection():
"""
Plot horizontal lines in polar projection.
"""
fig = Figure()
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")
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.
"""
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])
Loading