Skip to content

Commit 7926a95

Browse files
authored
AliasSystem: Support adding a suffix to a value and simplify Figure.wiggle (#4259)
1 parent a668235 commit 7926a95

File tree

2 files changed

+70
-67
lines changed

2 files changed

+70
-67
lines changed

pygmt/alias.py

Lines changed: 62 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
def _to_string(
1515
value: Any,
1616
prefix: str = "", # Default to an empty string to simplify the code logic.
17+
suffix: str = "", # Default to an empty string to simplify the code logic.
1718
mapping: Mapping | None = None,
1819
sep: Literal["/", ","] | None = None,
1920
size: int | Sequence[int] | None = None,
@@ -37,21 +38,23 @@ def _to_string(
3738
string that GMT accepts (e.g., mapping PyGMT's long-form argument ``"high"`` to
3839
GMT's short-form argument ``"h"``).
3940
40-
An optional prefix (e.g., `"+o"`) can be added to the beginning of the converted
41-
string.
41+
An optional prefix or suffix (e.g., `"+o"`) can be added to the beginning (or end)
42+
of the converted string.
4243
4344
To avoid extra overhead, this function does not validate parameter combinations. For
4445
example, if ``value`` is a sequence but ``sep`` is not specified, the function will
45-
return a sequence of strings. In this case, ``prefix`` has no effect, but the
46-
function does not check for such inconsistencies. The maintainer should ensure that
47-
the parameter combinations are valid.
46+
return a sequence of strings. In this case, ``prefix`` and ``suffix`` have no
47+
effect, but the function does not check for such inconsistencies. The maintainer
48+
should ensure that the parameter combinations are valid.
4849
4950
Parameters
5051
----------
5152
value
5253
The value to convert.
5354
prefix
5455
The string to add as a prefix to the returned value.
56+
suffix
57+
The string to add as a suffix to the returned value.
5558
mapping
5659
A mapping dictionary to map PyGMT's long-form arguments to GMT's short-form.
5760
sep
@@ -90,6 +93,11 @@ def _to_string(
9093
>>> _to_string(False, prefix="+a")
9194
>>> _to_string(None, prefix="+a")
9295
96+
>>> _to_string("blue", suffix="+l")
97+
'blue+l'
98+
>>> _to_string(True, suffix="+l")
99+
'+l'
100+
93101
>>> _to_string("mean", mapping={"mean": "a", "mad": "d", "full": "g"})
94102
'a'
95103
>>> _to_string("invalid", mapping={"mean": "a", "mad": "d", "full": "g"})
@@ -135,9 +143,9 @@ def _to_string(
135143
# None and False are converted to None.
136144
if value is None or value is False:
137145
return None
138-
# True is converted to an empty string with the optional prefix.
146+
# True is converted to an empty string with the optional prefix and suffix.
139147
if value is True:
140-
return f"{prefix}"
148+
return f"{prefix}{suffix}"
141149
# Any non-sequence value is converted to a string.
142150
if not is_nonstr_iter(value):
143151
if mapping:
@@ -148,16 +156,16 @@ def _to_string(
148156
choices=mapping.keys(),
149157
)
150158
value = mapping.get(value, value)
151-
return f"{prefix}{value}"
159+
return f"{prefix}{value}{suffix}"
152160

153161
# Return the sequence if separator is not specified for options like '-B'.
154162
# True in a sequence will be converted to an empty string.
155163
if sep is None:
156164
return [str(item) if item is not True else "" for item in value]
157165
# Join the sequence of values with the separator.
158-
# "prefix" and "mapping" are ignored. We can enable them when needed.
166+
# "prefix", "suffix", and "mapping" are ignored. We can enable them when needed.
159167
_value = sequence_join(value, sep=sep, size=size, ndim=ndim, name=name)
160-
return _value if is_nonstr_iter(_value) else f"{prefix}{_value}"
168+
return _value if is_nonstr_iter(_value) else f"{prefix}{_value}{suffix}"
161169

162170

163171
class Alias:
@@ -172,6 +180,8 @@ class Alias:
172180
The name of the parameter to be used in the error message.
173181
prefix
174182
The string to add as a prefix to the returned value.
183+
suffix
184+
The string to add as a suffix to the returned value.
175185
mapping
176186
A mapping dictionary to map PyGMT's long-form arguments to GMT's short-form.
177187
sep
@@ -189,6 +199,10 @@ class Alias:
189199
>>> par._value
190200
'+o3.0/3.0'
191201
202+
>>> par = Alias("blue", suffix="+l")
203+
>>> par._value
204+
'blue+l'
205+
192206
>>> par = Alias("mean", mapping={"mean": "a", "mad": "d", "full": "g"})
193207
>>> par._value
194208
'a'
@@ -203,17 +217,20 @@ def __init__(
203217
value: Any,
204218
name: str | None = None,
205219
prefix: str = "",
220+
suffix: str = "",
206221
mapping: Mapping | None = None,
207222
sep: Literal["/", ","] | None = None,
208223
size: int | Sequence[int] | None = None,
209224
ndim: int = 1,
210225
):
211226
self.name = name
212227
self.prefix = prefix
228+
self.suffix = suffix
213229
self._value = _to_string(
214230
value=value,
215231
name=name,
216232
prefix=prefix,
233+
suffix=suffix,
217234
mapping=mapping,
218235
sep=sep,
219236
size=size,
@@ -223,15 +240,28 @@ def __init__(
223240

224241
class AliasSystem(UserDict):
225242
"""
226-
Alias system for mapping PyGMT's long-form parameters to GMT's short-form options.
243+
Alias system mapping PyGMT long-form parameters to GMT short-form options.
244+
245+
This class inherits from ``UserDict`` so it behaves like a dictionary and can be
246+
passed directly to ``build_arg_list``. It also provides ``merge`` to update the
247+
alias dictionary with additional keyword arguments.
227248
228-
This class is initialized with keyword arguments, where each key is a GMT option
229-
flag, and the corresponding value is an ``Alias`` object or a list of ``Alias``
230-
objects.
249+
Initialize with keyword arguments where each key is a GMT option flag and each value
250+
is an ``Alias`` instance or a list of ``Alias`` instances. For a single ``Alias``,
251+
we use its ``_value`` property. For a list, we check for suffixes:
231252
232-
This class inherits from ``UserDict``, which allows it to behave like a dictionary
233-
and can be passed to the ``build_arg_list`` function. It also provides the ``merge``
234-
method to update the alias dictionary with additional keyword arguments.
253+
- If any ``Alias`` has a suffix, return a list of values for repeated GMT options.
254+
For example, ``[Alias("blue", suffix="+l"), Alias("red", suffix="+r")]`` becomes
255+
``-Cblue+l -Cred+r``.
256+
- Otherwise, concatenate into a single string for combined modifiers. For example,
257+
``[Alias("TL", prefix="j"), Alias((1, 1), prefix="+o")]`` becomes ``jTL+o1/1``.
258+
259+
``alias._value`` is produced by ``_to_string`` and is one of: ``None``, ``str``, or
260+
a sequence of strings.
261+
262+
- ``None`` means the parameter is not specified.
263+
- A sequence of strings means this is a repeatable option and can only have one
264+
long-form parameter.
235265
236266
Examples
237267
--------
@@ -242,6 +272,8 @@ class AliasSystem(UserDict):
242272
... par0,
243273
... par1=None,
244274
... par2=None,
275+
... par3=None,
276+
... par4=None,
245277
... frame=False,
246278
... repeat=None,
247279
... panel=None,
@@ -254,6 +286,7 @@ class AliasSystem(UserDict):
254286
... Alias(par2, name="par2", prefix="+o", sep="/"),
255287
... ],
256288
... B=Alias(frame, name="frame"),
289+
... C=[Alias(par3, suffix="+l"), Alias(par4, suffix="+r")],
257290
... D=Alias(repeat, name="repeat"),
258291
... ).add_common(
259292
... V=verbose,
@@ -265,13 +298,14 @@ class AliasSystem(UserDict):
265298
... "infile",
266299
... par1="mytext",
267300
... par2=(12, 12),
301+
... par3="blue",
302+
... par4="red",
268303
... frame=True,
269304
... repeat=[1, 2, 3],
270-
... panel=(1, 2),
271-
... verbose="debug",
272-
... J="X10c/10c",
273305
... )
274-
['-Amytext+o12/12', '-B', '-D1', '-D2', '-D3', '-JX10c/10c', '-Vd', '-c1,2']
306+
['-Amytext+o12/12', '-B', '-Cblue+l', '-Cred+r', '-D1', '-D2', '-D3']
307+
>>> func("infile", panel=(1, 2), verbose="debug", J="X10c/10c")
308+
['-JX10c/10c', '-Vd', '-c1,2']
275309
"""
276310

277311
def __init__(self, **kwargs):
@@ -281,22 +315,18 @@ def __init__(self, **kwargs):
281315
# Store the aliases in a dictionary, to be used in the merge() method.
282316
self.aliasdict = kwargs
283317

284-
# The value of each key in kwargs is an Alias object or a sequence of Alias
285-
# objects. If it is a single Alias object, we will use its _value property. If
286-
# it is a sequence of Alias objects, we will concatenate their _value properties
287-
# into a single string.
288-
#
289-
# Note that alias._value is converted by the _to_string method and can only be
290-
# None, string or sequence of strings.
291-
# - None means the parameter is not specified.
292-
# - Sequence of strings means this is a repeatable option, so it can only have
293-
# one long-form parameter.
294318
kwdict = {}
295319
for option, aliases in kwargs.items():
296320
if isinstance(aliases, Sequence): # A sequence of Alias objects.
297321
values = [alias._value for alias in aliases if alias._value is not None]
298322
if values:
299-
kwdict[option] = "".join(values)
323+
# Check if any alias has suffix - if so, return as list
324+
has_suffix = any(alias.suffix for alias in aliases)
325+
# If has suffix and multiple values, return as list;
326+
# else concatenate into a single string.
327+
kwdict[option] = (
328+
values if has_suffix and len(values) > 1 else "".join(values)
329+
)
300330
elif aliases._value is not None: # A single Alias object and not None.
301331
kwdict[option] = aliases._value
302332
super().__init__(kwdict)

pygmt/src/wiggle.py

Lines changed: 8 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,33 +13,6 @@
1313
from pygmt.src._common import _parse_position
1414

1515

16-
def _parse_fills(positive_fill, negative_fill):
17-
"""
18-
Parse the positive_fill and negative_fill parameters.
19-
20-
>>> _parse_fills("red", "blue")
21-
['red+p', 'blue+n']
22-
>>> _parse_fills(None, "blue")
23-
'blue+n'
24-
>>> _parse_fills("red", None)
25-
'red+p'
26-
>>> _parse_fills(None, None)
27-
"""
28-
_fills = []
29-
if positive_fill is not None:
30-
_fills.append(positive_fill + "+p")
31-
if negative_fill is not None:
32-
_fills.append(negative_fill + "+n")
33-
34-
match len(_fills):
35-
case 0:
36-
return None
37-
case 1:
38-
return _fills[0]
39-
case 2:
40-
return _fills
41-
42-
4316
@fmt_docstring
4417
# TODO(PyGMT>=0.20.0): Remove the deprecated 'fillpositive' parameter.
4518
# TODO(PyGMT>=0.20.0): Remove the deprecated 'fillnegative' parameter.
@@ -71,8 +44,8 @@ def wiggle( # noqa: PLR0913
7144
length: float | str | None = None,
7245
label: str | None = None,
7346
label_alignment: Literal["left", "right"] | None = None,
74-
positive_fill=None,
75-
negative_fill=None,
47+
positive_fill: str | None = None,
48+
negative_fill: str | None = None,
7649
projection: str | None = None,
7750
region: Sequence[float | str] | str | None = None,
7851
frame: str | Sequence[str] | bool = False,
@@ -115,7 +88,6 @@ def wiggle( # noqa: PLR0913
11588
$table_classes.
11689
Use parameter ``incols`` to choose which columns are x, y, z,
11790
respectively.
118-
11991
position
12092
Position of the vertical scale on the plot. It can be specified in multiple
12193
ways:
@@ -141,9 +113,9 @@ def wiggle( # noqa: PLR0913
141113
or **p** to indicate the distance unit (centimeters, inches, or
142114
points); if no unit is given we use the default unit that is
143115
controlled by :gmt-term:`PROJ_LENGTH_UNIT`.
144-
positive_fill : str
116+
positive_fill
145117
Set color or pattern for filling positive wiggles [Default is no fill].
146-
negative_fill : str
118+
negative_fill
147119
Set color or pattern for filling negative wiggles [Default is no fill].
148120
track : str
149121
Draw track [Default is no track]. Append pen attributes to use
@@ -174,8 +146,6 @@ def wiggle( # noqa: PLR0913
174146
kwdict={"length": length, "label": label, "label_alignment": label_alignment},
175147
)
176148

177-
_fills = _parse_fills(positive_fill, negative_fill)
178-
179149
aliasdict = AliasSystem(
180150
D=[
181151
Alias(position, name="position"),
@@ -188,7 +158,10 @@ def wiggle( # noqa: PLR0913
188158
),
189159
Alias(label, name="label", prefix="+l"),
190160
],
191-
G=Alias(_fills, name="positive_fill/negative_fill"),
161+
G=[
162+
Alias(positive_fill, name="positive_fill", suffix="+p"),
163+
Alias(negative_fill, name="negative_fill", suffix="+n"),
164+
],
192165
).add_common(
193166
B=frame,
194167
J=projection,

0 commit comments

Comments
 (0)