Skip to content

Commit c892202

Browse files
committed
Figure.colorbar: Add parameters position/width/height and more to specify colorbar position and properties
1 parent 3dd7379 commit c892202

File tree

2 files changed

+173
-33
lines changed

2 files changed

+173
-33
lines changed

pygmt/src/_common.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ def _parse_position(
251251
position: Position | Sequence[float | str] | str | None,
252252
kwdict: dict[str, Any],
253253
default: Position | None,
254-
) -> Position | str:
254+
) -> Position | str | None:
255255
"""
256256
Parse the "position" parameter for embellishment-plotting functions.
257257
@@ -269,7 +269,8 @@ def _parse_position(
269269
The keyword arguments dictionary that conflicts with ``position`` if
270270
``position`` is given as a raw GMT command string.
271271
default
272-
The default Position object to use if ``position`` is ``None``.
272+
The default Position object to use if ``position`` is ``None``. If ``default``
273+
is ``None``, the GMT default is used.
273274
274275
Returns
275276
-------
@@ -339,7 +340,7 @@ def _parse_position(
339340
case str() if position in _valid_anchors: # Anchor code
340341
position = Position(position, cstype="inside")
341342
case str(): # Raw GMT command string.
342-
if any(v is not None for v in kwdict.values()):
343+
if any(v not in {None, False} for v in kwdict.values()):
343344
msg = (
344345
"Parameter 'position' is given with a raw GMT command string, and "
345346
f"conflicts with parameters {', '.join(repr(c) for c in kwdict)}."
@@ -349,7 +350,7 @@ def _parse_position(
349350
position = Position(position, cstype="plotcoords")
350351
case Position(): # Already a Position object.
351352
pass
352-
case None if default is not None: # Set default position.
353+
case None: # Set default position.
353354
position = default
354355
case _:
355356
msg = f"Invalid type for parameter 'position': {type(position)}."

pygmt/src/colorbar.py

Lines changed: 168 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,88 @@
55
from collections.abc import Sequence
66
from typing import Literal
77

8+
from pygmt._typing import AnchorCode
89
from pygmt.alias import Alias, AliasSystem
910
from pygmt.clib import Session
11+
from pygmt.exceptions import GMTValueError
1012
from pygmt.helpers import build_arg_list, fmt_docstring, use_alias
11-
from pygmt.params import Box
13+
from pygmt.helpers.utils import is_nonstr_iter
14+
from pygmt.params import Box, Position
15+
from pygmt.src._common import _parse_position
1216

1317
__doctest_skip__ = ["colorbar"]
1418

1519

20+
def _parse_move_text(
21+
move_text: Sequence[str] | None, label_as_column: bool = False
22+
) -> str | None:
23+
"""
24+
Parse the move_text parameter into the format required by GMT.
25+
26+
Examples
27+
--------
28+
>>> _parse_move_text(None)
29+
>>> _parse_move_text(["annotations", "label"])
30+
'al'
31+
>>> _parse_move_text(["unit"])
32+
'u'
33+
>>> _parse_move_text(["annotations", "label", "unit"])
34+
'alu'
35+
>>> _parse_move_text(["annotations"], label_as_column=True)
36+
'ac'
37+
>>> _parse_move_text(["invalid"])
38+
Traceback (most recent call last):
39+
...
40+
GMTValueError: 'invalid' is not a valid value for 'move_text'...
41+
42+
"""
43+
_valids = {"annotations", "label", "unit"}
44+
match move_text:
45+
case Sequence() if is_nonstr_iter(move_text) and all(
46+
v in _valids for v in move_text
47+
):
48+
argstr = "".join(item[0] for item in move_text)
49+
if label_as_column is True:
50+
argstr += "c"
51+
return argstr
52+
case None:
53+
return None
54+
case _:
55+
raise GMTValueError(
56+
move_text,
57+
description="move_text",
58+
choices=_valids,
59+
)
60+
61+
1662
@fmt_docstring
17-
@use_alias(C="cmap", D="position", L="equalsize", Z="zfile")
63+
@use_alias(C="cmap", L="equalsize", Z="zfile")
1864
def colorbar( # noqa: PLR0913
1965
self,
66+
position: Position | Sequence[float | str] | AnchorCode | None = None,
67+
length: float | str | None = None,
68+
width: float | str | None = None,
69+
orientation: Literal["horizontal", "vertical"] | None = None,
70+
reverse: bool = False,
71+
nan_rectangle: bool | str = False,
72+
nan_rectangle_position: Literal["start", "end"] | None = None,
73+
sidebar_triangles: bool | Literal["foreground", "background"] = False,
74+
sidebar_triangles_height: float | None = None,
75+
move_text: Sequence[str] | None = None,
76+
label_as_column: bool = False,
77+
box: Box | bool = False,
2078
truncate: Sequence[float] | None = None,
2179
shading: float | Sequence[float] | bool = False,
2280
log: bool = False,
2381
scale: float | None = None,
2482
projection: str | None = None,
25-
box: Box | bool = False,
26-
frame: str | Sequence[str] | bool = False,
2783
region: Sequence[float | str] | str | None = None,
84+
frame: str | Sequence[str] | bool = False,
2885
verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"]
2986
| bool = False,
3087
panel: int | Sequence[int] | bool = False,
31-
transparency: float | None = None,
3288
perspective: float | Sequence[float] | str | bool = False,
89+
transparency: float | None = None,
3390
**kwargs,
3491
):
3592
r"""
@@ -58,6 +115,9 @@ def colorbar( # noqa: PLR0913
58115
59116
$aliases
60117
- B = frame
118+
- D = position, **+w**: length/width, **+h**/**+v**: orientation,
119+
**+r**: reverse, **+n**: nan_rectangle, **+e**: sidebar_triangles,
120+
**+m**: move_annots
61121
- F = box
62122
- G = truncate
63123
- I = shading
@@ -72,31 +132,56 @@ def colorbar( # noqa: PLR0913
72132
73133
Parameters
74134
----------
75-
frame : str or list
76-
Set colorbar boundary frame, labels, and axes attributes.
77135
$cmap
78-
position : str
79-
[**g**\|\ **j**\|\ **J**\|\ **n**\|\ **x**]\ *refpoint*\
80-
[**+w**\ *length*\ [/\ *width*]]\ [**+e**\ [**b**\|\ **f**][*length*]]\
81-
[**+h**\|\ **v**][**+j**\ *justify*]\
82-
[**+m**\ [**a**\|\ **c**\|\ **l**\|\ **u**]]\
83-
[**+n**\ [*txt*]][**+o**\ *dx*\ [/*dy*]].
84-
Define the reference point on the map for the color scale using one of
85-
four coordinate systems: (1) Use **g** for map (user) coordinates, (2)
86-
use **j** or **J** for setting *refpoint* via a
87-
:doc:`2-character justification code </techref/justification_codes>`
88-
that refers to the (invisible) map domain rectangle,
89-
(3) use **n** for normalized (0-1) coordinates, or (4) use **x** for
90-
plot coordinates (inches, cm, etc.). All but **x** requires both
91-
``region`` and ``projection`` to be specified. Append **+w** followed
92-
by the length and width of the colorbar. If width is not specified
93-
then it is set to 4% of the given length. Give a negative length to
94-
reverse the scale bar. Append **+h** to get a horizontal scale
95-
[Default is vertical (**+v**)]. By default, the anchor point on the
96-
scale is assumed to be the bottom left corner (**BL**), but this can
97-
be changed by appending **+j** followed by a
98-
:doc:`2-character justification code </techref/justification_codes>`
99-
*justify*.
136+
position
137+
Position of the GMT logo on the plot. It can be specified in multiple ways:
138+
139+
- A :class:`pygmt.params.Position` object to fully control the reference point,
140+
anchor point, and offset.
141+
- A sequence of two values representing the x and y coordinates in plot
142+
coordinates, e.g., ``(1, 2)`` or ``("1c", "2c")``.
143+
- A :doc:`2-character justification code </techref/justification_codes>` for a
144+
position inside the plot, e.g., ``"TL"`` for Top Left corner inside the plot.
145+
146+
If not specified, defaults to the bottom-center corner outside of the plot.
147+
length
148+
width
149+
Length and width of the color bar. If length is given with a unit ``%`` then it
150+
is in percentage of the corrresponding plot side dimension (i.e., plot width for
151+
a horizontal colorbar, or plot height for a vertical colorbar). If width is
152+
given with unit ``%`` then it is in percentage of the bar length. [Length
153+
default to 80% of the corresponding plot side dimension, and width default to
154+
4% of the bar length].
155+
orientation
156+
Set the colorbar orientation to either ``"horizontal"`` or ``"vertical"``.
157+
[Default is vertical, unless position is set to bottom-center or top-center with
158+
``cstype="outside"`` or ``cstype="inside"``, then horizontal is the default].
159+
reverse
160+
Reverse the positive direction of the bar.
161+
nan_rectangle
162+
Draw a rectangle filled with the NaN color (via the **N** entry in the CPT or
163+
:term:`COLOR_NAN` if no such entry) at the start of the colorbar. If a string
164+
is given, use that string as the label for the NaN color.
165+
nan_rectangle_position
166+
Set the position of the NaN rectangle. Choose from ``"start"`` or ``"end"``.
167+
[Default is ``"start"``].
168+
sidebar_triangles
169+
Draw sidebar triangles for back- and/or foreground colors. If set to ``True``,
170+
both triangles are drawn. Alternatively, set it to ``"foreground"`` or
171+
``"background"`` to draw only one triangle. The back- and/or foreground colors
172+
are taken from the **B** and **F** entries in the CPT. If no such entries exist,
173+
then the system default colors for **B** and **F** are used instead (
174+
:term:`COLOR_BACKGROUND` and :term:`COLOR_FOREGROUND`).
175+
sidebar_triangles_height
176+
Height of the sidebar triangles [Default is half the bar width].
177+
move_text
178+
Move text (annotations, label, and unit) to opposite side. Accept a sequence of
179+
strings containing one or more of ``"annotations"``, ``"label"``, and
180+
``"unit"``. The default placement of these texts depends on the colorbar
181+
orientation and position.
182+
label_as_column
183+
Print a vertical label as a column of characters (does not work with special
184+
characters).
100185
box
101186
Draw a background box behind the colorbar. If set to ``True``, a simple
102187
rectangular box is drawn using :gmt-term:`MAP_FRAME_PEN`. To customize the box
@@ -138,6 +223,10 @@ def colorbar( # noqa: PLR0913
138223
may be in plot distance units or given as relative fractions and will
139224
be automatically scaled so that the sum of the widths equals the
140225
requested colorbar length.
226+
$projection
227+
$region
228+
frame : str or list
229+
Set colorbar boundary frame, labels, and axes attributes.
141230
$verbose
142231
$panel
143232
$perspective
@@ -162,7 +251,57 @@ def colorbar( # noqa: PLR0913
162251
"""
163252
self._activate_figure()
164253

254+
position = _parse_position(
255+
position,
256+
kwdict={
257+
"length": length,
258+
"width": width,
259+
"orientation": orientation,
260+
"reverse": reverse,
261+
"nan_rectangle": nan_rectangle,
262+
"nan_rectangle_position": nan_rectangle_position,
263+
"sidebar_triangles": sidebar_triangles,
264+
"sidebar_triangles_height": sidebar_triangles_height,
265+
"move_text": move_text,
266+
"label_as_column": label_as_column,
267+
},
268+
default=None, # Use GMT's default behavior if position is not provided.
269+
)
270+
165271
aliasdict = AliasSystem(
272+
D=[
273+
Alias(position, name="position"),
274+
Alias(length, name="length", prefix="+w"), # +wlength/width
275+
Alias(width, name="width", prefix="/"),
276+
Alias(
277+
orientation,
278+
name="orientation",
279+
mapping={"horizontal": "+h", "vertical": "+v"},
280+
),
281+
Alias(reverse, name="reverse", prefix="+r"),
282+
Alias(
283+
nan_rectangle,
284+
name="nan_rectangle",
285+
prefix="+n" if nan_rectangle_position == "start" else "+N",
286+
),
287+
Alias(
288+
sidebar_triangles,
289+
name="sidebar_triangles",
290+
prefix="+e",
291+
mapping={
292+
True: True,
293+
False: False,
294+
"foreground": "f",
295+
"background": "b",
296+
},
297+
),
298+
Alias(sidebar_triangles_height, name="sidebar_triangles_height"),
299+
Alias(
300+
_parse_move_text(move_text, label_as_column),
301+
name="move_text",
302+
prefix="+m",
303+
),
304+
],
166305
F=Alias(box, name="box"),
167306
G=Alias(truncate, name="truncate", sep="/", size=2),
168307
I=Alias(shading, name="shading", sep="/", size=2),

0 commit comments

Comments
 (0)