Source code for pygmt.params.position

"""
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.exceptions import GMTValueError
from pygmt.helpers import is_nonstr_iter
from pygmt.params.base import BaseParam


[docs] @dataclasses.dataclass(repr=False) class Position(BaseParam): """ Class for positioning embellishments on a plot. .. figure:: https://docs.generic-mapping-tools.org/dev/_images/GMT_anchor.png :width: 600 px :align: center The placement of a GMT embellishment (represented by a green rectangle) in relation to the underlying plot (represented by a bisque rectangle). This class provides flexible positioning for GMT embellishments (e.g., logo, scale, rose) by defining a *reference point* on the plot and an *anchor point* on the embellishment. The embellishment is positioned so these two points overlap. **Conceptual Model** Think of it like dropping an anchor from a boat: 1. The boat navigates to the *reference point* (a location on the plot) 2. The *anchor point* (a specific point on the embellishment) is aligned with the *reference point* 3. The embellishment is "dropped" at that position **Reference Point** The *reference point* can be specified in five different ways using the ``cstype`` and ``refpoint`` attributes: ``cstype="mapcoords"`` Map Coordinates Use data/geographic coordinates. Specify ``refpoint`` as (*longitude*, *latitude*). Useful when tying the embellishment to a specific geographic location. **Example:** ``refpoint=(135, 20), cstype="mapcoords"`` ``cstype="plotcoords"`` Plot Coordinates Use plot coordinates as distances from the lower-left plot origin. Specify ``refpoint`` as (*x*, *y*) with units (e.g., inches, centimeters, points). Useful for precise layout control. **Example:** ``refpoint=("2c", "2.5c"), cstype="plotcoords"`` ``cstype="boxcoords"`` Normalized Coordinates Use normalized coordinates where (0, 0) is the lower-left corner and (1, 1) is the upper-right corner of the bounding box of the current plot. Specify ``refpoint`` as (*nx*, *ny*). Useful for positioning relative to plot dimensions without units. **Example:** ``refpoint=(0.2, 0.1), cstype="boxcoords"`` ``cstype="inside"`` Inside Plot Select one of the nine :doc:`justification codes </techref/justification_codes>` as the *reference point*. The *anchor point* defaults to be the same as the *reference point*, so the embellishment is placed inside the plot. **Example:** ``refpoint="TL", cstype="inside"`` ``cstype="outside"`` Outside Plot Similar to ``cstype="inside"``, but the *anchor point* defaults to the mirror opposite of the *reference point*. Useful for placing embellishments outside the plot boundaries (e.g., color bars). **Example:** ``refpoint="TL", cstype="outside"`` **Anchor Point** The *anchor point* determines which part of the embellishment aligns with the *reference point*. It uses one of nine :doc:`justification codes </techref/justification_codes>`. Set ``anchor`` explicitly to override these defaults. If not set, the default *anchor* behaviors are: - ``cstype="inside"``: Same as the *reference point* justification code - ``cstype="outside"``: Mirror opposite of the *reference point* justification code - Other cstypes: ``"MC"`` (middle center) for map rose and scale, ``"BL"`` (bottom-left) for other embellishments **Offset** The ``offset`` parameter shifts the *anchor point* from its default position. Offsets are applied to the projected plot coordinates, with positive values moving in the direction indicated by the *anchor point*'s justification code. It should be a single value (applied to both x and y) or as (*offset_x*, *offset_y*). Examples -------- Position the GMT logo at map coordinates (3, 3) with the logo's middle-left point as the anchor, offset by (0.2, 0.2): >>> 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( ... (3, 3), cstype="mapcoords", anchor="ML", offset=(0.2, 0.2) ... ), ... box=True, ... ) >>> fig.show() Position the GMT logo at the top-left corner inside the plot: >>> fig = pygmt.Figure() >>> fig.basemap(region=[0, 10, 0, 10], projection="X10c", frame=True) >>> fig.logo(position=Position("TL", cstype="inside", offset="0.2c"), box=True) >>> fig.show() """ #: Location of the reference point on the plot. The format depends on ``cstype``: #: #: - ``cstype="mapcoords"``: (*longitude*, *latitude*) #: - ``cstype="plotcoords"``: (*x*, *y*) with plot units #: - ``cstype="boxcoords"``: (*nx*, *ny*) #: - ``cstype="inside"`` or ``"outside"``: #: :doc:`2-character justification codes </techref/justification_codes>` refpoint: Sequence[float | str] | AnchorCode #: cstype of the reference point. Valid values are: #: #: - ``"mapcoords"``: Map/Data coordinates #: - ``"plotcoords"``: Plot coordinates #: - ``"boxcoords"``: Normalized coordinates #: - ``"inside"`` or ``"outside"``: Justification codes #: #: If not specified, defaults to ``"inside"`` if ``refpoint`` is a justification #: code; otherwise defaults to ``"plotcoords"``. cstype: ( Literal["mapcoords", "inside", "outside", "boxcoords", "plotcoords"] | None ) = None #: Anchor point on the embellishment using a #: :doc:`2-character justification code </techref/justification_codes>`. #: If ``None``, defaults are applied based on ``cstype`` (see above). anchor: AnchorCode | None = None #: Offset for the anchor point as a single value or (*offset_x*, *offset_y*). #: If a single value is given, the offset is applied to both x and y directions. offset: float | str | Sequence[float | str] | None = None def _validate(self): """ Validate the parameters. """ _valid_anchors = {f"{h}{v}" for v in "TMB" for h in "LCR"} | { f"{v}{h}" for v in "TMB" for h in "LCR" } # Default to "inside" if cstype is not specified and location is an anchor code. if self.cstype is None: self.cstype = "inside" if isinstance(self.refpoint, str) else "plotcoords" # Validate the location based on cstype. match self.cstype: case "mapcoords" | "plotcoords" | "boxcoords": if not is_nonstr_iter(self.refpoint) or len(self.refpoint) != 2: raise GMTValueError( self.refpoint, description="reference point", reason="Expect a sequence of two values.", ) case "inside" | "outside": if self.refpoint not in _valid_anchors: raise GMTValueError( self.refpoint, description="reference point", reason="Expect a valid 2-character justification code.", ) # Validate the anchor if specified. if self.anchor is not None and self.anchor not in _valid_anchors: raise GMTValueError( self.anchor, description="anchor point", reason="Expect a valid 2-character justification code.", ) @property def _aliases(self): return [ Alias( self.cstype, name="cstype", mapping={ "mapcoords": "g", "boxcoords": "n", "plotcoords": "x", "inside": "j", "outside": "J", }, ), Alias(self.refpoint, name="refpoint", sep="/", size=2), Alias(self.anchor, name="anchor", prefix="+j"), Alias(self.offset, name="offset", prefix="+o", sep="/", size=2), ]