diff --git a/examples/gallery/lines/wiggle.py b/examples/gallery/lines/wiggle.py index ad2b37e68f5..22393499c02 100644 --- a/examples/gallery/lines/wiggle.py +++ b/examples/gallery/lines/wiggle.py @@ -2,17 +2,17 @@ Wiggle along tracks =================== -The :meth:`pygmt.Figure.wiggle` method can plot z = f(x,y) anomalies along -tracks. ``x``, ``y``, ``z`` can be specified as 1-D arrays or within a -specified file. The ``scale`` parameter can be used to set the scale of the -anomaly in data/distance units. The positive and/or negative areas can be -filled with color by setting the ``positive_fill`` and/or ``negative_fill`` -parameters. +The :meth:`pygmt.Figure.wiggle` method can plot z = f(x,y) anomalies along tracks. +``x``, ``y``, ``z`` can be specified as 1-D arrays or within a specified file. The +``scale`` parameter can be used to set the scale of the anomaly in data/distance units. +The positive and/or negative areas can be filled with color by setting the +``positive_fill`` and/or ``negative_fill`` parameters. """ # %% import numpy as np import pygmt +from pygmt.params import Position # Create (x, y, z) triplets x = np.arange(-7, 7, 0.1) @@ -25,18 +25,13 @@ x=x, y=y, z=z, - # Set anomaly scale to 20 centimeters - scale="20c", - # Fill positive areas red - positive_fill="red", - # Fill negative areas gray - negative_fill="gray", - # Set the outline width to 1.0 point - pen="1.0p", - # Draw a blue track with a width of 0.5 points - track="0.5p,blue", - # Plot a vertical scale bar at Middle Right (MR). The bar length (+w) - # is 100 in data (z) units. Set the z unit label (+l) to "nT". - position="jMR+w100+lnT", + scale="20c", # Set anomaly scale to 20 centimeters + positive_fill="red", # Fill positive areas red + negative_fill="gray", # Fill negative areas gray + pen="1.0p", # Set the outline width to 1.0 point + track="0.5p,blue", # Draw a blue track with a width of 0.5 points + position=Position("MR"), # Plot a vertical scale bar at Middle Right (MR). + length=100, # Bar length is 100 in data (z) units. + label="nT", # Set the z unit label to "nT". ) fig.show() diff --git a/pygmt/src/wiggle.py b/pygmt/src/wiggle.py index 66007fe5de5..27c3bb6b589 100644 --- a/pygmt/src/wiggle.py +++ b/pygmt/src/wiggle.py @@ -5,10 +5,12 @@ from collections.abc import Sequence from typing import Literal -from pygmt._typing import PathLike, TableLike +from pygmt._typing import AnchorCode, PathLike, TableLike from pygmt.alias import Alias, AliasSystem from pygmt.clib import Session from pygmt.helpers import build_arg_list, deprecate_parameter, fmt_docstring, use_alias +from pygmt.params import Position +from pygmt.src._common import _parse_position def _parse_fills(positive_fill, negative_fill): @@ -46,7 +48,6 @@ def _parse_fills(positive_fill, negative_fill): "fillnegative", "negative_fill", "v0.18.0", remove_version="v0.20.0" ) @use_alias( - D="position", T="track", W="pen", Z="scale", @@ -64,6 +65,10 @@ def wiggle( # noqa: PLR0913 x=None, y=None, z=None, + position: Position | Sequence[float | str] | AnchorCode | None = None, + length: float | str | None = None, + label: str | None = None, + label_alignment: Literal["left", "right"] | None = None, positive_fill=None, negative_fill=None, projection: str | None = None, @@ -72,8 +77,8 @@ def wiggle( # noqa: PLR0913 verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"] | bool = False, panel: int | Sequence[int] | bool = False, - transparency: float | None = None, perspective: float | Sequence[float] | str | bool = False, + transparency: float | None = None, incols: int | str | Sequence[int | str] | None = None, **kwargs, ): @@ -89,6 +94,7 @@ def wiggle( # noqa: PLR0913 $aliases - B = frame + - D = **+w**: length, **+l**: label, **+a**: label_alignment - G = **+p**: positive_fill, **+n**: negative_fill - J = projection - R = region @@ -107,19 +113,32 @@ def wiggle( # noqa: PLR0913 $table_classes. Use parameter ``incols`` to choose which columns are x, y, z, respectively. - $projection - $region + + position + Position of the vertical scale on the plot. It can be specified in multiple + ways: + + - A :class:`pygmt.params.Position` object to fully control the reference point, + anchor point, and offset. + - A sequence of two values representing the x and y coordinates in plot + coordinates, e.g., ``(1, 2)`` or ``("1c", "2c")``. + - A :doc:`2-character justification code ` for a + position inside the plot, e.g., ``"TL"`` for Top Left corner inside the plot. + + If not specified, defaults to the bottom-left corner of the plot with a 0.2-cm + offset. + length + Length of the vertical scale bar in data (z) units. + label + Set the z unit label that is used in the scale label [Default is no unit]. + label_alignment + Set the alignment of the scale label. Choose from ``"left"`` or ``"right"`` + [Default is ``"left"``]. scale : str or float Give anomaly scale in data-units/distance-unit. Append **c**, **i**, or **p** to indicate the distance unit (centimeters, inches, or points); if no unit is given we use the default unit that is controlled by :gmt-term:`PROJ_LENGTH_UNIT`. - $frame - position : str - [**g**\|\ **j**\|\ **J**\|\ **n**\|\ **x**]\ *refpoint*\ - **+w**\ *length*\ [**+j**\ *justify*]\ [**+al**\|\ **r**]\ - [**+o**\ *dx*\ [/*dy*]][**+l**\ [*label*]]. - Define the reference point on the map for the vertical scale bar. positive_fill : str Set color or pattern for filling positive wiggles [Default is no fill]. negative_fill : str @@ -127,9 +146,12 @@ def wiggle( # noqa: PLR0913 track : str Draw track [Default is no track]. Append pen attributes to use [Default is ``"0.25p,black,solid"``]. - $verbose pen : str Specify outline pen attributes [Default is no outline]. + $projection + $region + $frame + $verbose $binary $panel $nodata @@ -144,9 +166,26 @@ def wiggle( # noqa: PLR0913 """ self._activate_figure() + position = _parse_position( + position, + kwdict={"length": length, "label": label, "label_alignment": label_alignment}, + default=Position("BL", offset=0.2), # Default to BL with 0.2-cm offset. + ) + _fills = _parse_fills(positive_fill, negative_fill) aliasdict = AliasSystem( + D=[ + Alias(position, name="position"), + Alias(length, name="length", prefix="+w"), + Alias( + label_alignment, + name="label_alignment", + prefix="+a", + mapping={"left": "l", "right": "r"}, + ), + Alias(label, name="label", prefix="+l"), + ], G=Alias(_fills, name="positive_fill/negative_fill"), ).add_common( B=frame, diff --git a/pygmt/tests/baseline/test_wiggle_default_position.png.dvc b/pygmt/tests/baseline/test_wiggle_default_position.png.dvc new file mode 100644 index 00000000000..af62e458c57 --- /dev/null +++ b/pygmt/tests/baseline/test_wiggle_default_position.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: 362640b4554c7e5097340c578a672988 + size: 16058 + hash: md5 + path: test_wiggle_default_position.png diff --git a/pygmt/tests/test_wiggle.py b/pygmt/tests/test_wiggle.py index 76d2304496b..8e4ba6718d4 100644 --- a/pygmt/tests/test_wiggle.py +++ b/pygmt/tests/test_wiggle.py @@ -5,17 +5,73 @@ import numpy as np import pytest from pygmt import Figure +from pygmt.exceptions import GMTInvalidInput +from pygmt.params import Position -@pytest.mark.mpl_image_compare -def test_wiggle(): +@pytest.fixture(scope="module", name="data") +def fixture_xyz(): """ - Plot the z=f(x,y) anomalies along tracks. + Create sample (x, y, z) data for testing. """ x = np.arange(-2, 2, 0.02) y = np.zeros(x.size) z = np.cos(2 * np.pi * x) + return (x, y, z) + + +@pytest.mark.mpl_image_compare +def test_wiggle(data): + """ + Plot the z=f(x,y) anomalies along tracks. + """ + x, y, z = data + fig = Figure() + fig.wiggle( + region=[-4, 4, -1, 1], + projection="X8c", + x=x, + y=y, + z=z, + scale="0.5c", + positive_fill="red", + negative_fill="gray", + pen="1.0p", + track="0.5p", + position=Position("MR"), + length=2, + label="nT", + ) + return fig + + +@pytest.mark.mpl_image_compare +def test_wiggle_default_position(data): + """ + Test that wiggle works when position is not provided. + """ + x, y, z = data + fig = Figure() + fig.wiggle( + region=[-4, 4, -1, 1], + projection="X8c", + frame=True, + x=x, + y=y, + z=z, + pen="1p", + scale="0.5c", + length=1, + ) + return fig + +@pytest.mark.mpl_image_compare(filename="test_wiggle.png") +def test_wiggle_deprecated_position_syntax(data): + """ + Test the deprecated position syntax for wiggle. + """ + x, y, z = data fig = Figure() fig.wiggle( region=[-4, 4, -1, 1], @@ -28,23 +84,20 @@ def test_wiggle(): negative_fill="gray", pen="1.0p", track="0.5p", - position="jRM+w2+lnT", + position="jMR+w2+lnT", ) return fig @pytest.mark.benchmark @pytest.mark.mpl_image_compare(filename="test_wiggle.png") -def test_wiggle_data_incols(): +def test_wiggle_data_incols(data): """ Make sure that incols parameter works with input data array. """ - # put data into numpy array and swap x and y columns # as the use of the 'incols' parameter will reverse this action - x = np.arange(-2, 2, 0.02) - y = np.zeros(x.size) - z = np.cos(2 * np.pi * x) + x, y, z = data data = np.array([y, x, z]).T fig = Figure() @@ -58,6 +111,36 @@ def test_wiggle_data_incols(): negative_fill="gray", pen="1.0p", track="0.5p", - position="jRM+w2+lnT", + position=Position("MR"), + length=2, + label="nT", ) return fig + + +def test_wiggle_mixed_syntax(data): + """ + Test that an error is raised when mixing new and deprecated syntax in 'position'. + """ + x, y, z = data + fig = Figure() + kwargs = { + "region": [-4, 4, -1, 1], + "projection": "X8c", + "x": x, + "y": y, + "z": z, + "scale": "0.5c", + "positive_fill": "red", + "negative_fill": "gray", + "pen": "1.0p", + "track": "0.5p", + } + with pytest.raises(GMTInvalidInput): + fig.wiggle(position="jMR+w2+lnT", length=2, **kwargs) + + with pytest.raises(GMTInvalidInput): + fig.wiggle(position="jMR+w2+lnT", label="nT", **kwargs) + + with pytest.raises(GMTInvalidInput): + fig.wiggle(position="jMR+w2+lnT", length_alignment="left", **kwargs)