-
Notifications
You must be signed in to change notification settings - Fork 244
Add the Frame/Axis class for setting frame and axes [Part 1] #4406
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
Changes from all commits
d8eabe7
87a2407
a184f48
3e1ad10
8f3a67c
5fbe28b
d320711
0ba431d
b61639a
3a33364
c1dadd6
2dde989
a36b687
03e0ee7
68261ed
f6926e6
a11428b
4b12b5b
d630dfb
03ce3d6
4673732
a966d66
6a1e7f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,234 @@ | ||
| """ | ||
| The Axis and Frame classes for specifying the frame. | ||
| """ | ||
|
|
||
| import dataclasses | ||
|
|
||
| from pygmt.alias import Alias | ||
| from pygmt.exceptions import GMTParameterError | ||
| from pygmt.params.base import BaseParam | ||
|
|
||
| __doctest_skip__ = ["Axis", "Frame"] | ||
|
|
||
|
|
||
| @dataclasses.dataclass(repr=False) | ||
| class Axis(BaseParam): | ||
| """ | ||
| Class for setting up one axis of a plot. | ||
|
|
||
| Examples | ||
| -------- | ||
| To specify the same attributes for x- and y-axes, with intervals of 4 for | ||
| annotations, 2 for ticks, and 1 for gridlines: | ||
|
|
||
| >>> import pygmt | ||
| >>> fig = pygmt.Figure() | ||
| >>> fig.basemap( | ||
| ... region=[0, 10, 0, 20], | ||
| ... projection="X10c/10c", | ||
| ... frame=Axis(annot=4, tick=2, grid=1), | ||
| ... ) | ||
| >>> fig.show() | ||
| """ | ||
|
|
||
| #: Specify the interval for annotations. It can be ``True`` to let GMT decide the | ||
| #: interval automatically; or a value to set a specific interval in the format of | ||
| #: *stride*\ [±\ *phase*][*unit*], where, *stride* is the interval, *phase* is the | ||
| #: offset to shift the annotations by that amount, and *unit* is one of the | ||
| #: :gmt-docs:`18 supported unit codes <reference/options.html#tbl-units>` related to | ||
| #: time intervals. | ||
| annot: float | str | bool = False | ||
|
|
||
| #: Specify the interval for ticks. Same format as ``annot``. | ||
| tick: float | str | bool = False | ||
|
|
||
| #: Specify the interval for gridlines. Same format as ``annot``. | ||
| grid: float | str | bool = False | ||
|
|
||
| #: Label for the axis [Default is no label]. | ||
| label: str | None = None | ||
|
|
||
| #: A leading text prefix for the axis annotations (e.g., dollar sign for plots | ||
| #: related to money) [For Cartesian plots only]. | ||
| prefix: str | None = None | ||
|
|
||
| #: Unit to append to the axis annotations [For Cartesian plots only]. | ||
| unit: str | None = None | ||
|
|
||
| #: Angle of the axis annotations. | ||
| angle: float | None = None | ||
|
|
||
| @property | ||
| def _aliases(self): | ||
| return [ | ||
| Alias(self.annot, name="annot", prefix="a"), | ||
| Alias(self.tick, name="tick", prefix="f"), | ||
| Alias(self.grid, name="grid", prefix="g"), | ||
| Alias(self.label, name="label", prefix="+l"), | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How to alias
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I plan to alias it to |
||
| Alias(self.angle, name="angle", prefix="+a"), | ||
| Alias(self.prefix, name="prefix", prefix="+p"), | ||
| Alias(self.unit, name="unit", prefix="+u"), | ||
| ] | ||
|
|
||
|
|
||
| @dataclasses.dataclass(repr=False) | ||
| class _Axes(BaseParam): | ||
| """ | ||
| A private class to build the Axes part of the Frame class. | ||
| """ | ||
|
|
||
| axes: str | None = None | ||
| title: str | None = None | ||
|
|
||
| @property | ||
| def _aliases(self): | ||
| return [ | ||
| Alias(self.axes, name="axes"), | ||
| Alias(self.title, name="title", prefix="+t"), | ||
| ] | ||
|
|
||
|
|
||
| @dataclasses.dataclass(repr=False) | ||
| class Frame(BaseParam): | ||
| """ | ||
| Class for setting up the frame and axes of a plot. | ||
|
|
||
| Examples | ||
| -------- | ||
| To draw the west and south axes with both ticks and annotations, and draw the east | ||
| and north axes with ticks but without annotations: | ||
|
|
||
| >>> import pygmt | ||
| >>> fig = pygmt.Figure() | ||
| >>> fig.basemap( | ||
| ... region=[0, 10, 0, 20], projection="X10c/10c", frame=Frame(axes="WSen") | ||
| ... ) | ||
| >>> fig.show() | ||
|
|
||
| To draw the west and south axes with both ticks and annotations, and draw the east | ||
| and north axes without ticks and annotations. For west and south axes, specify | ||
| the same attributes for both axes using the ``axis`` parameter, with intervals of 4 | ||
| for annotations, 2 for ticks, and 1 for gridlines. | ||
|
|
||
| >>> fig = pygmt.Figure() | ||
| >>> fig.basemap( | ||
| ... region=[0, 10, 0, 20], | ||
| ... projection="X10c/10c", | ||
| ... frame=Frame(axes="WSrt", axis=Axis(annot=4, tick=2, grid=1)), | ||
| ... ) | ||
| >>> fig.show() | ||
|
|
||
| To specify the attributes for x- and y-axes separately: | ||
|
|
||
| >>> fig = pygmt.Figure() | ||
| >>> fig.basemap( | ||
| ... region=[0, 10, 0, 20], | ||
| ... projection="X10c/10c", | ||
| ... frame=Frame( | ||
| ... axes="WSrt", | ||
| ... xaxis=Axis(annot=4, tick=2, grid=1, label="X Label"), | ||
| ... yaxis=Axis(annot=5, tick=2.5, grid=1, label="Y Label"), | ||
| ... ), | ||
| ... ) | ||
| >>> fig.show() | ||
| """ | ||
|
|
||
| #: Controls which axes are drawn and whether they are annotated, using a combination | ||
| #: of the codes below. Axis ommitted from the set will not be drawn. | ||
| #: | ||
| #: For a 2-D plot, there are four axes: west, east, south, and north (or left, | ||
| #: right, bottom, top if you prefer); For a 3-D plot, there is an extra Z-axis. | ||
| #: They can be denoted by the following codes: | ||
| #: | ||
| #: - **W** (west), **E** (east), **S** (south), **N** (north), **Z**: Draw axes with | ||
| #: both ticks and annotations. | ||
| #: - **w** (west), **e** (east), **s** (south), **n** (north), **z**: Draw axes with | ||
| #: ticks but without annotations. | ||
| #: - **l** (left), **r** (right), **b** (bottom), **t** (top), **u** (up): Draw axes | ||
| #: without ticks or annotations. | ||
| #: | ||
| #: For examples: | ||
| #: | ||
| #: - ``"WS"``: Draw the west and south axes with both ticks and annotations, but do | ||
| #: not draw the east and north axes. | ||
| #: - ``"WSen"``: Draw the west and south axes with both ticks and annotations, draw | ||
| #: the east and north axes with ticks but without annotations. | ||
| #: - ``"WSrt"``: Draw the west and south axes with both ticks and annotations, draw | ||
| #: the east and north axes without ticks or annotations. | ||
| #: - ``"WSrtZ"``: Draw the west and south axes with both ticks and annotations, draw | ||
| #: the east and north axes without ticks or annotations, and draw the z-axis with | ||
| #: both ticks and annotations. | ||
| #: | ||
| #: For a 3-D plot, if the z-axis code is specified, a single vertical axis will be | ||
| #: drawn at the most suitable corner by default. Append any combination of the | ||
| #: corner IDs from 1 to 4 to draw one or more vertical axes at the corresponding | ||
| #: corners (e.g., ``"WSrtZ1234"``): | ||
| #: | ||
| #: - **1**: the south-western (lower-left) corner | ||
| #: - **2**: the south-eastern (lower-right) corner | ||
| #: - **3**: the north-eastern (upper-right) corner | ||
| #: - **4**: the north-western (upper-left) corner | ||
| axes: str | None = None | ||
|
|
||
| #: The title string centered above the plot frame [Default is no title]. | ||
| title: str | None = None | ||
|
|
||
| #: Specify the attributes for axes by an :class:`Axis` object. | ||
| #: | ||
| #: The attributes for each axis can be specified in two ways: (1) specifying the | ||
| #: same attributes for all axes using the ``axis`` parameter; (2) specifying the | ||
| #: attributes for each axis separately using the ``xaxis``, ``yaxis``, ``zaxis`` | ||
| #: parameter for the x-, y, and z-axes, respectively. | ||
| #: | ||
| #: GMT uses the notion of primary (the default) and secondary axes, while secondary | ||
| #: axes are optional and mostly used for time axes annotations. To specify the | ||
| #: attributes for the secondary axes, use the ``xaxis2``, ``yaxis2``, and ``zaxis2`` | ||
| #: parameters. | ||
| axis: Axis | None = None | ||
| xaxis: Axis | None = None | ||
| yaxis: Axis | None = None | ||
| zaxis: Axis | None = None | ||
| xaxis2: Axis | None = None | ||
| yaxis2: Axis | None = None | ||
| zaxis2: Axis | None = None | ||
|
|
||
| def _validate(self): | ||
| """ | ||
| Validate the parameters of the Frame class. | ||
| """ | ||
| if self.axis is not None and any( | ||
| [self.xaxis, self.yaxis, self.zaxis, self.xaxis2, self.yaxis2, self.zaxis2] | ||
| ): | ||
| raise GMTParameterError( | ||
| conflicts_with=( | ||
| "axis", | ||
| ["xaxis", "yaxis", "zaxis", "xaxis2", "yaxis2", "zaxis2"], | ||
| ), | ||
| reason="Either 'axis' or the individual axis parameters can be set, but not both.", | ||
| ) | ||
|
|
||
| @property | ||
| def _aliases(self): | ||
| # _Axes() maps to an empty string, which becomes '-B' without arguments and is | ||
| # invalid when combined with individual axis settings (e.g., '-B -Bxaf -Byaf'). | ||
| frame_settings = _Axes(axes=self.axes, title=self.title) | ||
| return [ | ||
| Alias(frame_settings) if str(frame_settings) else Alias(None), | ||
| Alias(self.axis, name="axis"), | ||
| Alias(self.xaxis, name="xaxis", prefix="px" if self.xaxis2 else "x"), | ||
| Alias(self.yaxis, name="yaxis", prefix="py" if self.yaxis2 else "y"), | ||
| Alias(self.zaxis, name="zaxis", prefix="pz" if self.zaxis2 else "z"), | ||
| Alias(self.xaxis2, name="xaxis2", prefix="sx"), | ||
| Alias(self.yaxis2, name="yaxis2", prefix="sy"), | ||
| Alias(self.zaxis2, name="zaxis2", prefix="sz"), | ||
| ] | ||
|
|
||
| def __iter__(self): | ||
| """ | ||
| Iterate over the aliases of the class. | ||
|
|
||
| Yields | ||
| ------ | ||
| The value of each alias in the class. None are excluded. | ||
| """ | ||
| yield from (alias._value for alias in self._aliases if alias._value is not None) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| """ | ||
| Test the Frame and Axis classes. | ||
| """ | ||
|
|
||
| from pygmt.params import Axis, Frame | ||
|
|
||
|
|
||
| def test_params_axis(): | ||
| """ | ||
| Test the Axis class. | ||
| """ | ||
| assert str(Axis(annot=True)) == "a" | ||
| assert str(Axis(annot=True, tick=True, grid=True)) == "afg" | ||
| assert str(Axis(annot=30, tick=15, grid=5)) == "a30f15g5" | ||
| assert str(Axis(annot=30, label="LABEL")) == "a30+lLABEL" | ||
| assert str(Axis(annot=30, prefix="$", unit="m")) == "a30+p$+um" | ||
|
|
||
|
|
||
| def test_params_frame_only(): | ||
| """ | ||
| Test the Frame class. | ||
| """ | ||
| assert str(Frame("WSen")) == "WSen" | ||
| assert str(Frame(axes="WSEN", title="My Title")) == "WSEN+tMy Title" | ||
|
|
||
|
|
||
| def test_params_frame_axis(): | ||
| """ | ||
| Test the Frame class with uniform axis setting. | ||
| """ | ||
| frame = Frame(axes="lrtb", title="My Title", axis=Axis(annot=30, tick=15, grid=10)) | ||
| assert list(frame) == ["lrtb+tMy Title", "a30f15g10"] | ||
|
|
||
| frame = Frame( | ||
| axes="WSEN", | ||
| title="My Title", | ||
| axis=Axis(annot=True, tick=True, grid=True, label="LABEL"), | ||
| ) | ||
| assert list(frame) == ["WSEN+tMy Title", "afg+lLABEL"] | ||
|
|
||
|
|
||
| def test_params_frame_separate_axes(): | ||
| """ | ||
| Test the Frame class with separate axis settings. | ||
| """ | ||
| frame = Frame( | ||
| xaxis=Axis(annot=10, tick=5, grid=2.5), | ||
| yaxis=Axis(annot=20, tick=10, grid=5), | ||
| ) | ||
| assert list(frame) == ["xa10f5g2.5", "ya20f10g5"] | ||
|
|
||
| frame = Frame( | ||
| axes="lrtb", | ||
| title="My Title", | ||
| xaxis=Axis(annot=10, tick=5, grid=2), | ||
| yaxis=Axis(annot=20, tick=10, grid=4), | ||
| ) | ||
| assert list(frame) == ["lrtb+tMy Title", "xa10f5g2", "ya20f10g4"] | ||
|
|
||
| frame = Frame( | ||
| axes="WSEN", | ||
| title="My Title", | ||
| xaxis=Axis(annot=True, tick=True, grid=True, label="X-LABEL"), | ||
| yaxis=Axis(annot=True, tick=True, grid=True, label="Y-LABEL"), | ||
| ) | ||
| assert list(frame) == ["WSEN+tMy Title", "xafg+lX-LABEL", "yafg+lY-LABEL"] | ||
|
|
||
|
|
||
| def test_params_frame_separate_axis_secondary(): | ||
| """ | ||
| Test the Frame class with separate axis settings including secondary axes. | ||
| """ | ||
| frame = Frame( | ||
| axes="lrtb", | ||
| title="My Title", | ||
| xaxis=Axis(annot=10, tick=5, grid=2), | ||
| xaxis2=Axis(annot=15, tick=7, grid=3), | ||
| yaxis=Axis(annot=20, tick=10, grid=4), | ||
| yaxis2=Axis(annot=25, tick=12, grid=5), | ||
| ) | ||
| assert list(frame) == [ | ||
| "lrtb+tMy Title", | ||
| "pxa10f5g2", | ||
| "pya20f10g4", | ||
| "sxa15f7g3", | ||
| "sya25f12g5", | ||
| ] | ||
|
|
||
| frame = Frame( | ||
| axes="WSEN", | ||
| title="My Title", | ||
| xaxis=Axis(annot=True, tick=True, grid=True, label="X-LABEL"), | ||
| yaxis=Axis(annot=True, tick=True, grid=True, label="Y-LABEL"), | ||
| ) | ||
| assert list(frame) == ["WSEN+tMy Title", "xafg+lX-LABEL", "yafg+lY-LABEL"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "long" version would be
frame=Frame(axis=Axis(annot=4, tick=2, grid=1))?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes