Skip to content

Use better contour defaults with logarithmic norms #10565

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion xarray/plot/dataarray_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -1594,7 +1594,7 @@ def newplotfunc(
kwargs["levels"] = cmap_params["levels"]
# if colors == a single color, matplotlib draws dashed negative
# contours. we lose this feature if we pass cmap and not colors
if isinstance(colors, str):
if colors is not None:
cmap_params["cmap"] = None
kwargs["colors"] = colors

Expand Down
3 changes: 0 additions & 3 deletions xarray/plot/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -931,9 +931,6 @@ def _process_cmap_cbar_kwargs(

cbar_kwargs = {} if cbar_kwargs is None else dict(cbar_kwargs)

if "contour" in func.__name__ and levels is None:
levels = 7 # this is the matplotlib default

# colors is mutually exclusive with cmap
if cmap and colors:
raise ValueError("Can't specify both cmap and colors.")
Expand Down
44 changes: 33 additions & 11 deletions xarray/tests/test_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -1776,6 +1776,18 @@ def test_levels(self) -> None:
artist = self.plotmethod(levels=3)
assert artist.extend == "neither"

def test_colormap_norm(self) -> None:
# Using a norm should plot a nice colorbar and look consistent with pcolormesh.
norm = mpl.colors.LogNorm(0.1, 1e1)

with pytest.warns(UserWarning):
artist = self.plotmethod(norm=norm, add_colorbar=True)

actual = artist.colorbar.locator()
expected = np.array([0.01, 0.1, 1.0, 10.0])

np.testing.assert_allclose(actual, expected)


@pytest.mark.slow
class TestContour(Common2dMixin, PlotTestCase):
Expand All @@ -1792,32 +1804,30 @@ def test_colors(self) -> None:
artist = self.plotmethod(colors="k")
assert artist.cmap.colors[0] == "k"

# 2 colors, will repeat every other tick:
artist = self.plotmethod(colors=["k", "b"])
assert self._color_as_tuple(artist.cmap.colors[1]) == (0.0, 0.0, 1.0)
assert artist.cmap.colors[:2] == ["k", "b"]

# 4 colors, will repeat every 4th tick:
artist = self.darray.plot.contour(
levels=[-0.5, 0.0, 0.5, 1.0], colors=["k", "r", "w", "b"]
)
assert self._color_as_tuple(artist.cmap.colors[1]) == (1.0, 0.0, 0.0)
assert self._color_as_tuple(artist.cmap.colors[2]) == (1.0, 1.0, 1.0)
assert artist.cmap.colors[:5] == ["k", "r", "w", "b"]

# the last color is now under "over"
assert self._color_as_tuple(artist.cmap._rgba_over) == (0.0, 0.0, 1.0)
assert self._color_as_tuple(artist.cmap.get_over()) == (0.0, 0.0, 1.0)

def test_colors_np_levels(self) -> None:
# https://github.com/pydata/xarray/issues/3284
levels = np.array([-0.5, 0.0, 0.5, 1.0])
artist = self.darray.plot.contour(levels=levels, colors=["k", "r", "w", "b"])
cmap = artist.cmap
assert isinstance(cmap, mpl.colors.ListedColormap)
# non-optimal typing in matplotlib (ArrayLike)
# https://github.com/matplotlib/matplotlib/blob/84464dd085210fb57cc2419f0d4c0235391d97e6/lib/matplotlib/colors.pyi#L133
colors = cast(np.ndarray, cmap.colors)

assert self._color_as_tuple(colors[1]) == (1.0, 0.0, 0.0)
assert self._color_as_tuple(colors[2]) == (1.0, 1.0, 1.0)
assert artist.cmap.colors[:5] == ["k", "r", "w", "b"] # type: ignore[attr-defined]

# the last color is now under "over"
assert hasattr(cmap, "_rgba_over")
assert self._color_as_tuple(cmap._rgba_over) == (0.0, 0.0, 1.0)
assert self._color_as_tuple(cmap.get_over()) == (0.0, 0.0, 1.0)

def test_cmap_and_color_both(self) -> None:
with pytest.raises(ValueError):
Expand All @@ -1841,6 +1851,18 @@ def test_single_level(self) -> None:
self.plotmethod(levels=[0.1])
self.plotmethod(levels=1)

def test_colormap_norm(self) -> None:
# Using a norm should plot a nice colorbar and look consistent with pcolormesh.
norm = mpl.colors.LogNorm(0.1, 1e1)

with pytest.warns(UserWarning):
artist = self.plotmethod(norm=norm, add_colorbar=True)

actual = artist.colorbar.locator()
expected = np.array([0.01, 0.1, 1.0, 10.0])

np.testing.assert_allclose(actual, expected)


class TestPcolormesh(Common2dMixin, PlotTestCase):
plotfunc = staticmethod(xplt.pcolormesh)
Expand Down
Loading