Skip to content

Simplify fill_between paths in SVG and PDF backends#31359

Open
cinocefalia wants to merge 1 commit into
matplotlib:mainfrom
cinocefalia:fillbetween-simplify-clean
Open

Simplify fill_between paths in SVG and PDF backends#31359
cinocefalia wants to merge 1 commit into
matplotlib:mainfrom
cinocefalia:fillbetween-simplify-clean

Conversation

@cinocefalia
Copy link
Copy Markdown

@cinocefalia cinocefalia commented Mar 24, 2026

PR summary

Suggestion for closing #22803.

This PR reduces SVG/PDF output size for fill_between by allowing simplification to be applied to FillBetweenPolyCollection paths when path.simplify is enabled.

Why

plot() already benefits from path simplification because Line2D paths are passed to the backends with the relevant simplification behavior already in place. In contrast, fill_between() produces a FillBetweenPolyCollection, and the resulting collection paths were not consistently reaching the vector backends in a form that allowed the same simplification to happen. its paths are closed polygons (they contain CLOSEPATH codes), which disables should_simplify
by design in Path._update_values. Additionally, filled paths always have clip=False in draw_path, so the existing simplification gate never fired.

In practice, this meant that increasing path.simplify_threshold greatly reduced vector output size for line plots, but often had little or no effect for fill_between().

What does this PR change

  • FillBetweenPolyCollection.set_verts tags each generated path with a _fill_between_simplify attribute.
  • In RendererSVG.draw_path and RendererPDF.draw_path, paths carrying that attribute (and without a hatch) are simplified by checking rcParams["path.simplify"] and rcParams["path.simplify_threshold"] directly, bypassing the should_simplifyproperty which is alwaysFalse` for closed polygon paths.
  • Hatched fill_between paths are explicitly excluded: in SVG, the hatch pattern is clipped to the fill polygon boundary, so simplifying that boundary would change the visible output.
  • No changes to path collection optimization or to unmarked paths - existing behavior is fully preserved.

What this approach solves:

For simplifiable fill_between paths, exported SVG/PDF files are reduced substantially. In the strongest benchmarked case (interpolate_cross), output size dropped by about 79.7% abd 75.5% for SVG and PDF, respectively. More fragmented cases still showed reductions, though with smaller gains and more mixed timing results.

Implementation notes:

-the change is intentionally narrow in scoppe
-simplification is only enabled for paths explicitly marked as simplifiable, rather than broadening simplification for all collections
-the PDF and SVG backends keep their existing behavior for unmarked paths
-the tests focus on output-size reduction rather than exact rendering bytes

Opt-in

Simplification only fires when the user has already set rcParams["path.simplify"] = True and rcParams["path.simplify_threshold"] > 0. This matches how simplification works for line plots in the same backends.

Scenario Backend thr=0.0 thr=1.0 Reduction
interpolate_cross SVG 73,531 14,943 79.7%
interpolate_cross PDF 30,102 7,382 75.5%
where_random SVG 155,694 104,644 32.8%
where_random PDF 35,829 20,425 43.0%
multi_regions SVG 392,895 293,482 25.3%
multi_regions PDF 58,940 33,757 42.7%

The old numbers (99.6-99.7% for interpolate_cross) were from the previous approach with a much larger dataset or different test setup. The real numbers here are more modest but still meaningful (75-80% for the smooth curve case, 25-43% for the fragmented cases).

Test

lib/matplotlib/tests/test_fill_between_simplify.py verifies that SVG and PDF output size is smaller at simplify_threshold=1.0 than at 0.0, for both single and multi-region fill_between scenarios.

Tested on 2000-point datasets. Reduction magnitude depends heavily on data density and threshold value - smooth curves (e.g. interpolate_cross) see near-complete reduction at threshold=1.0, while fragmented where masks
see smaller gains. The regression tests verify that reduction occurs in both backends across representative scenarios.

AI Disclosure

AI was used to assist with debugging environment issues, crafting the new test and refining the PR description, all the rest being manually tested.

Further explanation on the comments.

  • "closes #22803"
  • new and changed code is tested
  • [N/A] Plotting related features are demonstrated
  • [N/A ] New Features and API Changes are noted
  • [N/A] Documentation complies with general and docstring guidelines

Add path simplification for fill_between-generated paths in the
PDF and SVG vector backends. Paths tagged with `_fill_between_simplify`
are pre-cleaned before encoding, reducing file size for dense datasets.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cinocefalia cinocefalia force-pushed the fillbetween-simplify-clean branch from 9b38c23 to bc8a682 Compare May 19, 2026 20:38
@cinocefalia
Copy link
Copy Markdown
Author

cinocefalia commented May 19, 2026

Regarding my previous comment here, some changes were made

Forcing should_do_optimization=True to work around it caused hatching image test failures (in SVG, the hatch pattern is clipped to the fill polygon boundary, so simplifying that boundary changes the visible output).

The current approach sidesteps all of that by handling simplification directly in draw_path, which runs for every path regardless of the optimization heuristic.

Comment approach PR
Path attribute path.should_simplify = True path._fill_between_simplify = True
Simplify decision Inside _convert_path Inside draw_path
_convert_path Modified Untouched
pathCollectionObject Applies simplification Untouched
should_do_optimization Forced True Removed (caused hatching failures)
writePath Gates on should_simplify Receives explicit simplify from draw_path

@cinocefalia cinocefalia marked this pull request as ready for review May 20, 2026 02:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant