|
| 1 | +""" |
| 2 | +The 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 | +__doctest_skip__ = ["Axis", "Frame"] |
| 12 | + |
| 13 | + |
| 14 | +@dataclasses.dataclass(repr=False) |
| 15 | +class Axis(BaseParam): |
| 16 | + """ |
| 17 | + Class for setting up one axis of a plot. |
| 18 | +
|
| 19 | + Examples |
| 20 | + -------- |
| 21 | + To specify the same attributes for x- and y-axes, with intervals of 4 for |
| 22 | + annotations, 2 for ticks, and 1 for gridlines: |
| 23 | +
|
| 24 | + >>> import pygmt |
| 25 | + >>> fig = pygmt.Figure() |
| 26 | + >>> fig.basemap( |
| 27 | + ... region=[0, 10, 0, 20], |
| 28 | + ... projection="X10c/10c", |
| 29 | + ... frame=Axis(annot=4, tick=2, grid=1), |
| 30 | + ... ) |
| 31 | + >>> fig.show() |
| 32 | + """ |
| 33 | + |
| 34 | + #: Specify the interval for annotations. It can be ``True`` to let GMT decide the |
| 35 | + #: interval automatically; or a value to set a specific interval in the format of |
| 36 | + #: *stride*\ [±\ *phase*][*unit*], where, *stride* is the interval, *phase* is the |
| 37 | + #: offset to shift the annotations by that amount, and *unit* is one of the |
| 38 | + #: :gmt-docs:`18 supported unit codes <reference/options.html#tbl-units>` related to |
| 39 | + #: time intervals. |
| 40 | + annot: float | str | bool = False |
| 41 | + |
| 42 | + #: Specify the interval for ticks. Same format as ``annot``. |
| 43 | + tick: float | str | bool = False |
| 44 | + |
| 45 | + #: Specify the interval for gridlines. Same format as ``annot``. |
| 46 | + grid: float | str | bool = False |
| 47 | + |
| 48 | + #: Label for the axis [Default is no label]. |
| 49 | + label: str | None = None |
| 50 | + |
| 51 | + #: A leading text prefix for the axis annotations (e.g., dollar sign for plots |
| 52 | + #: related to money) [For Cartesian plots only]. |
| 53 | + prefix: str | None = None |
| 54 | + |
| 55 | + #: Unit to append to the axis annotations [For Cartesian plots only]. |
| 56 | + unit: str | None = None |
| 57 | + |
| 58 | + #: Angle of the axis annotations. |
| 59 | + angle: float | None = None |
| 60 | + |
| 61 | + @property |
| 62 | + def _aliases(self): |
| 63 | + return [ |
| 64 | + Alias(self.annot, name="annot", prefix="a"), |
| 65 | + Alias(self.tick, name="tick", prefix="f"), |
| 66 | + Alias(self.grid, name="grid", prefix="g"), |
| 67 | + Alias(self.label, name="label", prefix="+l"), |
| 68 | + Alias(self.angle, name="angle", prefix="+a"), |
| 69 | + Alias(self.prefix, name="prefix", prefix="+p"), |
| 70 | + Alias(self.unit, name="unit", prefix="+u"), |
| 71 | + ] |
| 72 | + |
| 73 | + |
| 74 | +@dataclasses.dataclass(repr=False) |
| 75 | +class _Axes(BaseParam): |
| 76 | + """ |
| 77 | + A private class to build the Axes part of the Frame class. |
| 78 | + """ |
| 79 | + |
| 80 | + axes: str | None = None |
| 81 | + title: str | None = None |
| 82 | + |
| 83 | + @property |
| 84 | + def _aliases(self): |
| 85 | + return [ |
| 86 | + Alias(self.axes, name="axes"), |
| 87 | + Alias(self.title, name="title", prefix="+t"), |
| 88 | + ] |
| 89 | + |
| 90 | + |
| 91 | +@dataclasses.dataclass(repr=False) |
| 92 | +class Frame(BaseParam): |
| 93 | + """ |
| 94 | + Class for setting up the frame and axes of a plot. |
| 95 | +
|
| 96 | + Examples |
| 97 | + -------- |
| 98 | + To draw the west and south axes with both ticks and annotations, and draw the east |
| 99 | + and north axes with ticks but without annotations: |
| 100 | +
|
| 101 | + >>> import pygmt |
| 102 | + >>> fig = pygmt.Figure() |
| 103 | + >>> fig.basemap( |
| 104 | + ... region=[0, 10, 0, 20], projection="X10c/10c", frame=Frame(axes="WSen") |
| 105 | + ... ) |
| 106 | + >>> fig.show() |
| 107 | +
|
| 108 | + To draw the west and south axes with both ticks and annotations, and draw the east |
| 109 | + and north axes without ticks and annotations. For west and south axes, specify |
| 110 | + the same attributes for both axes using the ``axis`` parameter, with intervals of 4 |
| 111 | + for annotations, 2 for ticks, and 1 for gridlines. |
| 112 | +
|
| 113 | + >>> fig = pygmt.Figure() |
| 114 | + >>> fig.basemap( |
| 115 | + ... region=[0, 10, 0, 20], |
| 116 | + ... projection="X10c/10c", |
| 117 | + ... frame=Frame(axes="WSrt", axis=Axis(annot=4, tick=2, grid=1)), |
| 118 | + ... ) |
| 119 | + >>> fig.show() |
| 120 | +
|
| 121 | + To specify the attributes for x- and y-axes separately: |
| 122 | +
|
| 123 | + >>> fig = pygmt.Figure() |
| 124 | + >>> fig.basemap( |
| 125 | + ... region=[0, 10, 0, 20], |
| 126 | + ... projection="X10c/10c", |
| 127 | + ... frame=Frame( |
| 128 | + ... axes="WSrt", |
| 129 | + ... xaxis=Axis(annot=4, tick=2, grid=1, label="X Label"), |
| 130 | + ... yaxis=Axis(annot=5, tick=2.5, grid=1, label="Y Label"), |
| 131 | + ... ), |
| 132 | + ... ) |
| 133 | + >>> fig.show() |
| 134 | + """ |
| 135 | + |
| 136 | + #: Controls which axes are drawn and whether they are annotated, using a combination |
| 137 | + #: of the codes below. Axis ommitted from the set will not be drawn. |
| 138 | + #: |
| 139 | + #: For a 2-D plot, there are four axes: west, east, south, and north (or left, |
| 140 | + #: right, bottom, top if you prefer); For a 3-D plot, there is an extra Z-axis. |
| 141 | + #: They can be denoted by the following codes: |
| 142 | + #: |
| 143 | + #: - **W** (west), **E** (east), **S** (south), **N** (north), **Z**: Draw axes with |
| 144 | + #: both ticks and annotations. |
| 145 | + #: - **w** (west), **e** (east), **s** (south), **n** (north), **z**: Draw axes with |
| 146 | + #: ticks but without annotations. |
| 147 | + #: - **l** (left), **r** (right), **b** (bottom), **t** (top), **u** (up): Draw axes |
| 148 | + #: without ticks or annotations. |
| 149 | + #: |
| 150 | + #: For examples: |
| 151 | + #: |
| 152 | + #: - ``"WS"``: Draw the west and south axes with both ticks and annotations, but do |
| 153 | + #: not draw the east and north axes. |
| 154 | + #: - ``"WSen"``: Draw the west and south axes with both ticks and annotations, draw |
| 155 | + #: the east and north axes with ticks but without annotations. |
| 156 | + #: - ``"WSrt"``: Draw the west and south axes with both ticks and annotations, draw |
| 157 | + #: the east and north axes without ticks or annotations. |
| 158 | + #: - ``"WSrtZ"``: Draw the west and south axes with both ticks and annotations, draw |
| 159 | + #: the east and north axes without ticks or annotations, and draw the z-axis with |
| 160 | + #: both ticks and annotations. |
| 161 | + #: |
| 162 | + #: For a 3-D plot, if the z-axis code is specified, a single vertical axis will be |
| 163 | + #: drawn at the most suitable corner by default. Append any combination of the |
| 164 | + #: corner IDs from 1 to 4 to draw one or more vertical axes at the corresponding |
| 165 | + #: corners (e.g., ``"WSrtZ1234"``): |
| 166 | + #: |
| 167 | + #: - **1**: the south-western (lower-left) corner |
| 168 | + #: - **2**: the south-eastern (lower-right) corner |
| 169 | + #: - **3**: the north-eastern (upper-right) corner |
| 170 | + #: - **4**: the north-western (upper-left) corner |
| 171 | + axes: str | None = None |
| 172 | + |
| 173 | + #: The title string centered above the plot frame [Default is no title]. |
| 174 | + title: str | None = None |
| 175 | + |
| 176 | + #: Specify the attributes for axes by an :class:`Axis` object. |
| 177 | + #: |
| 178 | + #: The attributes for each axis can be specified in two ways: (1) specifying the |
| 179 | + #: same attributes for all axes using the ``axis`` parameter; (2) specifying the |
| 180 | + #: attributes for each axis separately using the ``xaxis``, ``yaxis``, ``zaxis`` |
| 181 | + #: parameter for the x-, y, and z-axes, respectively. |
| 182 | + #: |
| 183 | + #: GMT uses the notion of primary (the default) and secondary axes, while secondary |
| 184 | + #: axes are optional and mostly used for time axes annotations. To specify the |
| 185 | + #: attributes for the secondary axes, use the ``xaxis2``, ``yaxis2``, and ``zaxis2`` |
| 186 | + #: parameters. |
| 187 | + axis: Axis | None = None |
| 188 | + xaxis: Axis | None = None |
| 189 | + yaxis: Axis | None = None |
| 190 | + zaxis: Axis | None = None |
| 191 | + xaxis2: Axis | None = None |
| 192 | + yaxis2: Axis | None = None |
| 193 | + zaxis2: Axis | None = None |
| 194 | + |
| 195 | + def _validate(self): |
| 196 | + """ |
| 197 | + Validate the parameters of the Frame class. |
| 198 | + """ |
| 199 | + if self.axis is not None and any( |
| 200 | + [self.xaxis, self.yaxis, self.zaxis, self.xaxis2, self.yaxis2, self.zaxis2] |
| 201 | + ): |
| 202 | + raise GMTParameterError( |
| 203 | + conflicts_with=( |
| 204 | + "axis", |
| 205 | + ["xaxis", "yaxis", "zaxis", "xaxis2", "yaxis2", "zaxis2"], |
| 206 | + ), |
| 207 | + reason="Either 'axis' or the individual axis parameters can be set, but not both.", |
| 208 | + ) |
| 209 | + |
| 210 | + @property |
| 211 | + def _aliases(self): |
| 212 | + # _Axes() maps to an empty string, which becomes '-B' without arguments and is |
| 213 | + # invalid when combined with individual axis settings (e.g., '-B -Bxaf -Byaf'). |
| 214 | + frame_settings = _Axes(axes=self.axes, title=self.title) |
| 215 | + return [ |
| 216 | + Alias(frame_settings) if str(frame_settings) else Alias(None), |
| 217 | + Alias(self.axis, name="axis"), |
| 218 | + Alias(self.xaxis, name="xaxis", prefix="px" if self.xaxis2 else "x"), |
| 219 | + Alias(self.yaxis, name="yaxis", prefix="py" if self.yaxis2 else "y"), |
| 220 | + Alias(self.zaxis, name="zaxis", prefix="pz" if self.zaxis2 else "z"), |
| 221 | + Alias(self.xaxis2, name="xaxis2", prefix="sx"), |
| 222 | + Alias(self.yaxis2, name="yaxis2", prefix="sy"), |
| 223 | + Alias(self.zaxis2, name="zaxis2", prefix="sz"), |
| 224 | + ] |
| 225 | + |
| 226 | + def __iter__(self): |
| 227 | + """ |
| 228 | + Iterate over the aliases of the class. |
| 229 | +
|
| 230 | + Yields |
| 231 | + ------ |
| 232 | + The value of each alias in the class. None are excluded. |
| 233 | + """ |
| 234 | + yield from (alias._value for alias in self._aliases if alias._value is not None) |
0 commit comments