From d8eabe769380c4f2db86d14ed592e3a614a2492b Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 10 Feb 2026 22:30:21 +0800 Subject: [PATCH 01/23] Add the Frame/Axis class for setting frame and axes --- pygmt/params/__init__.py | 1 + pygmt/params/frame.py | 120 +++++++++++++++++++++++++++++++ pygmt/tests/test_params_frame.py | 88 +++++++++++++++++++++++ 3 files changed, 209 insertions(+) create mode 100644 pygmt/params/frame.py create mode 100644 pygmt/tests/test_params_frame.py diff --git a/pygmt/params/__init__.py b/pygmt/params/__init__.py index d1a00a7f5f2..43b83ab0131 100644 --- a/pygmt/params/__init__.py +++ b/pygmt/params/__init__.py @@ -3,5 +3,6 @@ """ from pygmt.params.box import Box +from pygmt.params.frame import Axis, Frame from pygmt.params.pattern import Pattern from pygmt.params.position import Position diff --git a/pygmt/params/frame.py b/pygmt/params/frame.py new file mode 100644 index 00000000000..9281765d12c --- /dev/null +++ b/pygmt/params/frame.py @@ -0,0 +1,120 @@ +""" +The Axes, Axis, and Frame classes for specifying the frame. +""" + +import dataclasses + +from pygmt.alias import Alias +from pygmt.exceptions import GMTParameterError +from pygmt.params.base import BaseParam + + +@dataclasses.dataclass(repr=False) +class Axis(BaseParam): + """ + Class for setting up one axis of a plot. + """ + + # Specify the intervals for annotations, ticks, and gridlines. + annot: float | None = None + tick: float | None = None + grid: float | None = None + # The label for the axis [Default is no label]. + label: str | None = None + + @property + def _aliases(self): + return [ + Alias(self.annot, name="annot", prefix="a"), + Alias(self.tick, name="tick", prefix="f"), + Alias(self.grid, name="grid", prefix="g"), + Alias(self.label, name="label", prefix="+l"), + ] + + +@dataclasses.dataclass(repr=False) +class _Axes(BaseParam): + """ + A private class to build the Axes part of the Frame class. + """ + + # Refer to the Frame class for full documentation. + axes: str | None = None + title: str | None = None + + @property + def _aliases(self): + return [ + Alias(self.axes, name="axes"), + Alias(self.title, name="title", prefix="+t"), + ] + + +@dataclasses.dataclass(repr=False) +class Frame(BaseParam): + """ + Class for setting up the frame of a plot. + """ + + #: Specify which axes to draw and their attributes. + axes: str | None = None + + #: The title string centered above the plot frame [Default is no title]. + title: str | None = None + + #: Specify the attributes for axes. + #: + #: The attributes for each axis can be specified in two ways: + #: #. specifying the same attributes for all axes using the ``axis`` parameter + #: #. specifying the attributes for each axis separately using the ``xaxis``, + #: ``yaxis``, ``zaxis`` parameter for the x-, y, and z-axes, respectively. + #: + #: GMT uses the notion of primary (the default) and secondary axes, while secondary + #: axes are optional and mostly used for time axes annotations. To specify the + #: attributes for the secondary axes, use the ``xaxis2``, ``yaxis2``, and ``zaxis2`` + #: parameters. + axis: Axis | None = None + xaxis: Axis | None = None + yaxis: Axis | None = None + zaxis: Axis | None = None + xaxis2: Axis | None = None + yaxis2: Axis | None = None + zaxis2: Axis | None = None + + def _validate(self): + """ + Validate the parameters of the Frame class. + """ + if self.axis is not None and any( + [self.xaxis, self.yaxis, self.zaxis, self.xaxis2, self.yaxis2, self.zaxis2] + ): + raise GMTParameterError( + conflicts_with=( + "axis", + ["xaxis", "yaxis", "zaxis", "xaxis2", "yaxis2", "zaxis2"], + ), + reason="Either 'axis' or the individual axis parameters can be set, but not both.", + ) + + @property + def _aliases(self): + return [ + Alias(_Axes(axes=self.axes, title=self.title)), + Alias(self.axis, name="axis"), + Alias(self.xaxis, name="xaxis", prefix="px" if self.xaxis2 else "x"), + Alias(self.yaxis, name="yaxis", prefix="py" if self.yaxis2 else "y"), + Alias(self.zaxis, name="zaxis", prefix="pz" if self.zaxis2 else "z"), + Alias(self.xaxis2, name="xaxis2", prefix="sx"), + Alias(self.yaxis2, name="yaxis2", prefix="sy"), + Alias(self.zaxis2, name="zaxis2", prefix="sz"), + ] + + def __iter__(self): + """ + Iterate over the aliases of the class. + + Yields + ------ + The value of each alias in the class. None are excluded. + """ + yield from (alias._value for alias in self._aliases if alias._value is not None) diff --git a/pygmt/tests/test_params_frame.py b/pygmt/tests/test_params_frame.py new file mode 100644 index 00000000000..f2471b5bece --- /dev/null +++ b/pygmt/tests/test_params_frame.py @@ -0,0 +1,88 @@ +""" +Test the Frame and Axis classes. +""" + +from pygmt.params import Axis, Frame + + +def test_params_axis(): + """ + Test the Axis class. + """ + assert str(Axis(annot=True)) == "a" + assert str(Axis(annot=True, tick=True, grid=True)) == "afg" + assert str(Axis(annot=30, tick=15, grid=5)) == "a30f15g5" + assert str(Axis(annot=30, label="LABEL")) == "a30+lLABEL" + + +def test_params_frame_only(): + """ + Test the Frame class. + """ + assert str(Frame("WSen")) == "WSen" + assert str(Frame(axes="WSEN", title="My Title")) == "WSEN+tMy Title" + + +def test_params_frame_axis(): + """ + Test the Frame class with uniform axis setting. + """ + frame = Frame(axes="lrtb", title="My Title", axis=Axis(annot=30, tick=15, grid=10)) + assert list(frame) == ["lrtb+tMy Title", "a30f15g10"] + + frame = Frame( + axes="WSEN", + title="My Title", + axis=Axis(annot=True, tick=True, grid=True, label="LABEL"), + ) + assert list(frame) == ["WSEN+tMy Title", "afg+lLABEL"] + + +def test_params_frame_separate_axes(): + """ + Test the Frame class with separate axis settings. + """ + frame = Frame( + axes="lrtb", + title="My Title", + xaxis=Axis(annot=10, tick=5, grid=2), + yaxis=Axis(annot=20, tick=10, grid=4), + ) + assert list(frame) == ["lrtb+tMy Title", "xa10f5g2", "ya20f10g4"] + + frame = Frame( + axes="WSEN", + title="My Title", + xaxis=Axis(annot=True, tick=True, grid=True, label="X-LABEL"), + yaxis=Axis(annot=True, tick=True, grid=True, label="Y-LABEL"), + ) + assert list(frame) == ["WSEN+tMy Title", "xafg+lX-LABEL", "yafg+lY-LABEL"] + + +def test_params_frame_separate_axis_secondary(): + """ + Test the Frame class with separate axis settings including secondary axes. + """ + frame = Frame( + axes="lrtb", + title="My Title", + xaxis=Axis(annot=10, tick=5, grid=2), + xaxis2=Axis(annot=15, tick=7, grid=3), + yaxis=Axis(annot=20, tick=10, grid=4), + yaxis2=Axis(annot=25, tick=12, grid=5), + ) + assert list(frame) == [ + "lrtb+tMy Title", + "pxa10f5g2", + "pya20f10g4", + "sxa15f7g3", + "sya25f12g5", + ] + + frame = Frame( + axes="WSEN", + title="My Title", + xaxis=Axis(annot=True, tick=True, grid=True, label="X-LABEL"), + yaxis=Axis(annot=True, tick=True, grid=True, label="Y-LABEL"), + ) + assert list(frame) == ["WSEN+tMy Title", "xafg+lX-LABEL", "yafg+lY-LABEL"] From a184f48876cf98ce1c15b774d789ca7aa6906006 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 11 Feb 2026 20:31:10 +0800 Subject: [PATCH 02/23] Add to doc index --- doc/api/index.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/api/index.rst b/doc/api/index.rst index cce3f68f19d..3f874a9a2c2 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -216,7 +216,9 @@ Class-style Parameters :toctree: generated :template: autosummary/params.rst + Axis Box + Frame Pattern Position From 3e1ad10d0f6ad9c93906a5659af087aa6f3caab9 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 11 Feb 2026 21:30:58 +0800 Subject: [PATCH 03/23] Improve docstrings for 'axes' --- pygmt/params/frame.py | 52 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/pygmt/params/frame.py b/pygmt/params/frame.py index 9281765d12c..b99b8c90b28 100644 --- a/pygmt/params/frame.py +++ b/pygmt/params/frame.py @@ -15,13 +15,20 @@ class Axis(BaseParam): Class for setting up one axis of a plot. """ - # Specify the intervals for annotations, ticks, and gridlines. + #: Specify the intervals for annotations, ticks, and gridlines. annot: float | None = None tick: float | None = None grid: float | None = None - # The label for the axis [Default is no label]. + #: Label for the axis [Default is no label]. label: str | None = None + #: A leading text prefix for the axis annotations (e.g., dollar sign for plots + #: related to money) [For Cartesian plots only]. + prefix: str | None = None + + #: Unix to append to the axis annotations [For Cartesian plots only]. + unit: str | None = None + @property def _aliases(self): return [ @@ -29,6 +36,8 @@ def _aliases(self): Alias(self.tick, name="tick", prefix="f"), Alias(self.grid, name="grid", prefix="g"), Alias(self.label, name="label", prefix="+l"), + Alias(self.prefix, name="prefix", prefix="+p"), + Alias(self.unit, name="unit", prefix="+u"), ] @@ -38,7 +47,6 @@ class _Axes(BaseParam): A private class to build the Axes part of the Frame class. """ - # Refer to the Frame class for full documentation. axes: str | None = None title: str | None = None @@ -53,10 +61,44 @@ def _aliases(self): @dataclasses.dataclass(repr=False) class Frame(BaseParam): """ - Class for setting up the frame of a plot. + Class for setting up the frame and axes of a plot. """ - #: Specify which axes to draw and their attributes. + #: Controls which axes are drawn and whether they are annotated, using a combination + #: of the codes below. Axis ommitted from the set will not be drawn. + #: + #: For a 2-D plot, there are four axes: west, east, south, and north (or left, + #: right, bottom, top if you prefer); For a 3-D plot, there is an extra Z-axis. + #: They can be denoted by the following codes: + #: + #: - **W** (west), **E** (east), **S** (south), **N** (north), **Z**: Draw axes with + #: both tick marks and annotations. + #: - **w** (west), **e** (east), **s** (south), **n** (north), **z**: Draw axes with + #: tick marks but without annotations. + #: - **l** (left), **r** (right), **b** (bottom), **t** (top), **u** (up): Draw axes + #: without tick marks or annotations. + #: + #: For examples: + #: + #: - ``"WS"``: Draw the west and south axes with both tick marks and annotations, + #: but do not draw the east and north axes. + #: - ``"WSen"``: Draw the west and south axes with both tick marks and annotations, + #: draw the east and north axes with tick marks but without annotations. + #: - ``"WSrt"``: Draw the west and south axes with both tick marks and annotations, + #: draw the east and north axes without tick marks or annotations. + #: - ``"WSrtZ"``: Draw the west and south axes with both tick marks and annotations, + #: draw the east and north axes without tick marks or annotations, and draw the + #: z-axis with both tick marks and annotations. + #: + #: For a 3-D plot, if the z-axis code is specified, a single vertical axis will be + #: drawn at the most suitable corner by default. Append any combination of the + #: corner IDs from 1 to 4 to draw one or more vertical axes at the corresponding + #: corners. For example, `"WSrtZ1234"`. + #: + #: - **1**: the south-western (lower-left) corner + #: - **2**: the south-eastern (lower-right) corner + #: - **3**: the north-eastern (upper-right) corner + #: - **4**: the north-western (upper-left) corner axes: str | None = None #: The title string centered above the plot frame [Default is no title]. From 8f3a67ce765f88f992cfb7c522a17cba480f5a3e Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 11 Feb 2026 21:40:27 +0800 Subject: [PATCH 04/23] Fix docstrings --- pygmt/params/frame.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/params/frame.py b/pygmt/params/frame.py index b99b8c90b28..69352b341a0 100644 --- a/pygmt/params/frame.py +++ b/pygmt/params/frame.py @@ -83,7 +83,7 @@ class Frame(BaseParam): #: - ``"WS"``: Draw the west and south axes with both tick marks and annotations, #: but do not draw the east and north axes. #: - ``"WSen"``: Draw the west and south axes with both tick marks and annotations, - #: draw the east and north axes with tick marks but without annotations. + #: draw the east and north axes with tick marks but without annotations. #: - ``"WSrt"``: Draw the west and south axes with both tick marks and annotations, #: draw the east and north axes without tick marks or annotations. #: - ``"WSrtZ"``: Draw the west and south axes with both tick marks and annotations, @@ -93,7 +93,7 @@ class Frame(BaseParam): #: For a 3-D plot, if the z-axis code is specified, a single vertical axis will be #: drawn at the most suitable corner by default. Append any combination of the #: corner IDs from 1 to 4 to draw one or more vertical axes at the corresponding - #: corners. For example, `"WSrtZ1234"`. + #: corners (e.g., ``"WSrtZ1234"``): #: #: - **1**: the south-western (lower-left) corner #: - **2**: the south-eastern (lower-right) corner From 5fbe28b87fe197eb42b7b6c9c033df3327038a3f Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 11 Feb 2026 21:47:59 +0800 Subject: [PATCH 05/23] Add docstrings for annot/grid/tick --- pygmt/params/frame.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/pygmt/params/frame.py b/pygmt/params/frame.py index 69352b341a0..77b20699952 100644 --- a/pygmt/params/frame.py +++ b/pygmt/params/frame.py @@ -15,10 +15,20 @@ class Axis(BaseParam): Class for setting up one axis of a plot. """ - #: Specify the intervals for annotations, ticks, and gridlines. - annot: float | None = None - tick: float | None = None - grid: float | None = None + #: Specify the interval for annoations. It can be ``True`` to let GMT decide the + #: interval automatically; or a value to set a specific interval in the format of + #: *stride*[±*phase*][*unit*], where, *stride* is the interval, *phase* is the + #: offset to shift the annotations by that amount, and *unit* is one of the + #: :gmt-docs:`18 supported unit codes ` related to + #: time intervals. + annot: float | bool = False + + #: Specify the interval for ticks. Same format as ``annot``. + tick: float | bool = False + + #: Specify the interval for gridlines. Same format as ``annot``. + grid: float | bool = False + #: Label for the axis [Default is no label]. label: str | None = None @@ -104,12 +114,12 @@ class Frame(BaseParam): #: The title string centered above the plot frame [Default is no title]. title: str | None = None - #: Specify the attributes for axes. + #: Specify the attributes for axes by an :class:`Axis` object. #: - #: The attributes for each axis can be specified in two ways: - #: #. specifying the same attributes for all axes using the ``axis`` parameter - #: #. specifying the attributes for each axis separately using the ``xaxis``, - #: ``yaxis``, ``zaxis`` parameter for the x-, y, and z-axes, respectively. + #: The attributes for each axis can be specified in two ways: (1) specifying the + #: same attributes for all axes using the ``axis`` parameter; (2) specifying the + #: attributes for each axis separately using the ``xaxis``, ``yaxis``, ``zaxis`` + #: parameter for the x-, y, and z-axes, respectively. #: #: GMT uses the notion of primary (the default) and secondary axes, while secondary #: axes are optional and mostly used for time axes annotations. To specify the From d320711ac5838cc04564c5606873fb014e513a59 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 11 Feb 2026 21:52:05 +0800 Subject: [PATCH 06/23] Add one more test --- pygmt/tests/test_params_frame.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pygmt/tests/test_params_frame.py b/pygmt/tests/test_params_frame.py index f2471b5bece..55a5f58f36b 100644 --- a/pygmt/tests/test_params_frame.py +++ b/pygmt/tests/test_params_frame.py @@ -13,6 +13,7 @@ def test_params_axis(): assert str(Axis(annot=True, tick=True, grid=True)) == "afg" assert str(Axis(annot=30, tick=15, grid=5)) == "a30f15g5" assert str(Axis(annot=30, label="LABEL")) == "a30+lLABEL" + assert str(Axis(annot=30, prefix="$", unit="m")) == "a30+p$+um" def test_params_frame_only(): From 0ba431dba7b373d3702d97d78ef06952a6fc134b Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 11 Feb 2026 21:56:57 +0800 Subject: [PATCH 07/23] Add doctest --- pygmt/params/frame.py | 51 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/pygmt/params/frame.py b/pygmt/params/frame.py index 77b20699952..6e05266d869 100644 --- a/pygmt/params/frame.py +++ b/pygmt/params/frame.py @@ -13,6 +13,20 @@ class Axis(BaseParam): """ Class for setting up one axis of a plot. + + Examples + -------- + To specify the same attributes for all axes, with intervals of 4 for annotations, + 2 for ticks, and 1 for gridlines: + + >>> import pygmt + >>> fig = pygmmt.Figure() + >>> fig.basemap( + ... region=[0, 10, 0, 20], + ... projection="X10c/10c", + ... frame=Axis(annot=4, tick=2, grid=1), + ... ) + >>> fig.show() """ #: Specify the interval for annoations. It can be ``True`` to let GMT decide the @@ -72,6 +86,43 @@ def _aliases(self): class Frame(BaseParam): """ Class for setting up the frame and axes of a plot. + + Examples + -------- + To specify the west and south axes with both tick marks and annotations, draw the + east and north axes with tick marks but without annotations: + + >>> import pygmt + >>> fig = pygmt.Figure() + >>> fig.basemap( + ... region=[0, 10, 0, 20], projection="X10c/10c", frame=Frame(axes="WSen") + ... ) + >>> fig.show() + + To specify the same attributes for all axes, with intervals of 4 for annotations, + 2 for ticks, and 1 for gridlines: + + >>> fig = pygmt.Figure() + >>> fig.basemap( + ... region=[0, 10, 0, 20], + ... projection="X10c/10c", + ... frame=Frame(axes="WSrt", axis=Axis(annot=4, tick=2, grid=1)), + ... ) + ... fig.show() + + To specify the attributes for each axis separately: + + >>> fig = pygmt.Figure() + >>> fig.basemap( + ... region=[0, 10, 0, 20], + ... projection="X10c/10c", + ... frame=Frame( + ... axes="WSrt", + ... xaxis=Axis(annot=4, tick=2, grid=1, label="X-axis"), + ... yaxis=Axis(annot=5, tick=2.5, grid=1, label="Y-axis"), + ... ), + ... ) + ... fig.show() """ #: Controls which axes are drawn and whether they are annotated, using a combination From b61639a448a58ed2df6af56001e2cd10c0619422 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 11 Feb 2026 21:59:33 +0800 Subject: [PATCH 08/23] Add doctests --- pygmt/params/frame.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pygmt/params/frame.py b/pygmt/params/frame.py index 6e05266d869..e0ff162ec47 100644 --- a/pygmt/params/frame.py +++ b/pygmt/params/frame.py @@ -8,6 +8,8 @@ from pygmt.exceptions import GMTParameterError from pygmt.params.base import BaseParam +__doctest_skip__ = ["Axis", "Frame"] + @dataclasses.dataclass(repr=False) class Axis(BaseParam): @@ -20,7 +22,7 @@ class Axis(BaseParam): 2 for ticks, and 1 for gridlines: >>> import pygmt - >>> fig = pygmmt.Figure() + >>> fig = pygmt.Figure() >>> fig.basemap( ... region=[0, 10, 0, 20], ... projection="X10c/10c", @@ -108,7 +110,7 @@ class Frame(BaseParam): ... projection="X10c/10c", ... frame=Frame(axes="WSrt", axis=Axis(annot=4, tick=2, grid=1)), ... ) - ... fig.show() + >>> fig.show() To specify the attributes for each axis separately: @@ -118,11 +120,11 @@ class Frame(BaseParam): ... projection="X10c/10c", ... frame=Frame( ... axes="WSrt", - ... xaxis=Axis(annot=4, tick=2, grid=1, label="X-axis"), - ... yaxis=Axis(annot=5, tick=2.5, grid=1, label="Y-axis"), + ... xaxis=Axis(annot=4, tick=2, grid=1, label="X Label"), + ... yaxis=Axis(annot=5, tick=2.5, grid=1, label="Y Label"), ... ), ... ) - ... fig.show() + >>> fig.show() """ #: Controls which axes are drawn and whether they are annotated, using a combination From 3a33364089416a4b84c1ef1f45729bc47b8a991f Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 11 Feb 2026 22:05:28 +0800 Subject: [PATCH 09/23] Fix docstrings --- pygmt/params/frame.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/params/frame.py b/pygmt/params/frame.py index e0ff162ec47..514c2e518f5 100644 --- a/pygmt/params/frame.py +++ b/pygmt/params/frame.py @@ -33,7 +33,7 @@ class Axis(BaseParam): #: Specify the interval for annoations. It can be ``True`` to let GMT decide the #: interval automatically; or a value to set a specific interval in the format of - #: *stride*[±*phase*][*unit*], where, *stride* is the interval, *phase* is the + #: *stride*\ [±\ *phase*][*unit*], where, *stride* is the interval, *phase* is the #: offset to shift the annotations by that amount, and *unit* is one of the #: :gmt-docs:`18 supported unit codes ` related to #: time intervals. From c1dadd6379132ae6afea80b34328dd715e01cc89 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 12 Feb 2026 16:22:18 +0800 Subject: [PATCH 10/23] Fix an edge case --- pygmt/params/frame.py | 5 ++++- pygmt/tests/test_params_frame.py | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/pygmt/params/frame.py b/pygmt/params/frame.py index 514c2e518f5..63344f601b2 100644 --- a/pygmt/params/frame.py +++ b/pygmt/params/frame.py @@ -203,8 +203,11 @@ def _validate(self): @property def _aliases(self): + # _Axes() maps to an empty string, which becomes '-B' without arguments and is + # invalid when combined with individual axis settings (e.g., '-B -Bxaf -Byaf'). + frame_settings = _Axes(axes=self.axes, title=self.title) return [ - Alias(_Axes(axes=self.axes, title=self.title)), + Alias(frame_settings) if str(frame_settings) else Alias(None), Alias(self.axis, name="axis"), Alias(self.xaxis, name="xaxis", prefix="px" if self.xaxis2 else "x"), Alias(self.yaxis, name="yaxis", prefix="py" if self.yaxis2 else "y"), diff --git a/pygmt/tests/test_params_frame.py b/pygmt/tests/test_params_frame.py index 55a5f58f36b..6b80e7854f1 100644 --- a/pygmt/tests/test_params_frame.py +++ b/pygmt/tests/test_params_frame.py @@ -43,6 +43,12 @@ def test_params_frame_separate_axes(): """ Test the Frame class with separate axis settings. """ + frame = Frame( + xaxis=Axis(annot=10, tick=5, grid=2.5), + yaxis=Axis(annot=20, tick=10, grid=5), + ) + assert list(frame) == ["xa10f5g2.5", "ya20f10g5"] + frame = Frame( axes="lrtb", title="My Title", From 2dde989a4903f1cda7b553b0a26451cd10f0f266 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 12 Feb 2026 17:20:06 +0800 Subject: [PATCH 11/23] Allow setting annotation angles --- pygmt/params/frame.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pygmt/params/frame.py b/pygmt/params/frame.py index 63344f601b2..8abade14dbe 100644 --- a/pygmt/params/frame.py +++ b/pygmt/params/frame.py @@ -55,6 +55,9 @@ class Axis(BaseParam): #: Unix to append to the axis annotations [For Cartesian plots only]. unit: str | None = None + #: Angle of the axis annotations. + angle: float | None = None + @property def _aliases(self): return [ @@ -62,6 +65,7 @@ def _aliases(self): Alias(self.tick, name="tick", prefix="f"), Alias(self.grid, name="grid", prefix="g"), Alias(self.label, name="label", prefix="+l"), + Alias(self.angle, name="angle", prefix="+a"), Alias(self.prefix, name="prefix", prefix="+p"), Alias(self.unit, name="unit", prefix="+u"), ] From e990cbdba7b2e14e9a9f1b9fe28da5b9ebff936a Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 10 Feb 2026 23:53:19 +0800 Subject: [PATCH 12/23] Figure.colorbar: Pythonic way to set labels and annotations --- pygmt/src/colorbar.py | 101 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 89 insertions(+), 12 deletions(-) diff --git a/pygmt/src/colorbar.py b/pygmt/src/colorbar.py index a52c8d9336f..588a45f43c4 100644 --- a/pygmt/src/colorbar.py +++ b/pygmt/src/colorbar.py @@ -11,12 +11,58 @@ from pygmt.exceptions import GMTValueError from pygmt.helpers import build_arg_list, fmt_docstring, use_alias from pygmt.helpers.utils import is_nonstr_iter -from pygmt.params import Box, Position +from pygmt.params import Axis, Box, Frame, Position from pygmt.src._common import _parse_position __doctest_skip__ = ["colorbar"] +def _build_frame( + annot: float | None = None, + tick: float | None = None, + grid: float | None = None, + annot_angel: float | None = None, + annot_prefix: str | None = None, + annot_unit: str | None = None, + label: str | None = None, + unit: str | None = None, + frame=None, +): + """ + Create the list of Alias objects for the -B option. + + >>> _build_frame(annot=1, tick=0.5, xlabel="Distance", ylabel="Depth") + Frame(xaxis=Axis(annot=1, tick=0.5, label='Distance'), yaxis=Axis(label='Depth')) + """ + if frame is not None and frame is not False: + return frame + + _xaxis_is_set = any( + v is not None + for v in {annot, tick, grid, annot_angel, annot_prefix, annot_unit, label} + ) + _yaxis_is_set = unit is not None + + # Need to return None if no parameters are give. Otherwise, it may return "". + if not (_xaxis_is_set or _yaxis_is_set): + return None + + xaxis, yaxis = None, None + if _xaxis_is_set: + xaxis = Axis( + annot=annot, + tick=tick, + grid=grid, + # angle=annot_angel, + # prefix=annot_prefix, + # unit=annot_unit, + label=label, + ) + if _yaxis_is_set: + yaxis = Axis(label=unit) + return Frame(xaxis=xaxis, yaxis=yaxis) + + def _alias_option_D( # noqa: N802, PLR0913 position=None, length=None, @@ -153,6 +199,15 @@ def colorbar( # noqa: PLR0913 length: float | str | None = None, width: float | str | None = None, orientation: Literal["horizontal", "vertical"] | None = None, + label: str | None = None, + unit: str | None = None, + annot: float | None = None, + tick: float | None = None, + grid: float | None = None, + annot_angel: float | None = None, + annot_prefix: str | None = None, + annot_unit: str | None = None, + frame: str | Sequence[str] | bool = False, reverse: bool = False, nan: bool = False, nan_position: Literal["start", "end"] | None = None, @@ -168,7 +223,6 @@ def colorbar( # noqa: PLR0913 scale: float | None = None, projection: str | None = None, region: Sequence[float | str] | str | None = None, - frame: str | Sequence[str] | bool = False, verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"] | bool = False, panel: int | Sequence[int] | bool = False, @@ -201,7 +255,6 @@ def colorbar( # noqa: PLR0913 Full GMT docs at :gmt-docs:`colorbar.html`. $aliases - - B = frame - F = box - G = truncate - I = shading @@ -217,6 +270,7 @@ def colorbar( # noqa: PLR0913 .. hlist:: :columns: 1 + - B = label, unit, annot, tick, grid, annot_angel, annot_prefix, annot_unit - D = position, **+w**: length/width, **+h**/**+v**: orientation, **+r**: reverse, **+n**: nan/nan_position, **+e**: fg_triangle/bg_triangle/triangle_height, @@ -244,6 +298,26 @@ def colorbar( # noqa: PLR0913 given with unit ``%`` then it is in percentage of the bar length. [Length defaults to 80% of the corresponding plot side dimension, and width defaults to 4% of the bar length]. + label + unit + Set the label and unit for the colorbar. The label is placed along the colorbar + and the unit is placed at the end of the colorbar. + annot + grid + tick + Intervals for annotations, grid lines, and ticks. Refer to + :class:`pygmt.params.Axis` for more details on how these parameters work. + Parameters ``annot_prefix``, ``annot_unit``, and ``annot_angel`` can be used to + further customize the annotations. + frame + Set colorbar boundary frame, labels, and axes attributes. + + .. deprecated:: v0.19.0 + + Use ``annot``, ``tick``, ``grid``, ``annot_angel``, ``annot_prefix``, + ``annot_unit``, ``label``, and ``unit`` parameters to customize the colorbar + annotations and labels. + orientation Set the colorbar orientation to either ``"horizontal"`` or ``"vertical"``. [Default is vertical, unless ``position`` is set to bottom-center or top-center @@ -321,8 +395,6 @@ def colorbar( # noqa: PLR0913 requested colorbar length. $projection $region - frame - Set colorbar boundary frame, labels, and axes attributes. $verbose $panel $perspective @@ -336,12 +408,7 @@ def colorbar( # noqa: PLR0913 >>> # Create a basemap >>> fig.basemap(region=[0, 10, 0, 3], projection="X10c/3c", frame=True) >>> # Call the colorbar method for the plot - >>> fig.colorbar( - ... # Set cmap to the "roma" CPT - ... cmap="SCM/roma", - ... # Label the x-axis "Velocity" and the y-axis "m/s" - ... frame=["x+lVelocity", "y+lm/s"], - ... ) + >>> fig.colorbar(cmap="SCM/roma", label="Velocity", unit="m/s") >>> # Show the plot >>> fig.show() """ @@ -386,7 +453,17 @@ def colorbar( # noqa: PLR0913 Q=Alias(log, name="log"), W=Alias(scale, name="scale"), ).add_common( - B=frame, + B=_build_frame( + annot=annot, + tick=tick, + grid=grid, + annot_angel=annot_angel, + annot_prefix=annot_prefix, + annot_unit=annot_unit, + label=label, + unit=unit, + frame=frame, + ), J=projection, R=region, V=verbose, From 3c75b9398554361de677442f6e537746b6dac6ba Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 11 Feb 2026 22:31:49 +0800 Subject: [PATCH 13/23] Fix doctest --- pygmt/src/colorbar.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/src/colorbar.py b/pygmt/src/colorbar.py index 588a45f43c4..ecb02e2cf3c 100644 --- a/pygmt/src/colorbar.py +++ b/pygmt/src/colorbar.py @@ -31,8 +31,8 @@ def _build_frame( """ Create the list of Alias objects for the -B option. - >>> _build_frame(annot=1, tick=0.5, xlabel="Distance", ylabel="Depth") - Frame(xaxis=Axis(annot=1, tick=0.5, label='Distance'), yaxis=Axis(label='Depth')) + >>> list(_build_frame(annot=1, tick=0.5, label="Distance", unit="Depth")) + ['xa1f0.5+lDistance', 'y+lDepth'] """ if frame is not None and frame is not False: return frame From c9461983782c922ca6866a9f97376f01c3ba5e54 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 12 Feb 2026 16:54:17 +0800 Subject: [PATCH 14/23] Fix typing issue --- pygmt/src/colorbar.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pygmt/src/colorbar.py b/pygmt/src/colorbar.py index ecb02e2cf3c..037bdefdca4 100644 --- a/pygmt/src/colorbar.py +++ b/pygmt/src/colorbar.py @@ -18,9 +18,9 @@ def _build_frame( - annot: float | None = None, - tick: float | None = None, - grid: float | None = None, + annot: float | bool = False, + tick: float | bool = False, + grid: float | bool = False, annot_angel: float | None = None, annot_prefix: str | None = None, annot_unit: str | None = None, @@ -201,9 +201,9 @@ def colorbar( # noqa: PLR0913 orientation: Literal["horizontal", "vertical"] | None = None, label: str | None = None, unit: str | None = None, - annot: float | None = None, - tick: float | None = None, - grid: float | None = None, + annot: float | bool = False, + tick: float | bool = False, + grid: float | bool = False, annot_angel: float | None = None, annot_prefix: str | None = None, annot_unit: str | None = None, From de43548c37b3633d637bcd1294bd30ac117fbaa1 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 12 Feb 2026 16:59:14 +0800 Subject: [PATCH 15/23] Support angle/prefix/unit --- pygmt/src/colorbar.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pygmt/src/colorbar.py b/pygmt/src/colorbar.py index 037bdefdca4..a9dfe08bd6a 100644 --- a/pygmt/src/colorbar.py +++ b/pygmt/src/colorbar.py @@ -34,6 +34,7 @@ def _build_frame( >>> list(_build_frame(annot=1, tick=0.5, label="Distance", unit="Depth")) ['xa1f0.5+lDistance', 'y+lDepth'] """ + # Using the old 'frame' parameter. if frame is not None and frame is not False: return frame @@ -53,9 +54,9 @@ def _build_frame( annot=annot, tick=tick, grid=grid, - # angle=annot_angel, - # prefix=annot_prefix, - # unit=annot_unit, + angle=annot_angel, + prefix=annot_prefix, + unit=annot_unit, label=label, ) if _yaxis_is_set: From 072fde2385b166d677d547e6d59148426c6afda9 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 12 Feb 2026 17:19:03 +0800 Subject: [PATCH 16/23] Fix a typo --- pygmt/src/colorbar.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pygmt/src/colorbar.py b/pygmt/src/colorbar.py index a9dfe08bd6a..2fdeb081482 100644 --- a/pygmt/src/colorbar.py +++ b/pygmt/src/colorbar.py @@ -21,7 +21,7 @@ def _build_frame( annot: float | bool = False, tick: float | bool = False, grid: float | bool = False, - annot_angel: float | None = None, + annot_angle: float | None = None, annot_prefix: str | None = None, annot_unit: str | None = None, label: str | None = None, @@ -40,7 +40,7 @@ def _build_frame( _xaxis_is_set = any( v is not None - for v in {annot, tick, grid, annot_angel, annot_prefix, annot_unit, label} + for v in {annot, tick, grid, annot_angle, annot_prefix, annot_unit, label} ) _yaxis_is_set = unit is not None @@ -54,7 +54,7 @@ def _build_frame( annot=annot, tick=tick, grid=grid, - angle=annot_angel, + angle=annot_angle, prefix=annot_prefix, unit=annot_unit, label=label, @@ -205,7 +205,7 @@ def colorbar( # noqa: PLR0913 annot: float | bool = False, tick: float | bool = False, grid: float | bool = False, - annot_angel: float | None = None, + annot_angle: float | None = None, annot_prefix: str | None = None, annot_unit: str | None = None, frame: str | Sequence[str] | bool = False, @@ -271,7 +271,7 @@ def colorbar( # noqa: PLR0913 .. hlist:: :columns: 1 - - B = label, unit, annot, tick, grid, annot_angel, annot_prefix, annot_unit + - B = label, unit, annot, tick, grid, annot_angle, annot_prefix, annot_unit - D = position, **+w**: length/width, **+h**/**+v**: orientation, **+r**: reverse, **+n**: nan/nan_position, **+e**: fg_triangle/bg_triangle/triangle_height, @@ -308,14 +308,14 @@ def colorbar( # noqa: PLR0913 tick Intervals for annotations, grid lines, and ticks. Refer to :class:`pygmt.params.Axis` for more details on how these parameters work. - Parameters ``annot_prefix``, ``annot_unit``, and ``annot_angel`` can be used to + Parameters ``annot_prefix``, ``annot_unit``, and ``annot_angle`` can be used to further customize the annotations. frame Set colorbar boundary frame, labels, and axes attributes. .. deprecated:: v0.19.0 - Use ``annot``, ``tick``, ``grid``, ``annot_angel``, ``annot_prefix``, + Use ``annot``, ``tick``, ``grid``, ``annot_angle``, ``annot_prefix``, ``annot_unit``, ``label``, and ``unit`` parameters to customize the colorbar annotations and labels. @@ -458,7 +458,7 @@ def colorbar( # noqa: PLR0913 annot=annot, tick=tick, grid=grid, - annot_angel=annot_angel, + annot_angle=annot_angle, annot_prefix=annot_prefix, annot_unit=annot_unit, label=label, From 7aa50bd83e0970e81696367093f793343760310d Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 12 Feb 2026 17:19:16 +0800 Subject: [PATCH 17/23] Add more doctests --- pygmt/src/colorbar.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/pygmt/src/colorbar.py b/pygmt/src/colorbar.py index 2fdeb081482..72915174718 100644 --- a/pygmt/src/colorbar.py +++ b/pygmt/src/colorbar.py @@ -31,8 +31,25 @@ def _build_frame( """ Create the list of Alias objects for the -B option. - >>> list(_build_frame(annot=1, tick=0.5, label="Distance", unit="Depth")) - ['xa1f0.5+lDistance', 'y+lDepth'] + >>> list(_build_frame(annot=1, tick=0.5, label="Distance", unit="km")) + ['xa1f0.5+lDistance', 'y+lkm'] + + >>> list( + ... _build_frame( + ... annot=1, + ... tick=0.5, + ... grid=0.2, + ... annot_angel=30, + ... annot_prefix="m", + ... annot_unit="s", + ... label="Distance", + ... unit="km", + ... ) + ... ) + ['xa1f0.5g0.2a30+m+s+lDistance', 'y+lkm'] + >>> list(_build_frame(frame=["xaf0.5+lDistance", "y+lkm"]))) + ['xaf0.5+lDistance', 'y+lkm'] + """ # Using the old 'frame' parameter. if frame is not None and frame is not False: From 13f13bfaa1a0352181346a5e0f5e4ac1cbe9da44 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 12 Feb 2026 18:33:01 +0800 Subject: [PATCH 18/23] Fix a typo in doctest --- pygmt/src/colorbar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/colorbar.py b/pygmt/src/colorbar.py index 72915174718..de9f63e3c02 100644 --- a/pygmt/src/colorbar.py +++ b/pygmt/src/colorbar.py @@ -39,7 +39,7 @@ def _build_frame( ... annot=1, ... tick=0.5, ... grid=0.2, - ... annot_angel=30, + ... annot_angle=30, ... annot_prefix="m", ... annot_unit="s", ... label="Distance", From b0037260f590c1804b16417c7a0e7f58e57f8de0 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 12 Feb 2026 22:08:59 +0800 Subject: [PATCH 19/23] Fix doctest --- pygmt/src/colorbar.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pygmt/src/colorbar.py b/pygmt/src/colorbar.py index de9f63e3c02..89d24f1094d 100644 --- a/pygmt/src/colorbar.py +++ b/pygmt/src/colorbar.py @@ -40,14 +40,12 @@ def _build_frame( ... tick=0.5, ... grid=0.2, ... annot_angle=30, - ... annot_prefix="m", - ... annot_unit="s", ... label="Distance", ... unit="km", ... ) ... ) - ['xa1f0.5g0.2a30+m+s+lDistance', 'y+lkm'] - >>> list(_build_frame(frame=["xaf0.5+lDistance", "y+lkm"]))) + ['xa1f0.5g0.2+lDistance+a30', 'y+lkm'] + >>> list(_build_frame(frame=["xaf0.5+lDistance", "y+lkm"])) ['xaf0.5+lDistance', 'y+lkm'] """ From 68261edea321c3527167de05db7289f2cec33524 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 25 Feb 2026 13:05:49 +0800 Subject: [PATCH 20/23] Fix a typo --- pygmt/params/frame.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/params/frame.py b/pygmt/params/frame.py index 8abade14dbe..e1181ecb5e9 100644 --- a/pygmt/params/frame.py +++ b/pygmt/params/frame.py @@ -52,7 +52,7 @@ class Axis(BaseParam): #: related to money) [For Cartesian plots only]. prefix: str | None = None - #: Unix to append to the axis annotations [For Cartesian plots only]. + #: Unit to append to the axis annotations [For Cartesian plots only]. unit: str | None = None #: Angle of the axis annotations. From 3f94e419509828319a99ff859a207b5646735970 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 25 Feb 2026 13:09:37 +0800 Subject: [PATCH 21/23] Fix merge conflicts --- pygmt/src/colorbar.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/pygmt/src/colorbar.py b/pygmt/src/colorbar.py index 7fd1ed2bb4b..18439100600 100644 --- a/pygmt/src/colorbar.py +++ b/pygmt/src/colorbar.py @@ -326,7 +326,8 @@ def colorbar( # noqa: PLR0913 Parameters ``annot_prefix``, ``annot_unit``, and ``annot_angle`` can be used to further customize the annotations. frame - Set colorbar boundary frame, labels, and axes attributes. + Set colorbar boundary frame, labels, and axes attributes. If set to ``"none"``, + then no frame will be drawn. .. deprecated:: v0.19.0 @@ -411,12 +412,6 @@ def colorbar( # noqa: PLR0913 requested colorbar length. $projection $region - <<<<<<< HEAD - ======= - frame - Set colorbar boundary frame, labels, and axes attributes. If set to ``"none"``, - then no frame will be drawn. - >>>>>>> class/frame-part1 $verbose $panel $perspective From 5498316f36b4aa2a1159a659f08679fbee384c51 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 25 Feb 2026 14:30:56 +0800 Subject: [PATCH 22/23] Allow frame='none' in colorbar --- pygmt/src/colorbar.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pygmt/src/colorbar.py b/pygmt/src/colorbar.py index 18439100600..b84d81fa890 100644 --- a/pygmt/src/colorbar.py +++ b/pygmt/src/colorbar.py @@ -48,13 +48,16 @@ def _build_frame( >>> list(_build_frame(frame=["xaf0.5+lDistance", "y+lkm"])) ['xaf0.5+lDistance', 'y+lkm'] + >>> _build_frame(frame="none") + 'none' + >>> _build_frame() # Passing no parameters returns None """ # Using the old 'frame' parameter. if frame is not None and frame is not False: return frame _xaxis_is_set = any( - v is not None + v is not None and v is not False for v in {annot, tick, grid, annot_angle, annot_prefix, annot_unit, label} ) _yaxis_is_set = unit is not None From e30070ffbba545a427102be2c6f0680dfa3d4b45 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Mon, 9 Mar 2026 10:40:08 +0800 Subject: [PATCH 23/23] Improve docstrings --- pygmt/src/colorbar.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/pygmt/src/colorbar.py b/pygmt/src/colorbar.py index b84d81fa890..89a3e2d0d40 100644 --- a/pygmt/src/colorbar.py +++ b/pygmt/src/colorbar.py @@ -322,22 +322,19 @@ def colorbar( # noqa: PLR0913 Set the label and unit for the colorbar. The label is placed along the colorbar and the unit is placed at the end of the colorbar. annot - grid tick - Intervals for annotations, grid lines, and ticks. Refer to + grid + Intervals for annotations, ticks, and gridlines. Refer to :class:`pygmt.params.Axis` for more details on how these parameters work. - Parameters ``annot_prefix``, ``annot_unit``, and ``annot_angle`` can be used to - further customize the annotations. + annot_prefix + annot_unit + annot_angle + The prefix, unit and angle for the annotations. The prefix is placed before the + annotation text; the unit is placed after the annotation text; and the angle is + the angle of the annotation text. frame Set colorbar boundary frame, labels, and axes attributes. If set to ``"none"``, then no frame will be drawn. - - .. deprecated:: v0.19.0 - - Use ``annot``, ``tick``, ``grid``, ``annot_angle``, ``annot_prefix``, - ``annot_unit``, ``label``, and ``unit`` parameters to customize the colorbar - annotations and labels. - orientation Set the colorbar orientation to either ``"horizontal"`` or ``"vertical"``. [Default is vertical, unless ``position`` is set to bottom-center or top-center