From eec0fb74bd7e16dad6ec769ef64e50f532a74f97 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sun, 16 Nov 2025 11:56:45 +0800 Subject: [PATCH 1/8] Initial implemention of the Position class --- pygmt/params/__init__.py | 1 + pygmt/params/position.py | 42 ++++++++++++++++++++++++++++++++++++++++ pygmt/src/logo.py | 20 +++++++++++++++---- 3 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 pygmt/params/position.py diff --git a/pygmt/params/__init__.py b/pygmt/params/__init__.py index b80b921407a..d1a00a7f5f2 100644 --- a/pygmt/params/__init__.py +++ b/pygmt/params/__init__.py @@ -4,3 +4,4 @@ from pygmt.params.box import Box from pygmt.params.pattern import Pattern +from pygmt.params.position import Position diff --git a/pygmt/params/position.py b/pygmt/params/position.py new file mode 100644 index 00000000000..060c2bed2a4 --- /dev/null +++ b/pygmt/params/position.py @@ -0,0 +1,42 @@ +""" +The Position class for positioning GMT embellishments. +""" + +import dataclasses +from collections.abc import Sequence +from typing import Literal + +from pygmt._typing import AnchorCode +from pygmt.alias import Alias +from pygmt.params.base import BaseParam + + +@dataclasses.dataclass(repr=False) +class Position(BaseParam): + """ + The class for positioning GMT embellishments. + """ + + location: str | tuple[float | str, float | str] + type: Literal["mapcoords", "inside", "outside", "boxcoords", "plotcoords"] + anchor: AnchorCode + offset: Sequence[float | str] + + @property + def _aliases(self): + return [ + Alias( + self.type, + name="type", + mapping={ + "mapcoords": "g", + "boxcoords": "n", + "plotcoords": "x", + "inside": "j", + "outside": "J", + }, + ), + Alias(self.location, name="location", sep="/", size=2), + Alias(self.anchor, name="anchor"), + Alias(self.offset, name="offset", sep="/", size=2), + ] diff --git a/pygmt/src/logo.py b/pygmt/src/logo.py index defdc065eb3..668dc0a7ca2 100644 --- a/pygmt/src/logo.py +++ b/pygmt/src/logo.py @@ -7,14 +7,16 @@ from pygmt.alias import Alias, AliasSystem from pygmt.clib import Session -from pygmt.helpers import build_arg_list, fmt_docstring, use_alias -from pygmt.params import Box +from pygmt.helpers import build_arg_list, fmt_docstring +from pygmt.params import Box, Position @fmt_docstring -@use_alias(D="position") def logo( self, + position: Position, + width: float | str | None = None, + height: float | str | None = None, projection: str | None = None, region: Sequence[float | str] | str | None = None, style: Literal["standard", "url", "no_label"] = "standard", @@ -36,7 +38,12 @@ def logo( Full GMT docs at :gmt-docs:`gmtlogo.html`. - {aliases} + **Aliases:** + + .. hlist:: + :columns: 3 + + - D = position, **+w**: width, **+h**: height - F = box - J = projection - R = region @@ -73,6 +80,11 @@ def logo( self._activate_figure() aliasdict = AliasSystem( + D=[ + Alias(position, name="position"), + Alias(width, name="width", prefix="+w"), + Alias(height, name="height", prefix="+h"), + ], F=Alias(box, name="box"), S=Alias( style, name="style", mapping={"standard": "l", "url": "u", "no_label": "n"} From 539f66f25f97c4a4d3e3418a9bb9173d1047484b Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 20 Nov 2025 16:14:34 +0800 Subject: [PATCH 2/8] Fix styling --- pygmt/src/logo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/logo.py b/pygmt/src/logo.py index 668dc0a7ca2..ab66ff3366f 100644 --- a/pygmt/src/logo.py +++ b/pygmt/src/logo.py @@ -12,7 +12,7 @@ @fmt_docstring -def logo( +def logo( # noqa: PLR0913 self, position: Position, width: float | str | None = None, From 97f015f040a0aab7bee4f58beb104557b98d7365 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sun, 23 Nov 2025 16:45:43 +0800 Subject: [PATCH 3/8] Add tests and improve docstrings --- pygmt/params/position.py | 40 +++++++++++++++++++++++++---- pygmt/tests/test_params_position.py | 30 ++++++++++++++++++++++ 2 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 pygmt/tests/test_params_position.py diff --git a/pygmt/params/position.py b/pygmt/params/position.py index 060c2bed2a4..4353841424f 100644 --- a/pygmt/params/position.py +++ b/pygmt/params/position.py @@ -17,10 +17,40 @@ class Position(BaseParam): The class for positioning GMT embellishments. """ - location: str | tuple[float | str, float | str] + #: Specify the reference point on the plot. The method of defining the reference + #: point is controlled by ``type``, and the exact location is set by ``position``. + location: Sequence[float | str] | AnchorCode + + #: Specify the type of coordinates used to define the reference point. It can be + #: one of the following values: + #: + #: - ``"mapcoords"``: ``position`` is specified as (*longitude*, *latitude*) in map + #: coordinates. + #: - ``"boxcoords"``: ``position`` is specified as (*nx*, *ny*) in normalized + #: coordinates, i.e., fractional values between 0 and 1 along the x- and y-axes. + #: - ``"plotcoords"``: ``position`` is specified as (*x*, *y*) in plot coordinates, + #: i.e., distances from the lower-left plot origin given in inches, centimeters, + #: or points. + #: - ``"inside"`` or ``"outside"``: ``position`` is one of the nine + #: :doc:`two-character justification codes `, + #: indicating a specific location relative to the plot bounding box. + #: type: Literal["mapcoords", "inside", "outside", "boxcoords", "plotcoords"] - anchor: AnchorCode - offset: Sequence[float | str] + + #: Specify the anchor point of the GMT logo, using one of the + #: :doc:`2-character justification codes `. The + #: default value depends on ``position_type``. + #: + #: - ``position_type="inside"``: ``anchor`` defaults to the same as ``position``. + #: - ``position_type="outside"``: ``anchor`` defaults to the mirror opposite of + #: ``position``. + #: - Otherwise, ``anchor`` defaults to ``"MC"`` (middle center). + anchor: AnchorCode | None = None + + #: Specifies an offset for the anchor point as *offset* or (*offset_x*, *offset_y*). + #: If a single value *offset* is given, both *offset_x* and *offset_y* are set to + #: *offset*. + offset: Sequence[float | str] | None = None @property def _aliases(self): @@ -37,6 +67,6 @@ def _aliases(self): }, ), Alias(self.location, name="location", sep="/", size=2), - Alias(self.anchor, name="anchor"), - Alias(self.offset, name="offset", sep="/", size=2), + Alias(self.anchor, name="anchor", prefix="+j"), + Alias(self.offset, name="offset", prefix="+o", sep="/", size=2), ] diff --git a/pygmt/tests/test_params_position.py b/pygmt/tests/test_params_position.py new file mode 100644 index 00000000000..8999f2822c4 --- /dev/null +++ b/pygmt/tests/test_params_position.py @@ -0,0 +1,30 @@ +""" +Test the Position class. +""" + +from pygmt.params import Position + + +def test_params_position_types(): + """ + Test the Position class with different types of coordinate systems. + """ + assert str(Position(location=(10, 20), type="mapcoords")) == "g10/20" + assert str(Position(location=(0.1, 0.2), type="boxcoords")) == "n0.1/0.2" + assert str(Position(location=("5c", "3c"), type="plotcoords")) == "x5c/3c" + assert str(Position(location="TL", type="inside")) == "jTL" + assert str(Position(location="BR", type="outside")) == "JBR" + + +def test_params_position_anchor_offset(): + """ + Test the Position class with anchor and offset parameters. + """ + pos = Position(location=(10, 20), type="mapcoords", anchor="TL") + assert str(pos) == "g10/20+jTL" + + pos = Position(location=(10, 20), type="mapcoords", offset=(1, 2)) + assert str(pos) == "g10/20+o1/2" + + pos = Position(location="TL", type="inside", anchor="MC", offset=("1c", "2c")) + assert str(pos) == "jTL+jMC+o1c/2c" From 854804ef0f992ee6e291e9a4a5647a53d0fdac2f Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sun, 23 Nov 2025 16:46:28 +0800 Subject: [PATCH 4/8] Add to API doc --- doc/api/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/api/index.rst b/doc/api/index.rst index 3656bba286e..264f5a9175a 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -214,6 +214,7 @@ Class-style Parameters Box Pattern + Position Enums ----- From 6b55dde70d63ab6d865bb3565adddea41c4e7bf4 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sun, 23 Nov 2025 16:48:38 +0800 Subject: [PATCH 5/8] Add an inline doctest --- pygmt/params/position.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pygmt/params/position.py b/pygmt/params/position.py index 4353841424f..5858dd9103a 100644 --- a/pygmt/params/position.py +++ b/pygmt/params/position.py @@ -15,6 +15,20 @@ class Position(BaseParam): """ The class for positioning GMT embellishments. + + Example + ------- + >>> import pygmt + >>> from pygmt.params import Position + >>> fig = pygmt.Figure() + >>> fig.basemap(region=[0, 10, 0, 10], projection="X10c", frame=True) + >>> fig.logo( + ... position=Position( + ... location=(3, 3), type="mapcoords", anchor="ML", offset=(0.2, 0.2) + ... ), + ... box=True, + ... ) + >>> fig.show() """ #: Specify the reference point on the plot. The method of defining the reference From 3d629cb52a0e7bb05968a8e0f7f2411289172a52 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sun, 23 Nov 2025 19:27:50 +0800 Subject: [PATCH 6/8] position is not required --- pygmt/src/logo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/logo.py b/pygmt/src/logo.py index ab66ff3366f..d98d4f3fb2f 100644 --- a/pygmt/src/logo.py +++ b/pygmt/src/logo.py @@ -14,7 +14,7 @@ @fmt_docstring def logo( # noqa: PLR0913 self, - position: Position, + position: Position | None = None, width: float | str | None = None, height: float | str | None = None, projection: str | None = None, From 576b822e51518096e8009cb2b0c20f99d45481a4 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sun, 23 Nov 2025 19:33:58 +0800 Subject: [PATCH 7/8] Default to plotcoords --- pygmt/params/position.py | 5 ++++- pygmt/tests/test_params_position.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pygmt/params/position.py b/pygmt/params/position.py index 5858dd9103a..6669a5dcb0a 100644 --- a/pygmt/params/position.py +++ b/pygmt/params/position.py @@ -49,7 +49,10 @@ class Position(BaseParam): #: :doc:`two-character justification codes `, #: indicating a specific location relative to the plot bounding box. #: - type: Literal["mapcoords", "inside", "outside", "boxcoords", "plotcoords"] + #: The default value is ``"plotcoords"``. + type: Literal["mapcoords", "inside", "outside", "boxcoords", "plotcoords"] = ( + "plotcoords" + ) #: Specify the anchor point of the GMT logo, using one of the #: :doc:`2-character justification codes `. The diff --git a/pygmt/tests/test_params_position.py b/pygmt/tests/test_params_position.py index 8999f2822c4..dbcf76b501e 100644 --- a/pygmt/tests/test_params_position.py +++ b/pygmt/tests/test_params_position.py @@ -9,6 +9,7 @@ def test_params_position_types(): """ Test the Position class with different types of coordinate systems. """ + assert str(Position(location=(10, 20))) == "x10/20" assert str(Position(location=(10, 20), type="mapcoords")) == "g10/20" assert str(Position(location=(0.1, 0.2), type="boxcoords")) == "n0.1/0.2" assert str(Position(location=("5c", "3c"), type="plotcoords")) == "x5c/3c" From f54bec989be2f53a938388947984c842c917813a Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sun, 23 Nov 2025 19:35:53 +0800 Subject: [PATCH 8/8] Updates --- pygmt/params/position.py | 4 +--- pygmt/tests/test_params_position.py | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pygmt/params/position.py b/pygmt/params/position.py index 6669a5dcb0a..2f12d5fdb60 100644 --- a/pygmt/params/position.py +++ b/pygmt/params/position.py @@ -23,9 +23,7 @@ class Position(BaseParam): >>> fig = pygmt.Figure() >>> fig.basemap(region=[0, 10, 0, 10], projection="X10c", frame=True) >>> fig.logo( - ... position=Position( - ... location=(3, 3), type="mapcoords", anchor="ML", offset=(0.2, 0.2) - ... ), + ... position=Position((3, 3), type="mapcoords", anchor="ML", offset=(0.2, 0.2)), ... box=True, ... ) >>> fig.show() diff --git a/pygmt/tests/test_params_position.py b/pygmt/tests/test_params_position.py index dbcf76b501e..2ca05cf2150 100644 --- a/pygmt/tests/test_params_position.py +++ b/pygmt/tests/test_params_position.py @@ -9,7 +9,8 @@ def test_params_position_types(): """ Test the Position class with different types of coordinate systems. """ - assert str(Position(location=(10, 20))) == "x10/20" + assert str(Position((1, 2))) == "x1/2" + assert str(Position(location=(1, 2))) == "x1/2" assert str(Position(location=(10, 20), type="mapcoords")) == "g10/20" assert str(Position(location=(0.1, 0.2), type="boxcoords")) == "n0.1/0.2" assert str(Position(location=("5c", "3c"), type="plotcoords")) == "x5c/3c"