Skip to content

Commit d9ccd1f

Browse files
committed
Add the Box class for specifying box properties of embellishments
1 parent 654261c commit d9ccd1f

File tree

4 files changed

+200
-0
lines changed

4 files changed

+200
-0
lines changed

doc/api/index.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,16 @@ Getting metadata from tabular or grid data:
194194
info
195195
grdinfo
196196

197+
Common Parameters
198+
-----------------
199+
200+
.. currentmodule:: pygmt.params
201+
202+
.. autosummary::
203+
:toctree: generated
204+
205+
Box
206+
197207
Xarray Integration
198208
------------------
199209

pygmt/params/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""
2+
Classes for common parameters in PyGMT.
3+
"""
4+
5+
from pygmt.params.box import Box

pygmt/params/base.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""
2+
Base class for common parameters shared in PyGMT.
3+
"""
4+
5+
6+
class BaseParam:
7+
"""
8+
Base class for parameters in PyGMT.
9+
10+
To define a new parameter class, inherit from this class and define the attributes
11+
that correspond to the parameters you want to include. The class should also
12+
implement the ``_aliases`` property, which returns a list of ``Alias`` objects. Each
13+
``Alias`` object represents a parameter and its value, and the ``__str__`` method
14+
will concatenate these values into a single string that can be passed to GMT.
15+
16+
Examples
17+
--------
18+
>>> from typing import Any
19+
>>> import dataclasses
20+
>>> from pygmt.params.base import BaseParam
21+
>>> from pygmt.alias import Alias
22+
>>>
23+
>>> @dataclasses.dataclass(repr=False)
24+
... class Test(BaseParam):
25+
... par1: Any = None
26+
... par2: Any = None
27+
... par3: Any = None
28+
...
29+
... @property
30+
... def _aliases(self):
31+
... return [
32+
... Alias(self.par1),
33+
... Alias(self.par2, prefix="+a"),
34+
... Alias(self.par3, prefix="+b", separator="/"),
35+
... ]
36+
37+
>>> var = Test(par1="val1")
38+
>>> str(var)
39+
'val1'
40+
>>> repr(var)
41+
"Test(par1='val1')"
42+
43+
>>> var = Test(par1="val1", par2="val2", par3=("val3a", "val3b"))
44+
>>> str(var)
45+
'val1+aval2+bval3a/val3b'
46+
>>> repr(var)
47+
"Test(par1='val1', par2='val2', par3=('val3a', 'val3b'))"
48+
"""
49+
50+
def __str__(self):
51+
"""
52+
String representation of the object that can be passed to GMT directly.
53+
"""
54+
return "".join(
55+
[alias._value for alias in self._aliases if alias._value is not None]
56+
)
57+
58+
def __repr__(self):
59+
"""
60+
String representation of the object.
61+
"""
62+
params = ", ".join(f"{k}={v!r}" for k, v in vars(self).items() if v is not None)
63+
return f"{self.__class__.__name__}({params})"

pygmt/params/box.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
"""
2+
The Box class for specifying the box around GMT embellishments.
3+
"""
4+
5+
import dataclasses
6+
from collections.abc import Sequence
7+
8+
from pygmt.alias import Alias
9+
from pygmt.exceptions import GMTValueError
10+
from pygmt.params.base import BaseParam
11+
12+
13+
@dataclasses.dataclass(repr=False)
14+
class Box(BaseParam):
15+
"""
16+
Class for specifying the box around GMT embellishments.
17+
18+
Attributes
19+
----------
20+
clearance
21+
Set clearances between the embellishment and the box border. It can be either a
22+
scalar value or a sequence of two/four values.
23+
24+
- a scalar value means a uniform clearance in all four directions.
25+
- a sequence of two values means separate clearances in x- and y- directions.
26+
- a sequence of four values means separate clearances for left/right/bottom/top.
27+
fill
28+
Fill for the box. Default is no fill.
29+
pen
30+
Pen attributes for the box outline.
31+
radius
32+
Draw a rounded rectangular borders instead of sharp. Passing a value with unit
33+
to control the corner radius (default is ``"6p"``).
34+
inner_gap
35+
Gap between the outer and inner border. Default is ``"2p"``.
36+
inner_pen
37+
Pen attributes for the inner border. Default to :gmt-term:`MAP_DEFAULT_PEN`.
38+
shading_offset
39+
Place an offset background shaded region behind the box. A sequence of two
40+
values (dx, dy) indicates the shift relative to the foreground frame. Default is
41+
``("4p", "-4p")``.
42+
shading_fill
43+
Fill for the shading region. Default is ``"gray50"``.
44+
45+
Examples
46+
--------
47+
>>> from pygmt.params import Box
48+
>>> str(Box(fill="red@20"))
49+
'+gred@20'
50+
>>> str(Box(clearance=(0.2, 0.2), fill="red@20", pen="blue"))
51+
'+c0.2/0.2+gred@20+pblue'
52+
>>> str(Box(clearance=(0.2, 0.2), pen="blue", radius=True))
53+
'+c0.2/0.2+pblue+r'
54+
>>> str(Box(clearance=(0.1, 0.2, 0.3, 0.4), pen="blue", radius="10p"))
55+
'+c0.1/0.2/0.3/0.4+pblue+r10p'
56+
>>> str(
57+
... Box(
58+
... clearance=0.2,
59+
... pen="blue",
60+
... radius="10p",
61+
... shading_offset=("5p", "5p"),
62+
... shading_fill="lightred",
63+
... )
64+
... )
65+
'+c0.2+pblue+r10p+s5p/5p/lightred'
66+
>>> str(Box(clearance=0.2, inner_gap="2p", inner_pen="1p,red", pen="blue"))
67+
'+c0.2+i2p/1p,red+pblue'
68+
>>> str(Box(clearance=0.2, shading_offset=("5p", "5p"), shading_fill="lightred"))
69+
'+c0.2+s5p/5p/lightred'
70+
"""
71+
72+
# The GMT CLI syntax is:
73+
#
74+
# -F[+cclearances][+gfill][+i[[gap/]pen]][+p[pen]][+r[radius]][+s[[dx/dy/][shade]]]
75+
clearance: float | str | Sequence[float | str] | None = None
76+
fill: str | None = None
77+
inner_gap: float | str | None = None
78+
inner_pen: str | None = None
79+
pen: str | None = None
80+
radius: str | bool = False
81+
shading_offset: Sequence[float | str] | None = None
82+
shading_fill: str | None = None
83+
84+
@property
85+
def _innerborder(self) -> list[str | float] | None:
86+
"""
87+
Inner border of the box, formatted as a list of 1-2 values, or None.
88+
"""
89+
return [v for v in (self.inner_gap, self.inner_pen) if v is not None] or None
90+
91+
@property
92+
def _shading(self) -> list[str | float] | None:
93+
"""
94+
Shading for the box, formatted as a list of 1-3 values, or None.
95+
"""
96+
# Local variable to simplify the code.
97+
_shading_offset = [] if self.shading_offset is None else self.shading_offset
98+
99+
# shading_offset must be a sequence of two values (dx, dy) or None.
100+
if len(_shading_offset) not in {0, 2}:
101+
raise GMTValueError(
102+
self.shading_offset,
103+
description="value for parameter 'shading_offset'",
104+
reason="Must be a sequence of two values (dx, dy) or None.",
105+
)
106+
return [
107+
v for v in (*_shading_offset, self.shading_fill) if v is not None
108+
] or None
109+
110+
@property
111+
def _aliases(self):
112+
"""
113+
Aliases for the parameter.
114+
"""
115+
return [
116+
Alias(self.clearance, prefix="+c", separator="/", size=[1, 2, 4]),
117+
Alias(self.fill, prefix="+g"),
118+
Alias(self._innerborder, prefix="+i", separator="/", size=[1, 2]),
119+
Alias(self.pen, prefix="+p"),
120+
Alias(self.radius, prefix="+r"),
121+
Alias(self._shading, prefix="+s", separator="/", size=[1, 2, 3]),
122+
]

0 commit comments

Comments
 (0)