diff --git a/ultraplot/axes/plot.py b/ultraplot/axes/plot.py index d86be601e..e7d6fd3a8 100644 --- a/ultraplot/axes/plot.py +++ b/ultraplot/axes/plot.py @@ -7138,6 +7138,7 @@ def pcolorfast(self, x, y, z, **kwargs): %(plot.pcolorfast)s """ x, y, z, kw = self._parse_2d_args(x, y, z, edges=True, **kwargs) + user_interpolation = "interpolation" in kw kw.update(_pop_props(kw, "collection")) center_levels = kw.pop("center_levels", None) kw = self._parse_cmap( @@ -7148,6 +7149,8 @@ def pcolorfast(self, x, y, z, **kwargs): guide_kw = _pop_params(kw, self._update_guide) with self._keep_grid_bools(): m = self._call_native("pcolorfast", x, y, z, **kw) + if isinstance(m, mimage.AxesImage) and not user_interpolation: + m.set_interpolation("none") if not isinstance(m, mimage.AxesImage): # NOTE: PcolorImage is derivative self._fix_patch_edges(m, **edgefix_kw, **kw) self._add_auto_labels(m, **labels_kw) @@ -7405,6 +7408,7 @@ def imshow(self, z, **kwargs): %(plot.imshow)s """ kw = kwargs.copy() + kw.setdefault("interpolation", "none") center_levels = kw.pop("center_levels", None) kw = self._parse_cmap( z, center_levels=center_levels, default_discrete=False, **kw @@ -7434,6 +7438,7 @@ def spy(self, z, **kwargs): %(plot.spy)s """ kw = kwargs.copy() + user_interpolation = "interpolation" in kw 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) @@ -7442,6 +7447,8 @@ def spy(self, z, **kwargs): ) guide_kw = _pop_params(kw, self._update_guide) m = self._call_native("spy", z, **kw) + if isinstance(m, mimage.AxesImage) and not user_interpolation: + m.set_interpolation("none") self._update_guide(m, queue_colorbar=False, **guide_kw) return m diff --git a/ultraplot/internals/rcsetup.py b/ultraplot/internals/rcsetup.py index 1029c7a3d..e6ea75ccd 100644 --- a/ultraplot/internals/rcsetup.py +++ b/ultraplot/internals/rcsetup.py @@ -906,7 +906,9 @@ def _validator_accepts(validator, value): "hatch.color": BLACK, "hatch.linewidth": LINEWIDTH, "image.cmap": CMAPSEQ, - "image.interpolation": "none", + # Keep matplotlib default globally to avoid affecting third-party raster artists. + # UltraPlot image methods apply their own interpolation defaults locally. + "image.interpolation": "auto", "lines.linestyle": "-", "lines.linewidth": 1.5, "lines.markersize": 6.0, diff --git a/ultraplot/tests/test_config.py b/ultraplot/tests/test_config.py index 9a53e83d8..e602e71eb 100644 --- a/ultraplot/tests/test_config.py +++ b/ultraplot/tests/test_config.py @@ -274,3 +274,23 @@ def test_matplotlib_import_before_ultraplot_allows_custom_fontsize_tokens(): " uplt.rc[key] = 'med-large'\n" ) assert result.returncode == 0, result.stderr + + +def test_ultraplot_subplots_does_not_force_global_image_interpolation(): + """ + Regression test for issue #588: creating UltraPlot figures should not force + global raster interpolation defaults used by third-party OffsetImage artists. + """ + result = _run_in_subprocess( + "import matplotlib as mpl\n" + "from matplotlib.offsetbox import OffsetImage\n" + "mpl.rcParams['image.interpolation'] = 'auto'\n" + "import ultraplot as uplt\n" + "fig, ax = uplt.subplots()\n" + "fig.canvas.draw()\n" + "uplt.close(fig)\n" + "assert mpl.rcParams['image.interpolation'] == 'auto'\n" + "img = OffsetImage([[[0, 0, 0, 0]]]).get_children()[0]\n" + "assert img.get_interpolation() == 'auto'\n" + ) + assert result.returncode == 0, result.stderr diff --git a/ultraplot/tests/test_imshow.py b/ultraplot/tests/test_imshow.py index 5cc111ce2..af3f04da9 100644 --- a/ultraplot/tests/test_imshow.py +++ b/ultraplot/tests/test_imshow.py @@ -103,3 +103,13 @@ def test_colorbar(rng): m = axs[2].imshow(data, cmap="oslo", colorbar="b") axs[2].format(title="Imshow plot\ndiscrete=False (default)", yformatter="auto") return fig + + +def test_imshow_defaults_to_none_when_rc_interpolation_is_auto(): + with plt.rc.context({"image.interpolation": "auto"}): + fig, ax = plt.subplots() + m_default = ax.imshow(np.arange(4).reshape(2, 2)) + m_override = ax.imshow(np.arange(4).reshape(2, 2), interpolation="nearest") + assert m_default.get_interpolation() == "none" + assert m_override.get_interpolation() == "nearest" + plt.close(fig)