8000 QuadMesh point in poly by greglucas · Pull Request #22955 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content

QuadMesh point in poly #22955

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

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
46 changes: 40 additions & 6 deletions lib/matplotlib/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -2195,11 +2195,45 @@ def draw(self, renderer):
renderer.close_group(self.__class__.__name__)
self.stale = False

def contains(self, event):
# docstring inherited
Copy link
Member

Choose a reason for hiding this comment

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

is there a way to fast-path the often-used pcolormesh case of a rectilinear grid (lat and lon are each 1-d)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sort-of. I'm not sure it is doable in QuadMesh currently because it is so flexible with the mesh coordinates, so somehow we would need to store some information about what x/y grid was passed in. That case is already handled in PcolorImage though:

def get_cursor_data(self, event):
# docstring inherited
x, y = event.xdata, event.ydata
if (x < self._Ax[0] or x > self._Ax[-1] or
y < self._Ay[0] or y > self._Ay[-1]):
return None
j = np.searchsorted(self._Ax, x) - 1
i = np.searchsorted(self._Ay, y) - 1
try:
return self._A[i, j]
except IndexError:
return None

(also note that I still get quite good performance for 400x400 size meshes which is already pretty small boxes for investigating coordinate output)

Thinking about this more generally, I wonder if we could consolidate some of these pcolor-style plots into a more generic Collection that would defer choosing which one of PcolorImage/QuadMesh/PolyCollection to draw until the very end, but leaving all of the options available. For instance, if someone wants shading=gouraud we would choose QuadMesh for the rendering. I think this was sort of the idea behind pcolorfast() (?), but that will return different collections depending on what the input is, which may be somewhat confusing for modifying properties later?

Copy link
Member

Choose a reason for hiding this comment

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

I think in general unifying the three pcolor methods would be great - I am personally not a fan of different ones for different purposes; in general if there is a performance issue, one just rasterizes anyhow and performance is usually more than adequate.

x, y = event.xdata, event.ydata

p = self._coordinates
p_a = p[:-1, :-1, :]
p_b = p[:-1, 1:, :]
p_c = p[1:, 1:, :]
p_d = p[1:, :-1, :]
# (y - y0) (x1 - x0) - (x - x0) (y1 - y0)
def side_of_line(x, y, p0, p1):
"""
Return the side of the line the point (x, y) is on
left: >0
on: 0
right: <0
"""
return ((y - p0[..., 1]) * (p1[..., 0] - p0[..., 0])
- (x - p0[..., 0]) * (p1[..., 1] - p0[..., 1]))

# Winding number, can handle concave polys
# Algorithm from Dan Sunday
# https://web.archive.org/web/20130126163405/
# http://geomalgorithms.com/a03-_inclusion.html
winding_number = np.zeros(p_a.shape[:-1])
for (p0, p1) in zip([p_a, p_b, p_c, p_d], [p_b, p_c, p_d, p_a]):
winding_number += ((p0[..., 1] <= y)
& (p1[..., 1] > y) # upward crossing
& (side_of_line(x, y, p0, p1) > 0))

winding_number -= ((p0[..., 1] > y)
& (p1[..., 1] <= y) # downward crossing
& (side_of_line(x, y, p0, p1) < 0))

ind = np.nonzero((winding_number != 0).ravel())[0]
return len(ind) > 0, dict(ind=ind)

def get_cursor_data(self, event):
contained, info = self.contains(event)
if len(info["ind"]) == 1:
ind, = info["ind"]
array = self.get_array()
return array[ind] if array else None
else:
return None
if contained and len(self.get_array()):
return self.get_array()[info["ind"]]
return None
43 changes: 40 additions & 3 deletions lib/matplotlib/tests/test_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
import matplotlib.path as mpath
import matplotlib.transforms as mtransforms
from matplotlib.collections import (Collection, LineCollection,
EventCollection, PolyCollection)
EventCollection, PolyCollection,
QuadMesh)
from matplotlib.testing.decorators import check_figures_equal, image_comparison
from matplotlib._api.deprecation import MatplotlibDeprecationWarning

Expand Down Expand Up @@ -483,6 +484,44 @@ def test_picking():
assert_array_equal(indices['ind'], [0])


def test_quadmesh_contains():
n = 4
x = np.arange(n)
X = x[:, None] * x[None, :]

fig, ax = plt.subplots()
mesh = ax.pcolormesh(X)
mouse_event = SimpleNamespace(xdata=0, ydata=0)
found, indices = mesh.contains(mouse_event)
assert found
assert_array_equal(indices['ind'], [0])

mouse_event = SimpleNamespace(xdata=1.5, ydata=1.5)
found, indices = mesh.contains(mouse_event)
assert found
assert_array_equal(indices['ind'], [5])

# Test a concave polygon too, V-like shape
x = [[0, -1], [1, 0]]
y = [[0, 1], [1, -1]]
mesh = ax.pcolormesh(x, y, [[0]])
points = [(-0.5, 0.25, True), # left wing
(0, 0.25, False), # between the two wings
(0.5, 0.25, True), # right wing
(0, -0.25, True), # main body
]
for point in points:
x, y, expected = point
mouse_event = SimpleNamespace(xdata=x, ydata=y)
found, indices = mesh.contains(mouse_event)
assert found == expected

# Smoke test an empty array, get_array() == None
coll = QuadMesh(np.ones((3, 3, 2)))
found, indices = coll.contains(mouse_event)
assert not found


def test_linestyle_single_dashes():
plt.scatter([0, 1, 2], [0, 1, 2], linestyle=(0., [2., 2.]))
plt.draw()
Expand Down Expand Up @@ -749,8 +788,6 @@ def test_quadmesh_deprecated_signature(
fig_test, fig_ref, flat_ref, kwargs):
# test that the new and old quadmesh signature produce the same results
# remove when the old QuadMesh.__init__ signature expires (v3.5+2)
from matplotlib.collections import QuadMesh

x = [0, 1, 2, 3.]
y = [1, 2, 3.]
X, Y = np.meshgrid(x, y)
Expand Down
0