Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8bdb98e
Figure.colorbar: Add parameters position/width/height and more to spe…
seisman Aug 10, 2025
573f0a7
Fix typos
seisman Dec 20, 2025
800f9b0
Merge branch 'main' into refactor/colorbar
seisman Dec 25, 2025
07098f5
Merge branch 'main' into refactor/colorbar
seisman Dec 26, 2025
b04b4e3
Add tests for the complicated -D option
seisman Dec 26, 2025
c2fcfd1
Merge branch 'main' into refactor/colorbar
seisman Dec 30, 2025
fb10768
Fix docstring
seisman Dec 30, 2025
d62eb93
Fix term to gmt-term
seisman Dec 30, 2025
93fe643
Fix
seisman Dec 31, 2025
2df56bf
Merge branch 'main' into refactor/colorbar
seisman Dec 31, 2025
75e09a3
Add tests for colorbar
seisman Jan 1, 2026
a638a03
Fix typos
seisman Jan 1, 2026
627003e
Merge branch 'main' into refactor/colorbar
seisman Jan 2, 2026
65f094a
Update pygmt/src/colorbar.py
seisman Jan 6, 2026
a3aa3d8
Merge branch 'main' into refactor/colorbar
yvonnefroehlich Jan 6, 2026
1765d90
Update pygmt/src/colorbar.py
seisman Jan 7, 2026
5ad8d5d
Merge branch 'main' into refactor/colorbar
seisman Jan 7, 2026
c86db76
Add one more test
seisman Jan 7, 2026
57f8c39
Rename nan_rectangle/nan_rectangle_position to nan/nan_position
seisman Jan 7, 2026
405383c
Rename sidebar_triangles/sidebar_triangles_height to fg_triangle/bg_t…
seisman Jan 7, 2026
2b272df
Fix type.hints
seisman Jan 8, 2026
6230d31
Simplify _alias_option_D
seisman Jan 8, 2026
829f8c3
Add more tests
seisman Jan 8, 2026
43d2479
Update test_subplot.py with the fig.colorbar syntax
seisman Jan 8, 2026
d330cbc
Improve docstrings for nan_position
seisman Jan 9, 2026
2c61de8
Improve docstrings for frame
seisman Jan 9, 2026
abcb0b4
Fix typos
seisman Jan 9, 2026
c15f49f
Remove print statements
seisman Jan 9, 2026
f394221
Apply suggestions from code review
seisman Jan 10, 2026
177993a
Fix styling
seisman Jan 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pygmt/src/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ def _parse_position(
case str() if position in _valid_anchors: # Anchor code
position = Position(position, cstype="inside")
case str(): # Raw GMT command string.
if any(v is not None for v in kwdict.values()):
if any(v is not None and v is not False for v in kwdict.values()):
msg = (
"Parameter 'position' is given with a raw GMT command string, and "
f"conflicts with parameters {', '.join(repr(c) for c in kwdict)}."
Expand Down
212 changes: 183 additions & 29 deletions pygmt/src/colorbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,118 @@
from collections.abc import Sequence
from typing import Literal

from pygmt._typing import AnchorCode
from pygmt.alias import Alias, AliasSystem
from pygmt.clib import Session
from pygmt.exceptions import GMTValueError
from pygmt.helpers import build_arg_list, fmt_docstring, use_alias
from pygmt.params import Box
from pygmt.helpers.utils import is_nonstr_iter
from pygmt.params import Box, Position
from pygmt.src._common import _parse_position

__doctest_skip__ = ["colorbar"]


def _alias_option_D( # noqa: N802, PLR0913
position=None,
length=None,
width=None,
orientation=None,
reverse=None,
nan_rectangle=None,
nan_rectangle_position=None,
sidebar_triangles=None,
sidebar_triangles_height=None,
move_text=None,
label_as_column=None,
):
"""
Return a list of Alias objects for the -D option.
"""
# Parse the 'move_text' and 'label_as_column' parameters for the +m modifier.
if move_text or label_as_column:
modifier_m = ""
_valids = {"annotations", "label", "unit"}

match move_text:
case None:
pass
case str() if move_text in _valids:
modifier_m = move_text[0]
case Sequence() if is_nonstr_iter(move_text) and all(
v in _valids for v in move_text
):
modifier_m = "".join(item[0] for item in move_text)
case _:
raise GMTValueError(
move_text,
description="move_text",
choices=_valids,
)
if label_as_column:
modifier_m += "c"
else:
modifier_m = None

return [
Alias(position, name="position"),
Alias(length, name="length", prefix="+w"), # +wlength/width
Alias(width, name="width", prefix="/"),
Alias(
orientation,
name="orientation",
mapping={"horizontal": "+h", "vertical": "+v"},
),
Alias(reverse, name="reverse", prefix="+r"),
Alias(
nan_rectangle,
name="nan_rectangle",
prefix="+n" if nan_rectangle_position in {"start", None} else "+N",
),
Alias(
sidebar_triangles,
name="sidebar_triangles",
prefix="+e",
mapping={
True: True,
False: False,
"foreground": "f",
"background": "b",
},
),
Alias(sidebar_triangles_height, name="sidebar_triangles_height"),
Alias(modifier_m, name="move_text/label_as_column", prefix="+m"),
]


@fmt_docstring
@use_alias(C="cmap", D="position", L="equalsize", Z="zfile")
@use_alias(C="cmap", L="equalsize", Z="zfile")
def colorbar( # noqa: PLR0913
self,
position: Position | Sequence[float | str] | AnchorCode | None = None,
length: float | str | None = None,
width: float | str | None = None,
orientation: Literal["horizontal", "vertical"] | None = None,
reverse: bool = False,
nan_rectangle: bool | str = False,
nan_rectangle_position: Literal["start", "end"] | None = None,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps shorter names nan and nan_position are better?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think nan_position (or nan_side) is fine.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've renamed them to nan/nan_position in 57f8c39.

sidebar_triangles: bool | Literal["foreground", "background"] = False,
sidebar_triangles_height: float | None = None,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The names are also a little too long. It's also unclear what sidebar means. What about fg_triangle/bg_triangle and triangle_height?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree that these names are a bit long. fg_trinagle and bg_triangle would mean we have two parameters, correct? Maybe cb_triangles; "cb" for "colorbar", which is clearer as "sidebar"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel cb_triangles is a bad name for two reasons:

  • It's unclear what cb stands for
  • This is a parameter for Figure.colorbar, so the prefix cb (short for colorbar) is a little duplicate

Copy link
Member Author

@seisman seisman Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about fgcolor/bgcolor, which fgcolor=True/bgcolor=True means draw a triangle for foreground/background color.

fgcolor/bgcolor are also used in the Pattern class, but it takes a color, not a boolean value.

Edit: fgcolor/bgcolor may be good names, but it would be unclear what triangle_height means.

So, I still prefer fg_triangle/bg_triangle/triangle_height.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've renamed them to fg_triangle/bg_triangle/triangle_height in 405383c.

move_text: Sequence[str] | None = None,
label_as_column: bool = False,
box: Box | bool = False,
truncate: Sequence[float] | None = None,
shading: float | Sequence[float] | bool = False,
log: bool = False,
scale: float | None = None,
projection: str | None = None,
box: Box | bool = False,
frame: str | Sequence[str] | bool = False,
region: Sequence[float | str] | str | None = None,
frame: str | Sequence[str] | bool = False,
verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"]
| bool = False,
panel: int | Sequence[int] | bool = False,
transparency: float | None = None,
perspective: float | Sequence[float] | str | bool = False,
transparency: float | None = None,
**kwargs,
):
r"""
Expand Down Expand Up @@ -70,33 +157,66 @@ def colorbar( # noqa: PLR0913
- p = perspective
- t = transparency

.. hlist::
:columns: 1

- D = position, **+w**: length/width, **+h**/**+v**: orientation,
**+r**: reverse, **+n**: nan_rectangle/nan_rectangle_position,
**+e**: sidebar_triangles/scalebar_triangles_height,
**+m**: move_text/label_as_column

Parameters
----------
frame : str or list
Set colorbar boundary frame, labels, and axes attributes.
$cmap
position : str
[**g**\|\ **j**\|\ **J**\|\ **n**\|\ **x**]\ *refpoint*\
[**+w**\ *length*\ [/\ *width*]]\ [**+e**\ [**b**\|\ **f**][*length*]]\
[**+h**\|\ **v**][**+j**\ *justify*]\
[**+m**\ [**a**\|\ **c**\|\ **l**\|\ **u**]]\
[**+n**\ [*txt*]][**+o**\ *dx*\ [/*dy*]].
Define the reference point on the map for the color scale using one of
four coordinate systems: (1) Use **g** for map (user) coordinates, (2)
use **j** or **J** for setting *refpoint* via a
:doc:`2-character justification code </techref/justification_codes>`
that refers to the (invisible) map domain rectangle,
(3) use **n** for normalized (0-1) coordinates, or (4) use **x** for
plot coordinates (inches, cm, etc.). All but **x** requires both
``region`` and ``projection`` to be specified. Append **+w** followed
by the length and width of the colorbar. If width is not specified
then it is set to 4% of the given length. Give a negative length to
reverse the scale bar. Append **+h** to get a horizontal scale
[Default is vertical (**+v**)]. By default, the anchor point on the
scale is assumed to be the bottom left corner (**BL**), but this can
be changed by appending **+j** followed by a
:doc:`2-character justification code </techref/justification_codes>`
*justify*.
position
Position of the colorbar on the plot. It can be specified in multiple ways:

- A :class:`pygmt.params.Position` object to fully control the reference point,
anchor point, and offset.
- A sequence of two values representing the x- and y-coordinates in plot
coordinates, e.g., ``(1, 2)`` or ``("1c", "2c")``.
- A :doc:`2-character justification code </techref/justification_codes>` for a
position inside the plot, e.g., ``"TL"`` for Top Left corner inside the plot.

If not specified, defaults to the Bottom Center outside of the plot.
length
width
Length and width of the colorbar. If length is given with a unit ``%`` then it
is in percentage of the corresponding plot side dimension (i.e., plot width for
a horizontal colorbar, or plot height for a vertical colorbar). If width is
given with unit ``%`` then it is in percentage of the bar length. [Length
default to 80% of the corresponding plot side dimension, and width default to
4% of the bar length].
orientation
Set the colorbar orientation to either ``"horizontal"`` or ``"vertical"``.
[Default is vertical, unless position is set to bottom-center or top-center with
``cstype="outside"`` or ``cstype="inside"``, then horizontal is the default].
reverse
Reverse the positive direction of the bar.
nan_rectangle
Draw a rectangle filled with the NaN color (via the **N** entry in the CPT or
:gmt-term:`COLOR_NAN` if no such entry) at the start of the colorbar. If a
string is given, use that string as the label for the NaN color.
nan_rectangle_position
Set the position of the NaN rectangle. Choose from ``"start"`` or ``"end"``.
[Default is ``"start"``].
sidebar_triangles
Draw sidebar triangles for back- and/or foreground colors. If set to ``True``,
both triangles are drawn. Alternatively, set it to ``"foreground"`` or
``"background"`` to draw only one triangle. The back- and/or foreground colors
are taken from the **B** and **F** entries in the CPT. If no such entries exist,
then the system default colors for **B** and **F** are used instead (
:gmt-term:`COLOR_BACKGROUND` and :gmt-term:`COLOR_FOREGROUND`).
sidebar_triangles_height
Height of the sidebar triangles [Default is half the bar width].
move_text
Move text (annotations, label, and unit) to opposite side. Accept a sequence of
strings containing one or more of ``"annotations"``, ``"label"``, and
``"unit"``. The default placement of these texts depends on the colorbar
orientation and position.
label_as_column
Print a vertical label as a column of characters (does not work with special
characters).
box
Draw a background box behind the colorbar. If set to ``True``, a simple
rectangular box is drawn using :gmt-term:`MAP_FRAME_PEN`. To customize the box
Expand Down Expand Up @@ -138,6 +258,10 @@ def colorbar( # noqa: PLR0913
may be in plot distance units or given as relative fractions and will
be automatically scaled so that the sum of the widths equals the
requested colorbar length.
$projection
$region
frame : str or list
Set colorbar boundary frame, labels, and axes attributes.
$verbose
$panel
$perspective
Expand All @@ -162,7 +286,37 @@ def colorbar( # noqa: PLR0913
"""
self._activate_figure()

position = _parse_position(
position,
kwdict={
"length": length,
"width": width,
"orientation": orientation,
"reverse": reverse,
"nan_rectangle": nan_rectangle,
"nan_rectangle_position": nan_rectangle_position,
"sidebar_triangles": sidebar_triangles,
"sidebar_triangles_height": sidebar_triangles_height,
"move_text": move_text,
"label_as_column": label_as_column,
},
default=None, # Use GMT's default behavior if position is not provided.
)

aliasdict = AliasSystem(
D=_alias_option_D(
position=position,
length=length,
width=width,
orientation=orientation,
reverse=None,
nan_rectangle=None,
nan_rectangle_position=None,
sidebar_triangles=None,
sidebar_triangles_height=None,
move_text=None,
label_as_column=None,
),
F=Alias(box, name="box"),
G=Alias(truncate, name="truncate", sep="/", size=2),
I=Alias(shading, name="shading", sep="/", size=2),
Expand Down
78 changes: 77 additions & 1 deletion pygmt/tests/test_colorbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

import pytest
from pygmt import Figure
from pygmt.alias import AliasSystem
from pygmt.exceptions import GMTInvalidInput
from pygmt.params.position import Position
from pygmt.src.colorbar import _alias_option_D


@pytest.mark.benchmark
Expand All @@ -13,7 +17,12 @@ def test_colorbar():
Create a simple colorbar.
"""
fig = Figure()
fig.colorbar(cmap="gmt/rainbow", position="x0c/0c+w4c", frame=True)
fig.colorbar(
cmap="gmt/rainbow",
position=Position((0, 0), cstype="plotcoords"),
length=4,
frame=True,
)
return fig


Expand All @@ -26,3 +35,70 @@ def test_colorbar_shading_list():
fig.basemap(region=[0, 10, 0, 2], projection="X10c/2c", frame="a")
fig.colorbar(cmap="gmt/geo", shading=[-0.7, 0.2], frame=True)
return fig


def test_colorbar_alias_D(): # noqa: N802
"""
Test the parameters for the -D option.
"""

def alias_wrapper(**kwargs):
"""
A wrapper function for testing the parameters of -D option.
"""
aliasdict = AliasSystem(D=_alias_option_D(**kwargs))
return aliasdict.get("D")

argstr = alias_wrapper(position=Position("TL", offset=0.2), length=4, width=0.5)
assert argstr == "jTL+o0.2+w4/0.5"

assert alias_wrapper(orientation="horizontal") == "+h"
assert alias_wrapper(orientation="vertical") == "+v"

assert alias_wrapper(reverse=True) == "+r"

assert alias_wrapper(nan_rectangle=True) == "+n"
assert alias_wrapper(nan_rectangle=True, nan_rectangle_position="end") == "+N"

assert alias_wrapper(sidebar_triangles=True) == "+e"
assert alias_wrapper(sidebar_triangles="foreground") == "+ef"
assert alias_wrapper(sidebar_triangles="background") == "+eb"
assert (
alias_wrapper(sidebar_triangles=True, sidebar_triangles_height=0.3) == "+e0.3"
)

assert alias_wrapper(move_text=["annotations", "label", "unit"]) == "+malu"
assert alias_wrapper(label_as_column=True) == "+mc"


@pytest.mark.mpl_image_compare(filename="test_colorbar.png")
def test_colorbar_position_deprecated_syntax():
"""
Check that passing the deprecated GMT CLI syntax string to 'position' works.
"""
fig = Figure()
fig.colorbar(cmap="gmt/rainbow", position="x0/0+w4c", frame=True)
return fig


def test_image_position_mixed_syntax():
"""
Test that mixing deprecated GMT CLI syntax string with new parameters.
"""
fig = Figure()
with pytest.raises(GMTInvalidInput):
fig.colorbar(cmap="gmt/rainbow", position="x0/0", length="4c")
with pytest.raises(GMTInvalidInput):
fig.colorbar(cmap="gmt/rainbow", position="x0/0", width="0.5c")
with pytest.raises(GMTInvalidInput):
fig.colorbar(cmap="gmt/rainbow", position="x0/0", orientation="horizontal")
with pytest.raises(GMTInvalidInput):
fig.colorbar(cmap="gmt/rainbow", position="x0/0", reverse=True)
with pytest.raises(GMTInvalidInput):
fig.colorbar(cmap="gmt/rainbow", position="x0/0", nan_rectangle=True)
with pytest.raises(GMTInvalidInput):
fig.colorbar(cmap="gmt/rainbow", position="x0/0", sidebar_triangles=True)
with pytest.raises(GMTInvalidInput):
fig.colorbar(cmap="gmt/rainbow", position="x0/0", move_text=["label"])
with pytest.raises(GMTInvalidInput):
fig.colorbar(cmap="gmt/rainbow", position="x0/0", label_as_column=True)
Loading