Skip to content

Make Path3DCollection store indexed offset, and only apply z-ordered offset during draw #24491

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions lib/mpl_toolkits/mplot3d/art3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

import numpy as np

from contextlib import contextmanager

from matplotlib import (
artist, cbook, colors as mcolors, lines, text as mtext,
path as mpath)
Expand Down Expand Up @@ -629,10 +631,12 @@ def __init__(self, *args, zs=0, zdir='z', depthshade=True, **kwargs):
self._in_draw = False
super().__init__(*args, **kwargs)
self.set_3d_properties(zs, zdir)
self._offset_zordered = None

def draw(self, renderer):
with cbook._setattr_cm(self, _in_draw=True):
super().draw(renderer)
with self._use_zordered_offset():
with cbook._setattr_cm(self, _in_draw=True):
super().draw(renderer)

def set_sort_zpos(self, val):
"""Set the position to use for z-sorting."""
Expand Down Expand Up @@ -731,15 +735,32 @@ def do_3d_projection(self):
if len(self._linewidths3d) > 1:
self._linewidths = self._linewidths3d[z_markers_idx]

PathCollection.set_offsets(self, np.column_stack((vxs, vys)))

# Re-order items
vzs = vzs[z_markers_idx]
vxs = vxs[z_markers_idx]
vys = vys[z_markers_idx]

PathCollection.set_offsets(self, np.column_stack((vxs, vys)))
# Store ordered offset for drawing purpose
self._offset_zordered = np.column_stack((vxs, vys))

return np.min(vzs) if vzs.size else np.nan

@contextmanager
def _use_zordered_offset(self):
if self._offset_zordered is None:
# Do nothing
yield
else:
# Swap offset with z-ordered offset
old_offset = self._offsets
super().set_offsets(self._offset_zordered)
try:
yield
finally:
self._offsets = old_offset

def _maybe_depth_shade_and_sort_colors(self, color_array):
color_array = (
_zalpha(color_array, self._vzs)
Expand Down
38 changes: 38 additions & 0 deletions lib/mpl_toolkits/mplot3d/tests/test_art3d.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import matplotlib.pyplot as plt

from matplotlib.backend_bases import MouseEvent


def test_scatter_3d_projection_conservation():
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
# fix axes3d projection
ax.roll = 0
ax.elev = 0
ax.azim = -45
ax.stale = True

x = [0, 1, 2, 3, 4]
scatter_collection = ax.scatter(x, x, x)
fig.canvas.draw_idle()

# Get scatter location on canvas and freeze the data
scatter_offset = scatter_collection.get_offsets()
scatter_location = ax.transData.transform(scatter_offset)

# Yaw -44 and -46 are enough to produce two set of scatter
# with opposite z-order without moving points too far
for azim in (-44, -46):
ax.azim = azim
ax.stale = True
fig.canvas.draw_idle()

for i in range(5):
# Create a mouse event used to locate and to get index
# from each dots
event = MouseEvent("button_press_event", fig.canvas,
*scatter_location[i, :])
contains, ind = scatter_collection.contains(event)
assert contains is True
assert len(ind["ind"]) == 1
assert ind["ind"][0] == i