diff --git a/pygmt/params/frame.py b/pygmt/params/frame.py index 4286e7c2070..fe0a7329b16 100644 --- a/pygmt/params/frame.py +++ b/pygmt/params/frame.py @@ -91,6 +91,7 @@ class _Axes(BaseParam): axes: str | None = None title: str | None = None + subtitle: str | None = None fill: str | None = None @property @@ -99,6 +100,7 @@ def _aliases(self): Alias(self.axes, name="axes"), Alias(self.fill, name="fill", prefix="+g"), Alias(self.title, name="title", prefix="+t"), + Alias(self.subtitle, name="subtitle", prefix="+s"), ] @@ -187,6 +189,10 @@ class Frame(BaseParam): #: The title string centered above the plot frame [Default is no title]. title: str | None = None + #: The subtitle string centered below the plot title. Requires ``title`` to be set. + #: [Default is no subtitle]. + subtitle: str | None = None + #: Fill for the interior of the frame with a color or a pattern [Default is no #: fill]. fill: str | None = None @@ -217,6 +223,11 @@ def _validate(self): """ Validate the parameters of the Frame class. """ + if self.subtitle is not None and self.title is None: + raise GMTParameterError( + required="title", + reason="The 'subtitle' attribute requires 'title' to be set.", + ) if self.axis is not None and any( [self.xaxis, self.yaxis, self.xaxis2, self.yaxis2] ): @@ -236,7 +247,9 @@ def _validate(self): 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) + frame_settings = _Axes( + axes=self.axes, title=self.title, subtitle=self.subtitle, 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), diff --git a/pygmt/tests/test_params_frame.py b/pygmt/tests/test_params_frame.py index e9ac05dae95..6411b626d0d 100644 --- a/pygmt/tests/test_params_frame.py +++ b/pygmt/tests/test_params_frame.py @@ -33,8 +33,11 @@ def test_params_frame_only(): assert str(Frame("WSen")) == "WSen" assert str(Frame(axes="WSEN", title="My Title")) == "WSEN+tMy Title" - frame = str(Frame(axes="WSEN", title="My Title", fill="red")) - assert frame == "WSEN+gred+tMy Title" + frame = Frame(axes="WSEN", title="My Title", fill="red") + assert str(frame) == "WSEN+gred+tMy Title" + + frame = Frame(axes="WSEN", title="My Title", subtitle="My Subtitle", fill="red") + assert str(frame) == "WSEN+gred+tMy Title+sMy Subtitle" def test_params_frame_axis(): @@ -47,9 +50,10 @@ def test_params_frame_axis(): frame = Frame( axes="WSEN", title="My Title", + subtitle="My Subtitle", axis=Axis(annot=True, tick=True, grid=True, label="LABEL"), ) - assert list(frame) == ["WSEN+tMy Title", "afg+lLABEL"] + assert list(frame) == ["WSEN+tMy Title+sMy Subtitle", "afg+lLABEL"] frame = Frame( axes="WSEN", @@ -120,6 +124,9 @@ def test_params_frame_invalid_axis_combinations(): """ Test that invalid combinations of uniform and individual axis settings fail. """ + with pytest.raises(GMTParameterError, match="requires 'title'"): + Frame(subtitle="My Subtitle") + with pytest.raises(GMTParameterError, match="Either 'axis' or"): Frame(axis=Axis(annot=1), xaxis=Axis(annot=2))