diff --git a/ultraplot/axes/plot.py b/ultraplot/axes/plot.py index 7e35a79d0..acb4a7a63 100644 --- a/ultraplot/axes/plot.py +++ b/ultraplot/axes/plot.py @@ -7700,11 +7700,18 @@ def spy(self, z, **kwargs): """ kw = kwargs.copy() kw.update(_pop_props(kw, "line")) # takes valid Line2D properties - default_cmap = pcolors.DiscreteColormap(["w", "k"], "_no_name") - center_levels = kw.pop("center_levels", None) - kw = self._parse_cmap( - z, center_levels=center_levels, default_cmap=default_cmap, **kw - ) + # NOTE: Matplotlib's spy draws an image when no marker is given, and a + # Line2D of markers otherwise. Only the image understands a colormap, so + # parsing one for the marker path would hand 'cmap' to a Line2D. + markers = kw.get("marker", None) is not None or kw.get("markersize") is not None + if not markers: + default_cmap = pcolors.DiscreteColormap(["w", "k"], "_no_name") + center_levels = kw.pop("center_levels", None) + kw = self._parse_cmap( + z, center_levels=center_levels, default_cmap=default_cmap, **kw + ) + else: + kw.pop("center_levels", None) guide_kw = _pop_params(kw, self._update_guide) m = self._call_native("spy", z, **kw) self._update_guide(m, queue_colorbar=False, **guide_kw) diff --git a/ultraplot/tests/test_2dplots.py b/ultraplot/tests/test_2dplots.py index 583ac8eeb..ce12b0bd1 100644 --- a/ultraplot/tests/test_2dplots.py +++ b/ultraplot/tests/test_2dplots.py @@ -453,6 +453,38 @@ def test_tripcolor_warns_when_z_and_facecolors_given(): ) +def test_spy_marker_path_takes_no_colormap(): + """ + Marker-style spy must not be handed a colormap. + + Matplotlib's spy draws an image when no marker is given and a Line2D of + markers otherwise; only the image understands `cmap`, so parsing one for + the marker path raised `Line2D.set() got an unexpected keyword argument`. + """ + from matplotlib.image import AxesImage + from matplotlib.lines import Line2D + + matrix = np.random.default_rng(51423).random((12, 12)) > 0.8 + _, axs = uplt.subplots(ncols=4) + assert isinstance(axs[0].spy(matrix), AxesImage) + assert isinstance(axs[1].spy(matrix, markersize=2), Line2D) + assert isinstance(axs[2].spy(matrix, marker="s"), Line2D) + assert isinstance(axs[3].spy(matrix, color="denim", markersize=3), Line2D) + + +def test_spy_image_path_still_takes_a_colormap(): + """ + The image path keeps its colormap handling, including the discrete default. + """ + from matplotlib.image import AxesImage + + matrix = np.random.default_rng(51423).random((12, 12)) > 0.8 + _, ax = uplt.subplots() + image = ax.spy(matrix, cmap="Greys") + assert isinstance(image, AxesImage) + assert "greys" in image.get_cmap().name.lower() + + def test_tricontour_explicit_colors_match_levels(): """ Explicit triangular contour colors should map one-to-one with levels.