Skip to content

Add consistency check for PathCollection sizes and edgecolors (#28833) #30138

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
54 changes: 54 additions & 0 deletions lib/matplotlib/collections.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""
Classes for the efficient drawing of large collections of objects that
share most properties, e.g., a large number of line segments or
Expand Down Expand Up @@ -1132,6 +1132,45 @@
def get_paths(self):
return self._paths

def check_consistency(self):
"""
Emit warnings if the lengths of certain properties do not match
the number of paths.

This method checks whether the lengths of `sizes` and `edgecolors`
are either 1 or equal to the number of paths in the collection.
If not, a warning is issued.

This is useful for identifying potential bugs or silent mismatches
in collection properties when plotting, especially when properties
are expected to be applied per-path.

Notes
-----
- This method does not raise an error, only a warning.
- It is recommended to call this at draw-time to ensure properties
have been fully set.

Warnings
--------
UserWarning
If the number of sizes or edgecolors does not match the number
of paths and is not a singleton (length 1).

Examples
--------
>>> paths = [Path(...), Path(...), Path(...)]
>>> pc = PathCollection(paths, sizes=[10, 20]) # 3 paths, 2 sizes
>>> pc.check_consistency()
UserWarning: Number of sizes does not match number of paths.
"""
n_paths = len(self.get_paths())
if self._sizes is not None and len(self._sizes) not in (1, n_paths):
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if self._sizes is not None and len(self._sizes) not in (1, n_paths):
if self._sizes.size not in (0, 1, n_paths):

Looking at the set_sizes method, we always end up with an array here, though it may be size zero (if None was passed). I think this is the cause of at least some of the failing tests (any warnings are translated into errors in the tests).

warnings.warn("Number of sizes does not match number of paths.")
if self._edgecolors is not None and len(self._edgecolors) not in (1, n_paths):
warnings.warn("Number of edgecolors does not match number of paths.")


def legend_elements(self, prop="colors", num="auto",
fmt=None, func=lambda x: x, **kwargs):
"""
Expand Down Expand Up @@ -1272,6 +1311,21 @@

return handles, labels

def draw(self, renderer):
"""
Draw the collection using the given renderer.

This override adds consistency checks before delegating
to the base implementation.

Parameters
----------
renderer : RendererBase instance
The renderer to use for drawing.
"""
self.check_consistency()
return super().draw(renderer)


class PolyCollection(_CollectionWithSizes):

Expand Down
1 change: 1 addition & 0 deletions lib/matplotlib/collections.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class PathCollection(_CollectionWithSizes):
def __init__(
self, paths: Sequence[Path], sizes: ArrayLike | None = ..., **kwargs
) -> None: ...
def check_consistency(self) -> None: ...
def set_paths(self, paths: Sequence[Path]) -> None: ...
def get_paths(self) -> Sequence[Path]: ...
def legend_elements(
Expand Down
13 changes: 12 additions & 1 deletion lib/matplotlib/tests/test_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@
import matplotlib.path as mpath
import matplotlib.transforms as mtransforms
from matplotlib.collections import (Collection, LineCollection,
EventCollection, PolyCollection)
EventCollection, PolyCollection, PathCollection)
from matplotlib.collections import FillBetweenPolyCollection
from matplotlib.testing.decorators import check_figures_equal, image_comparison
from matplotlib.path import Path


@pytest.fixture(params=["pcolormesh", "pcolor"])
Expand Down Expand Up @@ -1533,3 +1534,13 @@ def mock_draw_path_collection(self, gc, master_transform, paths, all_transforms,
ax.add_collection(coll)

plt.draw()


def test_pathcollection_size_mismatch_warning():

paths = [Path([(0, 0), (1, 0), (0.5, 1)]) for _ in range(5)]
fig, ax = plt.subplots()
with pytest.warns(UserWarning, match="Number of sizes does not match"):
pc = PathCollection(paths, sizes=[10, 20])
ax.add_collection(pc)
fig.canvas.draw()
Loading