Skip to content

Commit a8709b5

Browse files
authored
Merge pull request #18375 from meeseeksmachine/auto-backport-of-pr-18293-on-v3.3.x
Backport PR #18293 on branch v3.3.x (Fix scatter3d color/linewidth re-projection)
2 parents 63e7554 + 80fbbb1 commit a8709b5

File tree

2 files changed

+57
-33
lines changed

2 files changed

+57
-33
lines changed

lib/mpl_toolkits/mplot3d/art3d.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,7 @@ def set_3d_properties(self, zs, zdir):
482482
self._facecolor3d = self.get_facecolor()
483483
self._edgecolor3d = self.get_edgecolor()
484484
self._sizes3d = self.get_sizes()
485+
self._linewidth3d = self.get_linewidth()
485486
self.stale = True
486487

487488
def do_3d_projection(self, renderer):
@@ -490,13 +491,10 @@ def do_3d_projection(self, renderer):
490491

491492
fcs = (_zalpha(self._facecolor3d, vzs) if self._depthshade else
492493
self._facecolor3d)
493-
fcs = mcolors.to_rgba_array(fcs, self._alpha)
494-
self.set_facecolors(fcs)
495-
496494
ecs = (_zalpha(self._edgecolor3d, vzs) if self._depthshade else
497495
self._edgecolor3d)
498-
499496
sizes = self._sizes3d
497+
lws = self._linewidth3d
500498

501499
# Sort the points based on z coordinates
502500
# Performance optimization: Create a sorted index array and reorder
@@ -507,11 +505,14 @@ def do_3d_projection(self, renderer):
507505
vzs = vzs[z_markers_idx]
508506
vxs = vxs[z_markers_idx]
509507
vys = vys[z_markers_idx]
510-
if self._depthshade:
508+
if len(fcs) > 1:
511509
fcs = fcs[z_markers_idx]
510+
if len(ecs) > 1:
512511
ecs = ecs[z_markers_idx]
513512
if len(sizes) > 1:
514513
sizes = sizes[z_markers_idx]
514+
if len(lws) > 1:
515+
lws = lws[z_markers_idx]
515516
vps = np.column_stack((vxs, vys))
516517

517518
fcs = mcolors.to_rgba_array(fcs, self._alpha)
@@ -520,6 +521,7 @@ def do_3d_projection(self, renderer):
520521
self.set_edgecolors(ecs)
521522
self.set_facecolors(fcs)
522523
self.set_sizes(sizes)
524+
self.set_linewidth(lws)
523525

524526
PathCollection.set_offsets(self, vps)
525527

lib/mpl_toolkits/tests/test_mplot3d.py

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import functools
2+
import itertools
23

34
import pytest
45

@@ -242,39 +243,60 @@ def test_scatter3d_color():
242243
color='b', marker='s')
243244

244245

245-
def test_scatter3d_depthshade_false():
246-
"""
247-
Test that 3d scatter plot doesn't throw
248-
IndexError with depthshade=False (issue #18037)
249-
"""
250-
x = y = z = np.arange(16)
251-
fig_test = plt.figure()
252-
ax_test = fig_test.add_subplot(projection='3d')
253-
ax_test.scatter(x, y, z, depthshade=False)
246+
@pytest.mark.parametrize('depthshade', [True, False])
247+
@check_figures_equal(extensions=['png'])
248+
def test_scatter3d_sorting(fig_ref, fig_test, depthshade):
249+
"""Test that marker properties are correctly sorted."""
254250

251+
y, x = np.mgrid[:10, :10]
252+
z = np.arange(x.size).reshape(x.shape)
255253

256-
@check_figures_equal(extensions=['png'])
257-
def test_scatter3d_size(fig_ref, fig_test):
258-
"""Test that large markers in correct position (issue #18135)"""
259-
x = np.arange(10)
260-
x, y = np.meshgrid(x, x)
261-
z = np.arange(100).reshape(10, 10)
262-
263-
s = np.full(z.shape, 5)
264-
s[0, 0] = 100
265-
s[-1, 0] = 100
266-
s[0, -1] = 100
267-
s[-1, -1] = 100
254+
sizes = np.full(z.shape, 25)
255+
sizes[0::2, 0::2] = 100
256+
sizes[1::2, 1::2] = 100
268257

269-
ax_ref = fig_ref.gca(projection='3d')
270-
ax_test = fig_test.gca(projection='3d')
258+
facecolors = np.full(z.shape, 'C0')
259+
facecolors[:5, :5] = 'C1'
260+
facecolors[6:, :4] = 'C2'
261+
facecolors[6:, 6:] = 'C3'
271262

272-
small = np.ma.masked_array(z, s == 100, dtype=float)
273-
large = np.ma.masked_array(z, s != 100, dtype=float)
263+
edgecolors = np.full(z.shape, 'C4')
264+
edgecolors[1:5, 1:5] = 'C5'
265+
edgecolors[5:9, 1:5] = 'C6'
266+
edgecolors[5:9, 5:9] = 'C7'
274267

275-
ax_ref.scatter(x, y, large, s=100, c="C0", alpha=1)
276-
ax_ref.scatter(x, y, small, s=5, c="C0", alpha=1)
277-
ax_test.scatter(x, y, z, s=s, c="C0", alpha=1)
268+
linewidths = np.full(z.shape, 2)
269+
linewidths[0::2, 0::2] = 5
270+
linewidths[1::2, 1::2] = 5
271+
272+
x, y, z, sizes, facecolors, edgecolors, linewidths = [
273+
a.flatten()
274+
for a in [x, y, z, sizes, facecolors, edgecolors, linewidths]
275+
]
276+
277+
ax_ref = fig_ref.gca(projection='3d')
278+
sets = (np.unique(a) for a in [sizes, facecolors, edgecolors, linewidths])
279+
for s, fc, ec, lw in itertools.product(*sets):
280+
subset = (
281+
(sizes != s) |
282+
(facecolors != fc) |
283+
(edgecolors != ec) |
284+
(linewidths != lw)
285+
)
286+
subset = np.ma.masked_array(z, subset, dtype=float)
287+
288+
# When depth shading is disabled, the colors are passed through as
289+
# single-item lists; this triggers single path optimization. The
290+
# following reshaping is a hack to disable that, since the optimization
291+
# would not occur for the full scatter which has multiple colors.
292+
fc = np.repeat(fc, sum(~subset.mask))
293+
294+
ax_ref.scatter(x, y, subset, s=s, fc=fc, ec=ec, lw=lw, alpha=1,
295+
depthshade=depthshade)
296+
297+
ax_test = fig_test.gca(projection='3d')
298+
ax_test.scatter(x, y, z, s=sizes, fc=facecolors, ec=edgecolors,
299+
lw=linewidths, alpha=1, depthshade=depthshade)
278300

279301

280302
@pytest.mark.parametrize('azim', [-50, 130]) # yellow first, blue first

0 commit comments

Comments
 (0)