Skip to content

Commit d8eabe7

Browse files
committed
Add the Frame/Axis class for setting frame and axes
1 parent 83f06c3 commit d8eabe7

3 files changed

Lines changed: 209 additions & 0 deletions

File tree

pygmt/params/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
"""
44

55
from pygmt.params.box import Box
6+
from pygmt.params.frame import Axis, Frame
67
from pygmt.params.pattern import Pattern
78
from pygmt.params.position import Position

pygmt/params/frame.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
"""
2+
The Axes, Axis, and Frame classes for specifying the frame.
3+
"""
4+
5+
import dataclasses
6+
7+
from pygmt.alias import Alias
8+
from pygmt.exceptions import GMTParameterError
9+
from pygmt.params.base import BaseParam
10+
11+
12+
@dataclasses.dataclass(repr=False)
13+
class Axis(BaseParam):
14+
"""
15+
Class for setting up one axis of a plot.
16+
"""
17+
18+
# Specify the intervals for annotations, ticks, and gridlines.
19+
annot: float | None = None
20+
tick: float | None = None
21+
grid: float | None = None
22+
# The label for the axis [Default is no label].
23+
label: str | None = None
24+
25+
@property
26+
def _aliases(self):
27+
return [
28+
Alias(self.annot, name="annot", prefix="a"),
29+
Alias(self.tick, name="tick", prefix="f"),
30+
Alias(self.grid, name="grid", prefix="g"),
31+
Alias(self.label, name="label", prefix="+l"),
32+
]
33+
34+
35+
@dataclasses.dataclass(repr=False)
36+
class _Axes(BaseParam):
37+
"""
38+
A private class to build the Axes part of the Frame class.
39+
"""
40+
41+
# Refer to the Frame class for full documentation.
42+
axes: str | None = None
43+
title: str | None = None
44+
45+
@property
46+
def _aliases(self):
47+
return [
48+
Alias(self.axes, name="axes"),
49+
Alias(self.title, name="title", prefix="+t"),
50+
]
51+
52+
53+
@dataclasses.dataclass(repr=False)
54+
class Frame(BaseParam):
55+
"""
56+
Class for setting up the frame of a plot.
57+
"""
58+
59+
#: Specify which axes to draw and their attributes.
60+
axes: str | None = None
61+
62+
#: The title string centered above the plot frame [Default is no title].
63+
title: str | None = None
64+
65+
#: Specify the attributes for axes.
66+
#:
67+
#: The attributes for each axis can be specified in two ways:
68+
#: #. specifying the same attributes for all axes using the ``axis`` parameter
69+
#: #. specifying the attributes for each axis separately using the ``xaxis``,
70+
#: ``yaxis``, ``zaxis`` parameter for the x-, y, and z-axes, respectively.
71+
#:
72+
#: GMT uses the notion of primary (the default) and secondary axes, while secondary
73+
#: axes are optional and mostly used for time axes annotations. To specify the
74+
#: attributes for the secondary axes, use the ``xaxis2``, ``yaxis2``, and ``zaxis2``
75+
#: parameters.
76+
axis: Axis | None = None
77+
xaxis: Axis | None = None
78+
yaxis: Axis | None = None
79+
zaxis: Axis | None = None
80+
xaxis2: Axis | None = None
81+
yaxis2: Axis | None = None
82+
zaxis2: Axis | None = None
83+
84+
def _validate(self):
85+
"""
86+
Validate the parameters of the Frame class.
87+
"""
88+
if self.axis is not None and any(
89+
[self.xaxis, self.yaxis, self.zaxis, self.xaxis2, self.yaxis2, self.zaxis2]
90+
):
91+
raise GMTParameterError(
92+
conflicts_with=(
93+
"axis",
94+
["xaxis", "yaxis", "zaxis", "xaxis2", "yaxis2", "zaxis2"],
95+
),
96+
reason="Either 'axis' or the individual axis parameters can be set, but not both.",
97+
)
98+
99+
@property
100+
def _aliases(self):
101+
return [
102+
Alias(_Axes(axes=self.axes, title=self.title)),
103+
Alias(self.axis, name="axis"),
104+
Alias(self.xaxis, name="xaxis", prefix="px" if self.xaxis2 else "x"),
105+
Alias(self.yaxis, name="yaxis", prefix="py" if self.yaxis2 else "y"),
106+
Alias(self.zaxis, name="zaxis", prefix="pz" if self.zaxis2 else "z"),
107+
Alias(self.xaxis2, name="xaxis2", prefix="sx"),
108+
Alias(self.yaxis2, name="yaxis2", prefix="sy"),
109+
Alias(self.zaxis2, name="zaxis2", prefix="sz"),
110+
]
111+
112+
def __iter__(self):
113+
"""
114+
Iterate over the aliases of the class.
115+
116+
Yields
117+
------
118+
The value of each alias in the class. None are excluded.
119+
"""
120+
yield from (alias._value for alias in self._aliases if alias._value is not None)

pygmt/tests/test_params_frame.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"""
2+
Test the Frame and Axis classes.
3+
"""
4+
5+
from pygmt.params import Axis, Frame
6+
7+
8+
def test_params_axis():
9+
"""
10+
Test the Axis class.
11+
"""
12+
assert str(Axis(annot=True)) == "a"
13+
assert str(Axis(annot=True, tick=True, grid=True)) == "afg"
14+
assert str(Axis(annot=30, tick=15, grid=5)) == "a30f15g5"
15+
assert str(Axis(annot=30, label="LABEL")) == "a30+lLABEL"
16+
17+
18+
def test_params_frame_only():
19+
"""
20+
Test the Frame class.
21+
"""
22+
assert str(Frame("WSen")) == "WSen"
23+
assert str(Frame(axes="WSEN", title="My Title")) == "WSEN+tMy Title"
24+
25+
26+
def test_params_frame_axis():
27+
"""
28+
Test the Frame class with uniform axis setting.
29+
"""
30+
frame = Frame(axes="lrtb", title="My Title", axis=Axis(annot=30, tick=15, grid=10))
31+
assert list(frame) == ["lrtb+tMy Title", "a30f15g10"]
32+
33+
frame = Frame(
34+
axes="WSEN",
35+
title="My Title",
36+
axis=Axis(annot=True, tick=True, grid=True, label="LABEL"),
37+
)
38+
assert list(frame) == ["WSEN+tMy Title", "afg+lLABEL"]
39+
40+
41+
def test_params_frame_separate_axes():
42+
"""
43+
Test the Frame class with separate axis settings.
44+
"""
45+
frame = Frame(
46+
axes="lrtb",
47+
title="My Title",
48+
xaxis=Axis(annot=10, tick=5, grid=2),
49+
yaxis=Axis(annot=20, tick=10, grid=4),
50+
)
51+
assert list(frame) == ["lrtb+tMy Title", "xa10f5g2", "ya20f10g4"]
52+
53+
frame = Frame(
54+
axes="WSEN",
55+
title="My Title",
56+
xaxis=Axis(annot=True, tick=True, grid=True, label="X-LABEL"),
57+
yaxis=Axis(annot=True, tick=True, grid=True, label="Y-LABEL"),
58+
)
59+
assert list(frame) == ["WSEN+tMy Title", "xafg+lX-LABEL", "yafg+lY-LABEL"]
60+
61+
62+
def test_params_frame_separate_axis_secondary():
63+
"""
64+
Test the Frame class with separate axis settings including secondary axes.
65+
"""
66+
frame = Frame(
67+
axes="lrtb",
68+
title="My Title",
69+
xaxis=Axis(annot=10, tick=5, grid=2),
70+
xaxis2=Axis(annot=15, tick=7, grid=3),
71+
yaxis=Axis(annot=20, tick=10, grid=4),
72+
yaxis2=Axis(annot=25, tick=12, grid=5),
73+
)
74+
assert list(frame) == [
75+
"lrtb+tMy Title",
76+
"pxa10f5g2",
77+
"pya20f10g4",
78+
"sxa15f7g3",
79+
"sya25f12g5",
80+
]
81+
82+
frame = Frame(
83+
axes="WSEN",
84+
title="My Title",
85+
xaxis=Axis(annot=True, tick=True, grid=True, label="X-LABEL"),
86+
yaxis=Axis(annot=True, tick=True, grid=True, label="Y-LABEL"),
87+
)
88+
assert list(frame) == ["WSEN+tMy Title", "xafg+lX-LABEL", "yafg+lY-LABEL"]

0 commit comments

Comments
 (0)