88from pygmt ._typing import PathLike , TableLike
99from pygmt .alias import Alias , AliasSystem
1010from pygmt .clib import Session
11+ from pygmt .exceptions import GMTValueError
1112from pygmt .helpers import build_arg_list , fmt_docstring , use_alias
13+ from pygmt .params import Axis , Frame
14+ from pygmt .params .frame import _Axes
15+
16+
17+ def _ternary_frame (frame ):
18+ """
19+ Convert 'frame' to ternary-compatible format.
20+
21+ For ternary diagrams, GMT uses axis names **a**, **b**, **c** instead of **x**,
22+ **y**, **z**, and there are no primary/secondary axes. This function converts a
23+ :class:`pygmt.params.Frame` or :class:`pygmt.params.Axis` object to a string or
24+ a list of strings with the correct axis prefixes.
25+
26+ Parameters
27+ ----------
28+ frame : Frame, Axis, str, list, or bool
29+ The frame parameter to convert.
30+
31+ Returns
32+ -------
33+ str, bool, or list of str
34+ The converted frame parameter. For Frame inputs, returns a list of strings;
35+ for Axis, str, bool, or list inputs, returns the value directly.
36+
37+ Examples
38+ --------
39+ >>> from pygmt.params import Axis, Frame
40+ >>> _ternary_frame(Axis(annot=True, tick=True, grid=True))
41+ ['afg', '']
42+ >>> _ternary_frame(
43+ ... Frame(title="Title", axis=Axis(annot=True, tick=True, grid=True))
44+ ... )
45+ ['+tTitle', 'afg']
46+ >>> _ternary_frame(
47+ ... Frame(
48+ ... title="Title",
49+ ... xaxis=Axis(annot=True, tick=True, grid=True, label="Water"),
50+ ... yaxis=Axis(annot=True, tick=True, grid=True, label="Air"),
51+ ... zaxis=Axis(annot=True, tick=True, grid=True, label="Limestone"),
52+ ... )
53+ ... )
54+ ['+tTitle', 'aafg+lWater', 'bafg+lAir', 'cafg+lLimestone']
55+ >>> _ternary_frame(Frame(fill="lightblue", axis=Axis(annot=True)))
56+ ['+glightblue', 'a']
57+ >>> _ternary_frame("afg")
58+ ['afg', '']
59+ >>> _ternary_frame(True)
60+ True
61+ >>> _ternary_frame(["aafg+lWater", "bafg+lAir", "cafg+lLimestone"])
62+ ['aafg+lWater', 'bafg+lAir', 'cafg+lLimestone']
63+ >>> _ternary_frame("none")
64+ 'none'
65+ >>> _ternary_frame(Frame(axes="WSen", axis=Axis(annot=True)))
66+ Traceback (most recent call last):
67+ pygmt.exceptions.GMTValueError: ...
68+ >>> _ternary_frame(Frame(xaxis2=Axis(annot=True)))
69+ Traceback (most recent call last):
70+ pygmt.exceptions.GMTValueError: ...
71+ """
72+ if isinstance (frame , Axis ):
73+ axis_str = str (frame )
74+ if axis_str :
75+ return [axis_str , "" ]
76+ return axis_str
77+ if isinstance (frame , Frame ):
78+ _attributes = ["title" , "subtitle" , "fill" , "axis" , "xaxis" , "yaxis" , "zaxis" ]
79+ if any (
80+ _attr not in _attributes and getattr (frame , _attr ) for _attr in vars (frame )
81+ ):
82+ raise GMTValueError (
83+ repr (frame ),
84+ description = "frame setting" ,
85+ reason = "For ternary diagrams, only Frame attributes "
86+ f"{ ', ' .join (repr (_attr ) for _attr in _attributes )} are supported." ,
87+ )
88+ frame_settings = _Axes (
89+ title = frame .title , subtitle = frame .subtitle , fill = frame .fill
90+ )
91+ params = [
92+ Alias (frame_settings ) if str (frame_settings ) else Alias (None ),
93+ Alias (frame .axis ),
94+ Alias (frame .xaxis , prefix = "a" ),
95+ Alias (frame .yaxis , prefix = "b" ),
96+ Alias (frame .zaxis , prefix = "c" ),
97+ ]
98+ result = [par ._value for par in params if par ._value is not None ]
99+ # When only general axis settings are used without frame-level settings
100+ # (title/fill) or axis-specific settings (xaxis/yaxis/zaxis), GMT needs
101+ # a bare -B to draw the frame border. E.g., -Bafg alone doesn't draw it.
102+ if not str (frame_settings ) and not any ((frame .xaxis , frame .yaxis , frame .zaxis )):
103+ result .append ("" )
104+ return result
105+ if isinstance (frame , str ) and frame not in {"" , "none" , "+n" }:
106+ return [frame , "" ]
107+ return frame
12108
13109
14110@fmt_docstring
@@ -20,7 +116,7 @@ def ternary( # noqa: PLR0913
20116 blabel : str | None = None ,
21117 clabel : str | None = None ,
22118 region : Sequence [float | str ] | str | None = None ,
23- frame : str | Sequence [str ] | Literal ["none" ] | bool = False ,
119+ frame : str | Sequence [str ] | Literal ["none" ] | bool | Frame | Axis = False ,
24120 verbose : Literal ["quiet" , "error" , "warning" , "timing" , "info" , "compat" , "debug" ]
25121 | bool = False ,
26122 panel : int | Sequence [int ] | bool = False ,
@@ -65,6 +161,9 @@ def ternary( # noqa: PLR0913
65161 [*amin*, *amax*, *bmin*, *bmax*, *cmin*, *cmax*].
66162 Give the min and max limits for each of the three axes **a**, **b**,
67163 and **c**.
164+ $frame
165+ For ternary diagrams, use :class:`pygmt.params.Frame` ``xaxis``, ``yaxis``, and
166+ ``zaxis`` attributes to set the **a**, **b**, and **c** axes, respectively.
68167 $cmap
69168 $fill
70169 alabel
@@ -85,15 +184,14 @@ def ternary( # noqa: PLR0913
85184 $transparency
86185 """
87186 self ._activate_figure ()
88-
89187 # -Lalabel/blabel/clabel. '-' means skipping the label.
90188 _labels = [v if v is not None else "-" for v in (alabel , blabel , clabel )]
91189 labels = _labels if any (v != "-" for v in _labels ) else None
92190
93191 aliasdict = AliasSystem (
94192 L = Alias (labels , name = "alabel/blabel/clabel" , sep = "/" , size = 3 ),
95193 ).add_common (
96- B = frame ,
194+ B = _ternary_frame ( frame ) ,
97195 R = region ,
98196 V = verbose ,
99197 c = panel ,
0 commit comments