GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Enable choosing the even-odd rule for filling shapes by ayshih · Pull Request #32253 · matplotlib/matplotlib

Conversation

@ayshih

@ayshih ayshih commented Aug 28, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

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
agg

Cairo
cairo

SVG
Figure_1

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.

All reactions
@github-actions github-actions Bot added backend: pgf backend: agg backend: ps backend: pdf backend: svg backend: cairo topic: canvas and figure manager labels Aug 28, 2026
@ayshih
ayshih force-pushed the fill_rule branch from d61a2a7 to 9639839 Compare August 28, 2026 15:57
@github-actions github-actions Bot added the Documentation: API files in lib/ and doc/api label Aug 28, 2026
@ayshih
ayshih force-pushed the fill_rule branch 2 times, most recently from 8d27fa9 to dafc1c5 Compare September 4, 2026 03:44
@github-actions github-actions Bot added the Documentation: tutorials files in galleries/tutorials label Sep 4, 2026
@ayshih
ayshih force-pushed the fill_rule branch 2 times, most recently from 587d431 to 01dafac Compare September 4, 2026 14:12
@github-actions github-actions Bot added the Documentation: examples files in galleries/examples label Sep 4, 2026
@ayshih
ayshih force-pushed the fill_rule branch from 01dafac to 2cd422b Compare September 4, 2026 14:39
@ayshih
ayshih marked this pull request as ready for review September 4, 2026 14:41
@ayshih
ayshih force-pushed the fill_rule branch from 2cd422b to be8866c Compare September 4, 2026 15:18
@ayshih

ayshih commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

This PR is ready for review! It includes a gallery example showing the difference between the two fill rules:
sphx_glr_fill_rule_demo_001

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@greglucas

greglucas commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

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.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ayshih

ayshih commented Sep 8, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

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.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

iccir
iccir reviewed Sep 9, 2026
View reviewed changes

@iccir iccir left a comment

Copy link
Copy Markdown
Contributor

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.

All reactions
Comment thread lib/matplotlib/patches.pyi Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/matplotlib/backends/backend_svg.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

@ayshih
Enable choosing between two fill rules
8f2d344
@ayshih
ayshih force-pushed the fill_rule branch from be8866c to 8f2d344 Compare September 9, 2026 13:25
iccir
iccir approved these changes Sep 9, 2026
View reviewed changes

@iccir iccir left a comment

Copy link
Copy Markdown
Contributor

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.

All reactions
@greglucas greglucas mentioned this pull request Sep 10, 2026
Behavior of contains() inconsistent with fill in overlapping paths #32329
Open
@greglucas

greglucas commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

My inclination is that fixing contains() should be deferred to a future PR.

I agree, I opened #32329 from that comment.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

greglucas
greglucas approved these changes Sep 10, 2026
View reviewed changes
@greglucas
greglucas merged commit f8791a8 into matplotlib:main Sep 10, 2026
41 of 42 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@QuLogic QuLogic added this to the v3.12.0 milestone Sep 10, 2026
@ayshih
ayshih deleted the fill_rule branch September 10, 2026 13:30
@maaz7409 maaz7409 mentioned this pull request Nov 9, 2026
TYP,BLD: Fix backend_pdf stub errors and install it #32586
Merged
1 task done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@iccir iccir iccir approved these changes
@greglucas greglucas greglucas approved these changes

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

backend: agg backend: cairo backend: pdf backend: pgf backend: ps backend: svg Documentation: API files in lib/ and doc/api Documentation: examples files in galleries/examples Documentation: tutorials files in galleries/tutorials topic: canvas and figure manager

Projects

None yet

Milestone

v3.12.0

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

4 participants

@ayshih @greglucas @iccir @QuLogic
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.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.