From 573d27b84b3edf7f4be1804efaa64863a9b7320a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=ADvia=20Lutz?= <108961867+livlutz@users.noreply.github.com> Date: Thu, 3 Jul 2025 15:45:30 +0000 Subject: [PATCH 1/4] Add interpolation option for facecolors in plot_surface method --- lib/mpl_toolkits/mplot3d/axes3d.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 55b204022fb9..84fb25c96939 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -2139,7 +2139,7 @@ def fill_between(self, x1, y1, z1, x2, y2, z2, *, return polyc def plot_surface(self, X, Y, Z, *, norm=None, vmin=None, - vmax=None, lightsource=None, axlim_clip=False, **kwargs): + vmax=None, lightsource=None, axlim_clip=False, interpolation=None, **kwargs): """ Create a surface plot. @@ -2249,6 +2249,18 @@ def plot_surface(self, X, Y, Z, *, norm=None, vmin=None, fcolors = kwargs.pop('facecolors', None) + if fcolors is not None and interpolation is not None: + try: + from scipy.ndimage import zoom + except ImportError: + raise ImportError("scipy is required for interpolation of facecolors") + fc = np.asarray(fcolors) + + zoom_factors = [X.shape[0] / fc.shape[0], X.shape[1] / fc.shape[1]] + order = 1 if interpolation == 'bilinear' else 3 if interpolation == 'bicubic' else 0 + fc_interp = zoom(fc, zoom_factors, order=order) + fcolors = fc_interp + cmap = kwargs.get('cmap', None) shade = kwargs.pop('shade', cmap is None) if shade is None: @@ -2308,7 +2320,7 @@ def plot_surface(self, X, Y, Z, *, norm=None, vmin=None, if fcolors is not None: polyc = art3d.Poly3DCollection( - polys, edgecolors=colset, facecolors=colset, shade=shade, + polys, facecolors=fcolors, shade=shade, lightsource=lightsource, axlim_clip=axlim_clip, **kwargs) elif cmap: polyc = art3d.Poly3DCollection(polys, axlim_clip=axlim_clip, **kwargs) From c4189826bf0372b07fa54184a5e2552b950094be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=ADvia=20Lutz?= <108961867+livlutz@users.noreply.github.com> Date: Thu, 3 Jul 2025 17:02:18 +0000 Subject: [PATCH 2/4] Refactor plot_surface method to fix linting errors --- lib/mpl_toolkits/mplot3d/axes3d.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 84fb25c96939..5f3df8a6e114 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -2138,8 +2138,10 @@ def fill_between(self, x1, y1, z1, x2, y2, z2, *, self.auto_scale_xyz([x1, x2], [y1, y2], [z1, z2], had_data) return polyc - def plot_surface(self, X, Y, Z, *, norm=None, vmin=None, - vmax=None, lightsource=None, axlim_clip=False, interpolation=None, **kwargs): + def plot_surface( + self, X, Y, Z, *, norm=None, vmin=None, vmax=None, lightsource=None, + axlim_clip=False, interpolation=None, **kwargs +): """ Create a surface plot. @@ -2256,8 +2258,15 @@ def plot_surface(self, X, Y, Z, *, norm=None, vmin=None, raise ImportError("scipy is required for interpolation of facecolors") fc = np.asarray(fcolors) - zoom_factors = [X.shape[0] / fc.shape[0], X.shape[1] / fc.shape[1]] - order = 1 if interpolation == 'bilinear' else 3 if interpolation == 'bicubic' else 0 + zoom_factors = [ + X.shape[0] / fc.shape[0], + X.shape[1] / fc.shape[1] + ] + order = ( + 1 if interpolation == 'bilinear' + else 3 if interpolation == 'bicubic' + else 0 + ) fc_interp = zoom(fc, zoom_factors, order=order) fcolors = fc_interp @@ -2515,12 +2524,15 @@ def plot_trisurf(self, *args, color=None, norm=None, vmin=None, vmax=None, shade : bool, default: True Whether to shade the facecolors. Shading is always disabled when *cmap* is specified. + lightsource : `~matplotlib.colors.LightSource`, optional The lightsource to use when *shade* is True. + axlim_clip : bool, default: False Whether to hide patches with a vertex outside the axes view limits. .. versionadded:: 3.10 + **kwargs All other keyword arguments are passed on to :class:`~mpl_toolkits.mplot3d.art3d.Poly3DCollection` From 0f67a3d85a2e7119066708ec7744248e02934796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=ADvia=20Lutz?= <108961867+livlutz@users.noreply.github.com> Date: Thu, 3 Jul 2025 17:39:22 +0000 Subject: [PATCH 3/4] Fixing RGB + adding test for interpolation feature --- lib/mpl_toolkits/mplot3d/axes3d.py | 5 +++-- lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 5f3df8a6e114..323f0f19f74a 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -2257,7 +2257,6 @@ def plot_surface( except ImportError: raise ImportError("scipy is required for interpolation of facecolors") fc = np.asarray(fcolors) - zoom_factors = [ X.shape[0] / fc.shape[0], X.shape[1] / fc.shape[1] @@ -2268,7 +2267,9 @@ def plot_surface( else 0 ) fc_interp = zoom(fc, zoom_factors, order=order) - fcolors = fc_interp + if fc_interp.shape[2] not in (3, 4): + raise ValueError("Interpolated facecolors must have 3 or 4 channels (RGB or RGBA)") + fcolors = fc_interp.astype(float) cmap = kwargs.get('cmap', None) shade = kwargs.pop('shade', cmap is None) diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index cd45c8e33a6f..b860204b1d22 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -652,6 +652,24 @@ def test_surface3d(): fig.colorbar(surf, shrink=0.5, aspect=5) +@mpl3d_image_comparison(['surface3d_facecolors_interp.png'], style='mpl20') +def test_surface3d_facecolors_interpolation(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + X = np.linspace(-2, 2, 5) + Y = np.linspace(-2, 2, 5) + X, Y = np.meshgrid(X, Y) + Z = np.sin(X**2 + Y**2) + # Facecolors: low-res RGB grid + C = np.zeros((4, 4, 3)) + C[..., 0] = np.linspace(0, 1, 4)[:, None] # Red gradient + C[..., 1] = np.linspace(1, 0, 4)[None, :] # Green gradient + C[..., 2] = 0.5 # Blue constant + + # Test bicubic interpolation + ax.plot_surface(X, Y, Z, facecolors=C, interpolation='bicubic', shade=False) + ax.set_title("plot_surface with facecolors and bicubic interpolation") + @image_comparison(['surface3d_label_offset_tick_position.png'], style='mpl20') def test_surface3d_label_offset_tick_position(): plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated From 71968e1bcb2bae8fad71e1adbc842443d168fe09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=ADvia=20Lutz?= <108961867+livlutz@users.noreply.github.com> Date: Thu, 3 Jul 2025 17:46:22 +0000 Subject: [PATCH 4/4] Fix linting errors --- lib/mpl_toolkits/mplot3d/axes3d.py | 4 +++- lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 323f0f19f74a..bc327d7c490b 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -2268,7 +2268,9 @@ def plot_surface( ) fc_interp = zoom(fc, zoom_factors, order=order) if fc_interp.shape[2] not in (3, 4): - raise ValueError("Interpolated facecolors must have 3 or 4 channels (RGB or RGBA)") + raise ValueError( + "Interpolated facecolors must have 3 or 4 channels (RGB or RGBA)" + ) fcolors = fc_interp.astype(float) cmap = kwargs.get('cmap', None) diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index b860204b1d22..8f43d1949851 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -670,6 +670,7 @@ def test_surface3d_facecolors_interpolation(): ax.plot_surface(X, Y, Z, facecolors=C, interpolation='bicubic', shade=False) ax.set_title("plot_surface with facecolors and bicubic interpolation") + @image_comparison(['surface3d_label_offset_tick_position.png'], style='mpl20') def test_surface3d_label_offset_tick_position(): plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated