Skip to content

Commit dd01578

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

File tree

4 files changed

+189
-0
lines changed

4 files changed

+189
-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: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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+
Examples
11+
--------
12+
>>> from typing import Any
13+
>>> import dataclasses
14+
>>> from pygmt.params.base import BaseParam
15+
>>> from pygmt.alias import Alias
16+
>>>
17+
>>> @dataclasses.dataclass(repr=False)
18+
... class Test(BaseParam):
19+
... par1: Any = None
20+
... par2: Any = None
21+
... par3: Any = None
22+
...
23+
... @property
24+
... def _aliases(self):
25+
... return [
26+
... Alias(self.par1),
27+
... Alias(self.par2, prefix="+a"),
28+
... Alias(self.par3, prefix="+b", separator="/"),
29+
... ]
30+
>>> var = Test(par1="val1")
31+
>>> str(var)
32+
'val1'
33+
>>> repr(var)
34+
"Test(par1='val1')"
35+
"""
36+
37+
def __str__(self):
38+
"""
39+
String representation of the object that can be passed to GMT directly.
40+
"""
41+
return "".join(
42+
[alias._value for alias in self._aliases if alias._value is not None]
43+
)
44+
45+
def __repr__(self):
46+
"""
47+
String representation of the object.
48+
"""
49+
params = ", ".join(f"{k}={v!r}" for k, v in vars(self).items() if v is not None)
50+
return f"{self.__class__.__name__}({params})"

pygmt/params/box.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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 :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+
"""
73+
[+i[[<gap>/]<pen>]]
74+
[+s[<dx>/<dy>/][<fill>]]
75+
"""
76+
# The GMT CLI syntax is:
77+
#
78+
# -F[+cclearances][+gfill][+i[[gap/]pen]][+p[pen]][+r[radius]][+s[[dx/dy/][shade]]]
79+
clearance: float | str | Sequence[float | str] | None = None
80+
fill: str | None = None
81+
inner_gap: float | str | None = None
82+
inner_pen: str | None = None
83+
pen: str | None = None
84+
radius: str | bool = False
85+
shading_offset: Sequence[float | str] | None = None
86+
shading_fill: str | None = None
87+
88+
def _innerborder(self) -> list[str | float] | None:
89+
"""
90+
Inner border of the box, formatted as a list of 1-2 values, or None.
91+
"""
92+
return [v for v in (self.inner_gap, self.inner_pen) if v is not None] or None
93+
94+
def _shading(self) -> list[str | float] | None:
95+
"""
96+
Shading for the box, formatted as a list of 1-3 values, or None.
97+
"""
98+
# Local variable to simplify the code.
99+
_shading_offset = [] if self.shading_offset is None else self.shading_offset
100+
101+
# shading_offset must be a sequence of two values (dx, dy) or None.
102+
if len(_shading_offset) not in {0, 2}:
103+
raise GMTValueError(
104+
self.shading_offset,
105+
description="value for parameter 'shading_offset'",
106+
reason="Must be a sequence of two values (dx, dy) or None.",
107+
)
108+
return [
109+
v for v in (*_shading_offset, self.shading_fill) if v is not None
110+
] or None
111+
112+
@property
113+
def _aliases(self):
114+
"""
115+
Aliases for the parameter.
116+
"""
117+
return [
118+
Alias(self.clearance, prefix="+c", separator="/", size=[1, 2, 4]),
119+
Alias(self.fill, prefix="+g"),
120+
Alias(self._innerborder(), prefix="+i", separator="/", size=[1, 2]),
121+
Alias(self.pen, prefix="+p"),
122+
Alias(self.radius, prefix="+r"),
123+
Alias(self._shading(), prefix="+s", separator="/", size=[1, 2, 3]),
124+
]

0 commit comments

Comments
 (0)