Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 24 additions & 3 deletions ultraplot/axes/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -4120,9 +4120,30 @@ def _parse_cmap(
# Parse keyword args
cmap_kw = cmap_kw or {}
norm_kw = norm_kw or {}
# If norm is given we use it to set vmin and vmax
if (vmin is not None or vmax is not None) and norm is not None:
raise ValueError("If 'norm' is given, 'vmin' and 'vmax' must not be set.")
# Tuple/list specs like ``('linear', 0, 1)`` pack positional args for
# ``constructor.Norm``. Build the Normalize now so downstream code can
# treat it uniformly with pre-constructed Normalize instances instead
# of risking a positional/kwarg collision when vmin/vmax are forwarded.
if (
np.iterable(norm)
and not isinstance(norm, str)
and not isinstance(norm, mcolors.Normalize)
and len(norm) > 1
):
norm = constructor.Norm(norm, **norm_kw)
norm_kw = {}
# A ``Normalize`` instance already carries vmin/vmax, so combining it
# with explicit vmin/vmax is ambiguous. String / single-element list or
# tuple specs are just names for ``constructor.Norm`` and accept
# vmin/vmax as kwargs.
if (vmin is not None or vmax is not None) and isinstance(
norm, mcolors.Normalize
):
raise ValueError(
"If 'norm' is a Normalize instance, 'vmin' and 'vmax' must not be "
"set. Pass them through the Normalize constructor, or specify "
"'norm' as a string / list / tuple to let vmin and vmax apply."
)
if isinstance(norm, mcolors.Normalize):
vmin = norm.vmin
vmax = norm.vmax
Expand Down
46 changes: 46 additions & 0 deletions ultraplot/tests/test_colorbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -1226,3 +1226,49 @@ def test_colorbar_span_position_matches_target_rows():
), f"Panel y0={panel_pos.y0:.3f} != row1 y0={row1_pos.y0:.3f}"
# Sanity: panel must be taller than a single row
assert panel_pos.height > row0_pos.height * 1.5


@pytest.mark.parametrize(
"norm",
["linear", ["linear"], ("linear",)],
)
def test_colorbar_norm_str_with_limits(norm):
"""
Should allow to pass vmin or vmax when we are passing a norm specification
as a string, list, or tuple (per the ``constructor.Norm`` contract).
"""
data = np.random.rand(10, 10)
fig, ax = uplt.subplots()
cm = ax.pcolormesh(data, vmin=0.1, norm=norm, vmax=1)
assert cm.norm.vmin == pytest.approx(0.1)
assert cm.norm.vmax == pytest.approx(1)


@pytest.mark.parametrize(
"norm", [("linear", 0.1, 1), ["linear", 0.1, 1], ("linear", 0.1, 1, False)]
)
def test_colorbar_norm_tuple_positional_limits(norm):
"""
Tuple / list form ``(name, vmin, vmax)`` should construct the normalizer
with the positional arguments and not collide with implicit vmin/vmax
kwargs when the user does not separately specify them.
"""
data = np.random.rand(10, 10)
fig, ax = uplt.subplots()
cm = ax.pcolormesh(data, norm=norm)
assert cm.norm.vmin == pytest.approx(0.1)
assert cm.norm.vmax == pytest.approx(1)


def test_colorbar_norm_with_limits():
""" "
Should allow to pass vmin or vmax when we are passing a str formatter
"""
data = np.random.rand(10, 10)
fig = None
with pytest.raises(ValueError):
fig, ax = uplt.subplots()
ax.pcolormesh(
data, vmin=0, norm=uplt.colors.mcolors.Normalize(vmin=0, vmax=1), vmax=1
)
return fig