FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

[Bug]: Fix 3D surface corruption on non-linear scales by masking invalid values by rahulrathnavel · Pull Request #31733 · matplotlib/matplotlib · GitHub

Repository navigation

27 changes: 23 additions & 4 deletions lib/mpl_toolkits/mplot3d/axes3d.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -2370,6 +2370,17 @@ 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 _mask_invalid_scale_values(self, X, Y, Z):
"""Mask out values that are invalid for the current scale."""
val_in_range_X = np.vectorize(self.xaxis._scale.val_in_range, otypes=[bool])
val_in_range_Y = np.vectorize(self.yaxis._scale.val_in_range, otypes=[bool])
val_in_range_Z = np.vectorize(self.zaxis._scale.val_in_range, otypes=[bool])

X = np.where(val_in_range_X(X), X, np.nan)
Y = np.where(val_in_range_Y(Y), Y, np.nan)
Z = np.where(val_in_range_Z(Z), Z, np.nan)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

This is fine for now since we're working a bug fix and want to limit changes. Being able to pass numpy arrays into val_in_range will be more efficient in the future.

return X, Y, Z

def plot_surface(self, X, Y, Z, *, norm=None, vmin=None,
vmax=None, lightsource=None, axlim_clip=False, **kwargs):
"""
Expand Down Expand Up @@ -2452,6 +2463,9 @@ def plot_surface(self, X, Y, Z, *, norm=None, vmin=None,

Z = cbook._to_unmasked_float_array(Z)
X, Y, Z = np.broadcast_arrays(X, Y, Z)

X, Y, Z = self._mask_invalid_scale_values(X, Y, Z)

rows, cols = Z.shape

has_stride = 'rstride' in kwargs or 'cstride' in kwargs
Expand Down Expand Up @@ -2618,6 +2632,9 @@ def plot_wireframe(self, X, Y, Z, *, axlim_clip=False, **kwargs):
raise ValueError("Argument Z must be 2-dimensional.")
# FIXME: Support masked arrays
X, Y, Z = np.broadcast_arrays(X, Y, Z)

X, Y, Z = self._mask_invalid_scale_values(X, Y, Z)

rows, cols = Z.shape

has_stride = 'rstride' in kwargs or 'cstride' in kwargs
Expand Down Expand Up @@ -2770,10 +2787,12 @@ def plot_trisurf(self, *args, color=None, norm=None, vmin=None, vmax=None,
z, *args = args
z = np.asarray(z)

tx, ty, tz = self._mask_invalid_scale_values(tri.x, tri.y, z)

triangles = tri.get_masked_triangles()
xt = tri.x[triangles]
yt = tri.y[triangles]
zt = z[triangles]
xt = tx[triangles]
yt = ty[triangles]
zt = tz[triangles]
verts = np.stack((xt, yt, zt), axis=-1)

if cmap:
Expand All @@ -2792,7 +2811,7 @@ def plot_trisurf(self, *args, color=None, norm=None, vmin=None, vmax=None,
facecolors=color, axlim_clip=axlim_clip, **kwargs)

self.add_collection(polyc, autolim="_datalim_only")
self.auto_scale_xyz(tri.x, tri.y, z, had_data)
self.auto_scale_xyz(tx, ty, tz, had_data)

return polyc

Expand Down
20 changes: 20 additions & 0 deletions lib/mpl_toolkits/mplot3d/tests/test_axes3d.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -3189,3 +3189,23 @@ def test_scale3d_calc_coord():
# Pane coordinate should match axis limit (y-pane at max)
assert pane_idx == 1
assert point[pane_idx] == pytest.approx(ax.get_ylim()[1])


def test_3d_log_scale_negative_masking():
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

# Set scales BEFORE plotting so the masking logic is triggered
ax.set_xscale('log')
ax.set_zscale('log')

# A 3x3 grid normally yields 4 polygons total
X, Y = np.meshgrid(np.linspace(-1, 1, 3), np.linspace(-1, 1, 3))
Z = X + Y

surf = ax.plot_surface(X, Y, Z)

# On main (broken), no values are masked, so all 4 polygons are generated.
# On our branch (fixed), the negative/zero values are masked to NaN,
# and plot_surface gracefully drops the invalid polygons.
assert len(surf.get_paths()) < 4
Loading

Back | FazBrowse Home | New Git URL