GeezerVison is showing github.com — Enable choosing the even-odd rule for filling shapes by ayshih · Pull Request #32253 · matplotlib/matplotlib
Conversation
ayshih
commented
Aug 28, 2026
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
PR summary
This PR enables choosing the even-odd rule for filling a shape, as an alternative to the existing behavior of the non-zero winding rule. The choice of fill rule is implemented as an Patch property, and defaults to the non-zero winding rule. All backends are supported.
Examples
Agg

Cairo

SVG

PDF
Figure_1.pdf
PGF
Figure_1.pgf.pdf
PS
Figure_1.ps.pdf
Generating script
import matplotlib
#matplotlib.use('TkCairo')
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import PathPatch, Polygon
from matplotlib.path import Path
deg = np.arange(6) * 144
x = np.sin(deg * np.pi / 180)
y = np.cos(deg * np.pi / 180)
star = np.stack([x, y], axis=1)
square_vertices = np.array([[-1, -1], [-1, 1], [1, 1], [1, -1], [-1, -1]])
square_codes = [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO]
fig, axs = plt.subplots(1, 2, figsize=(4, 5))
for ax, fill_rule in zip(axs, ['nonzero', 'evenodd']):
stroked_star = Polygon(star + [0, 4], closed=False, ec='b', lw=5, ls=(0, (5, 1)),
fc='r', hatch='xx', fill_rule=fill_rule)
ax.add_patch(stroked_star)
nonstroked_star = Polygon(star + [0, 2], closed=False, ec='none',
fc='r', hatch='xx', fill_rule=fill_rule)
ax.add_patch(nonstroked_star)
squares = Path(np.vstack([square_vertices * 0.9,
square_vertices / 3 + [0, 0.5],
square_vertices / 3 + [0.3, 0],
(square_vertices / 3)[::-1, :] + [0, -0.5]]),
square_codes * 4)
ax.add_patch(PathPatch(squares, fc='g', ec='m', fill_rule=fill_rule))
ax.set_xlim(-1, 1)
ax.set_ylim(-1, 5.1)
ax.set_aspect('equal')
ax.set_axis_off()
ax.set_title(f'fill_rule={fill_rule}')
plt.show()
AI Disclosure
No AI was used
PR quality check
- Use an expressive title, e.g. "Fix title font property precedence"
- New and changed code is tested
- Plotting related features are demonstrated in an example
- New features and API changes have release notes
- Documentation complies with general and docstring guidelines
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
This PR is ready for review! It includes a gallery example showing the difference between the two fill rules:

|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
How do the two cases behave with contains() and mouse checking on the various artists? Does the hover work only over the filled areas or is it everything within any enclosing line? I think I'd expect it to be just the filled portion, but I'm also not entirely clear whether that is always desired. It might be nice to add some of the mouse event contains checks as tests for this as well to verify the filling assertions other than with just the images.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
ayshih
commented
Sep 8, 2026
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
How do the two cases behave with contains() and mouse checking on the various artists?
I've investigated this, and amusingly enough, contains() is not fully consistent with either fill rule. The current logic (in this function) is essentially (and possibly unintentionally) consistent with the even-odd rule on any individual subpath (and if the subpath does not self-intersect, the non-zero winding rule is the same). However, if there are multiple subpaths, a point is considered "inside" the combined path if it is inside at least one subpath, even if the combined effect of all subpaths mean that the point should actually be "outside". This means:
- If all subpaths do not self-intersect and do not intersect with each other,
contains() is consistent with both the non-zero winding rule and the even-odd rule (that is, both rules have the same result)
- If there is a single subpath that self-intersects,
contains() is consistent with the even-odd rule
- If there are multiple subpaths that do not self-intersect but do intersect with each other, and all subpaths are the same orientation,
contains() is consistent with the non-zero winding rule
- If none of the above,
contains() gives a "wrong" result: it is not consistent with either rule
My inclination is that fixing contains() should be deferred to a future PR.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additional comments: I feel that there may be lurking bugs in GraphicsContextBase.restore() where we call restore() on a wrapped Cairo context, but we never actually restore the associated properties. I need to deepen my understanding of GraphicsContextBase, however.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The included changes look good. I'm not sure if additional changes are needed or if there are edge cases.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
My inclination is that fixing contains() should be deferred to a future PR.
I agree, I opened #32329 from that comment.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Reviewers
iccir
iccir approved these changes
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Development
Successfully merging this pull request may close these issues.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
|