diff --git a/pygmt/params/frame.py b/pygmt/params/frame.py index 65d8b6213ad..53a7ad0f3cb 100644 --- a/pygmt/params/frame.py +++ b/pygmt/params/frame.py @@ -189,9 +189,11 @@ class Frame(BaseParam): #: #: 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. + #: same attributes for both secondary x and y axes, use the ``axis2`` parameter. + #: To specify the attributes for the secondary axes separately, use the ``xaxis2``, + #: ``yaxis2``, and ``zaxis2`` parameters. axis: Axis | None = None + axis2: Axis | None = None xaxis: Axis | None = None yaxis: Axis | None = None zaxis: Axis | None = None @@ -210,17 +212,34 @@ def _validate(self): conflicts_with=("axis", ["xaxis", "yaxis", "xaxis2", "yaxis2"]), reason="Either 'axis' or the individual axis parameters can be set, but not both.", ) + if self.axis2 is not None and any( + [self.xaxis, self.yaxis, self.xaxis2, self.yaxis2] + ): + raise GMTParameterError( + conflicts_with=("axis2", ["xaxis", "yaxis", "xaxis2", "yaxis2"]), + reason="Either 'axis2' 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, fill=self.fill) + has_secondary_xy_axis = any([self.axis2, self.xaxis2, self.yaxis2]) 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.axis, name="axis", prefix="p" if has_secondary_xy_axis else ""), + Alias(self.axis2, name="axis2", prefix="s"), + Alias( + self.xaxis, + name="xaxis", + prefix="px" if self.xaxis2 or self.axis2 else "x", + ), + Alias( + self.yaxis, + name="yaxis", + prefix="py" if self.yaxis2 or self.axis2 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"), diff --git a/pygmt/tests/test_params_frame.py b/pygmt/tests/test_params_frame.py index bf4c6378cc0..d50bc0988e5 100644 --- a/pygmt/tests/test_params_frame.py +++ b/pygmt/tests/test_params_frame.py @@ -2,6 +2,8 @@ Test the Frame and Axis classes. """ +import pytest +from pygmt.exceptions import GMTParameterError from pygmt.params import Axis, Frame @@ -41,6 +43,14 @@ def test_params_frame_axis(): ) assert list(frame) == ["WSEN+tMy Title", "afg+lLABEL"] + frame = Frame( + axes="WSEN", + title="My Title", + axis=Axis(annot=30, tick=15, grid=10), + axis2=Axis(annot=60, tick=30, grid=20), + ) + assert list(frame) == ["WSEN+tMy Title", "pa30f15g10", "sa60f30g20"] + def test_params_frame_separate_axes(): """ @@ -96,3 +106,20 @@ def test_params_frame_separate_axis_secondary(): 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_invalid_axis_combinations(): + """ + Test that invalid combinations of uniform and individual axis settings fail. + """ + with pytest.raises(GMTParameterError, match="Either 'axis' or"): + Frame(axis=Axis(annot=1), xaxis=Axis(annot=2)) + + with pytest.raises(GMTParameterError, match="Either 'axis' or"): + Frame(axis=Axis(annot=1), xaxis2=Axis(annot=2)) + + with pytest.raises(GMTParameterError, match="Either 'axis2' or"): + Frame(axis2=Axis(annot=1), xaxis=Axis(annot=2)) + + with pytest.raises(GMTParameterError, match="Either 'axis2' or"): + Frame(axis2=Axis(annot=1), yaxis2=Axis(annot=2))