Skip to content

Add the Box class for specifying the box of GMT embellishments #3995

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

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions doc/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,16 @@ Getting metadata from tabular or grid data:
info
grdinfo

Common Parameters
-----------------

.. currentmodule:: pygmt.params

.. autosummary::
:toctree: generated

Box

Xarray Integration
------------------

Expand Down
3 changes: 2 additions & 1 deletion examples/gallery/embellishments/inset.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

# %%
import pygmt
from pygmt.params import Box

fig = pygmt.Figure()
# Create the primary figure, setting the region to Madagascar, the land color
Expand All @@ -19,7 +20,7 @@
# Create an inset, placing it in the Top Left (TL) corner with a width of 3.5 cm and
# x- and y-offsets of 0.2 cm. The margin is set to 0, and the border is "gold" with a
# pen size of 1.5 points.
with fig.inset(position="jTL+w3.5c+o0.2c", margin=0, box="+p1.5p,gold"):
with fig.inset(position="jTL+w3.5c+o0.2c", margin=0, box=Box(pen="1.5p,gold")):
# Create a figure in the inset using coast. This example uses the azimuthal
# orthogonal projection centered at 47E, 20S. The land color is set to
# "gray" and Madagascar is highlighted in "red3".
Expand Down
3 changes: 2 additions & 1 deletion examples/gallery/embellishments/inset_rectangle_region.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

# %%
import pygmt
from pygmt.params import Box

# Set the region of the main figure
region = [137.5, 141, 34, 37]
Expand All @@ -34,7 +35,7 @@
# a pen of "1p".
with fig.inset(
position="jBR+o0.1c",
box="+gwhite+p1p",
box=Box(fill="white", pen="1p"),
region=[129, 146, 30, 46],
projection="U54S/3c",
):
Expand Down
3 changes: 2 additions & 1 deletion examples/gallery/embellishments/scalebar.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@

# %%
import pygmt
from pygmt.params import Box

# Create a new Figure instance
fig = pygmt.Figure()
Expand Down Expand Up @@ -105,7 +106,7 @@
# Fill the box in white with a transparency of 30 percent, add a solid
# outline in darkgray (gray30) with a thickness of 0.5 points, and use
# rounded edges with a radius of 3 points
box="+gwhite@30+p0.5p,gray30,solid+r3p",
box=Box(fill="white@30", pen="0.5p,gray30,solid", radius="3p"),
)

fig.show()
Expand Down
7 changes: 4 additions & 3 deletions examples/gallery/images/cross_section.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

# %%
import pygmt
from pygmt.params import Box

# Define region of study area
# lon_min, lon_max, lat_min, lat_max in degrees East and North
Expand Down Expand Up @@ -44,9 +45,9 @@
# corner with an offset ("+o") of 0.7 centimeters and 0.3 centimeters in x- or y-
# directions, respectively; move the x-label above the horizontal colorbar ("+ml")
position="jBR+o0.7c/0.8c+h+w5c/0.3c+ml",
# Add a box around the colobar with a fill ("+g") in "white" color and a
# transparency ("@") of 30 % and with a 0.8-points thick, black, outline ("+p")
box="+gwhite@30+p0.8p,black",
# Add a box around the colobar, filled in "white" and a 30% transparency, with a
# 0.8-points thick, black, outline.
box=Box(pen="0.8p,black", fill="white@30"),
# Add x- and y-labels ("+l")
frame=["x+lElevation", "y+lm"],
)
Expand Down
3 changes: 2 additions & 1 deletion examples/gallery/lines/hlines_vlines.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# In Cartesian coordinate systems lines are plotted as straight lines.

import pygmt
from pygmt.params import Box

fig = pygmt.Figure()

Expand All @@ -31,7 +32,7 @@
fig.hlines(
y=[2, 3], xmin=[0, 1], xmax=[7, 7.5], pen="1.5p,dodgerblue3", label="Lines 7 & 8"
)
fig.legend(position="JBR+jBR+o0.2c", box="+gwhite+p1p")
fig.legend(position="JBR+jBR+o0.2c", box=Box(pen="1p", fill="white"))

fig.shift_origin(xshift="w+2c")

Expand Down
7 changes: 4 additions & 3 deletions examples/tutorials/advanced/insets.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

# %%
import pygmt
from pygmt.params import Box

# %%
# Prior to creating an inset figure, a larger figure must first be plotted. In
Expand Down Expand Up @@ -48,7 +49,7 @@
water="lightblue",
frame="a",
)
with fig.inset(position="jBL+w3c", box="+pblack+glightred"):
with fig.inset(position="jBL+w3c", box=Box(pen="black", fill="lightred")):
# pass is used to exit the with statement as no plotting methods are
# called
pass
Expand All @@ -72,7 +73,7 @@
water="lightblue",
frame="a",
)
with fig.inset(position="jBL+w3c+o0.5c/0.2c", box="+pblack+glightred"):
with fig.inset(position="jBL+w3c+o0.5c/0.2c", box=Box(pen="black", fill="lightred")):
pass
fig.show()

Expand All @@ -97,7 +98,7 @@
# parameters.
with fig.inset(
position="jBL+o0.5c/0.2c",
box="+pblack",
box=Box(pen="black"),
region=[-80, -65, 35, 50],
projection="M3c",
):
Expand Down
5 changes: 3 additions & 2 deletions examples/tutorials/advanced/legends.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import io

import pygmt
from pygmt.params import Box

# %%
# Create an auto-legend
Expand Down Expand Up @@ -90,7 +91,7 @@

# Add a box with a 2-points thick blue, solid outline and a white fill with a
# transparency of 70 percentage ("@30").
fig.legend(position="jTL+o0.3c/0.2c", box="+p2p,blue+gwhite@30")
fig.legend(position="jTL+o0.3c/0.2c", box=Box(pen="2p,blue", fill="white@30"))

fig.show()

Expand Down Expand Up @@ -152,7 +153,7 @@
fig.basemap(region=[-5, 5, -5, 5], projection="M10c", frame=True)

# Pass the io.StringIO object to the "spec" parameter
fig.legend(spec=spec_io, position="jMC+w9c", box="+p1p,gray50+ggray95")
fig.legend(spec=spec_io, position="jMC+w9c", box=Box(pen="1p,gray50", fill="gray95"))

fig.show()

Expand Down
66 changes: 65 additions & 1 deletion pygmt/alias.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
The PyGMT alias system to convert PyGMT's long-form arguments to GMT's short-form.
"""

import dataclasses
from collections.abc import Mapping, Sequence
from typing import Any, Literal

Expand Down Expand Up @@ -57,7 +58,7 @@ def _to_string(
size
Expected size of the 1-D sequence. It can be either an integer or a sequence of
integers. If an integer, it is the expected size of the 1-D sequence. If it is a
sequence, it is the allowed sizes of the 1-D sequence.
sequence, it is the allowed size of the 1-D sequence.
ndim
The expected maximum number of dimensions of the sequence.
name
Expand Down Expand Up @@ -131,3 +132,66 @@ def _to_string(
# "prefix" and "mapping" are ignored. We can enable them when needed.
_value = sequence_join(value, separator=separator, size=size, ndim=ndim, name=name)
return f"{prefix}{_value}"


@dataclasses.dataclass
class Alias:
"""
Class for aliasing a PyGMT parameter to a GMT option or a modifier.

Attributes
----------
value
The value of the alias.
prefix
The string to add as a prefix to the returned value.
mapping
A mapping dictionary to map PyGMT's long-form arguments to GMT's short-form.
separator
The separator to use if the value is a sequence.
size
Expected size of the 1-D sequence. It can be either an integer or a sequence of
integers. If an integer, it is the expected size of the 1-D sequence. If it is a
sequence, it is the allowed size of the 1-D sequence.
ndim
The expected maximum number of dimensions of the sequence.
name
The name of the parameter to be used in the error message.

Examples
--------
>>> par = Alias((3.0, 3.0), prefix="+o", separator="/")
>>> par._value
'+o3.0/3.0'

>>> par = Alias("mean", mapping={"mean": "a", "mad": "d", "full": "g"})
>>> par._value
'a'

>>> par = Alias(["xaf", "yaf", "WSen"])
>>> par._value
['xaf', 'yaf', 'WSen']
"""

value: Any
prefix: str = ""
mapping: Mapping | None = None
separator: Literal["/", ","] | None = None
size: int | Sequence[int] | None = None
ndim: int = 1
name: str | None = None

@property
def _value(self) -> str | list[str] | None:
"""
The value of the alias as a string, a sequence of strings or None.
"""
return _to_string(
value=self.value,
prefix=self.prefix,
mapping=self.mapping,
separator=self.separator,
size=self.size,
ndim=self.ndim,
name=self.name,
)
5 changes: 5 additions & 0 deletions pygmt/params/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
Classes for common parameters in PyGMT.
"""

from pygmt.params.box import Box
63 changes: 63 additions & 0 deletions pygmt/params/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""
Base class for common parameters shared in PyGMT.
"""


class BaseParam:
"""
Base class for parameters in PyGMT.

To define a new parameter class, inherit from this class and define the attributes
that correspond to the parameters you want to include. The class should also
implement the ``_aliases`` property, which returns a list of ``Alias`` objects. Each
``Alias`` object represents a parameter and its value, and the ``__str__`` method
will concatenate these values into a single string that can be passed to GMT.

Examples
--------
>>> from typing import Any
>>> import dataclasses
>>> from pygmt.params.base import BaseParam
>>> from pygmt.alias import Alias
>>>
>>> @dataclasses.dataclass(repr=False)
... class Test(BaseParam):
... par1: Any = None
... par2: Any = None
... par3: Any = None
...
... @property
... def _aliases(self):
... return [
... Alias(self.par1),
... Alias(self.par2, prefix="+a"),
... Alias(self.par3, prefix="+b", separator="/"),
... ]

>>> var = Test(par1="val1")
>>> str(var)
'val1'
>>> repr(var)
"Test(par1='val1')"

>>> var = Test(par1="val1", par2="val2", par3=("val3a", "val3b"))
>>> str(var)
'val1+aval2+bval3a/val3b'
>>> repr(var)
"Test(par1='val1', par2='val2', par3=('val3a', 'val3b'))"
"""

def __str__(self):
"""
String representation of the object that can be passed to GMT directly.
"""
return "".join(
[alias._value for alias in self._aliases if alias._value is not None]
)

def __repr__(self):
"""
String representation of the object.
"""
params = ", ".join(f"{k}={v!r}" for k, v in vars(self).items() if v is not None)
return f"{self.__class__.__name__}({params})"
Loading