From 75858bd923ce31018b4437f849ece9eccaa6e597 Mon Sep 17 00:00:00 2001 From: Evans Castonguay Date: Sun, 31 May 2026 09:10:15 -0400 Subject: [PATCH] SVG: simplify large filled paths e.g. fill_between (#22803) --- lib/matplotlib/backends/backend_svg.py | 12 ++++++++++-- lib/matplotlib/tests/test_backend_svg.py | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_svg.py b/lib/matplotlib/backends/backend_svg.py index 24790356b9d7..eb61f5e89f4b 100644 --- a/lib/matplotlib/backends/backend_svg.py +++ b/lib/matplotlib/backends/backend_svg.py @@ -691,7 +691,13 @@ def draw_path(self, gc, path, transform, rgbFace=None): # docstring inherited trans_and_flip = self._make_flip_transform(transform) clip = (rgbFace is None and gc.get_hatch_path() is None) - simplify = path.should_simplify and clip + # Also simplify large filled paths (e.g. fill_between); see #22803. + # Lines already simplify via should_simplify; honor the rcParam and + # leave hatched paths exact. + simplify = ((path.should_simplify + or (mpl.rcParams['path.simplify'] + and len(path.vertices) >= 128)) + and gc.get_hatch_path() is None) path_data = self._convert_path( path, trans_and_flip, clip=clip, simplify=simplify, sketch=gc.get_sketch_params()) @@ -714,7 +720,9 @@ def draw_markers( path_data = self._convert_path( marker_path, marker_trans + Affine2D().scale(1.0, -1.0), - simplify=False) + simplify=(marker_path.should_simplify + or (mpl.rcParams['path.simplify'] + and len(marker_path.vertices) >= 128))) style = self._get_style_dict(gc, rgbFace) dictkey = (path_data, _generate_css(style)) oid = self._markers.get(dictkey) diff --git a/lib/matplotlib/tests/test_backend_svg.py b/lib/matplotlib/tests/test_backend_svg.py index 6b63990f7620..242b880c25f1 100644 --- a/lib/matplotlib/tests/test_backend_svg.py +++ b/lib/matplotlib/tests/test_backend_svg.py @@ -705,3 +705,25 @@ def test_svgid(): assert plt.rcParams['svg.id'] == svg_id assert tree.findall(f'.[@id="{svg_id}"]') + + +def test_fill_between_svg_simplified(): + # Regression test for #22803: the polygon produced by fill_between is cached + # in the SVG backend like a reusable marker and was written without path + # simplification, producing very large files. It should be simplified like any + # other large path, while staying vector (not rasterized). + import re + rng = np.random.default_rng(424242) + n = 20000 + x = np.arange(n) + y = rng.normal(size=(10, n)) + fig, ax = plt.subplots() + ax.fill_between(x, y.min(axis=0), y.max(axis=0)) + ax.set_xlim(0, n) + buf = BytesIO() + fig.savefig(buf, format="svg") + svg = buf.getvalue().decode("utf-8") + biggest = max(re.findall(r'\sd="([^"]*)"', svg, re.S), key=len) + n_verts = biggest.count("L") + biggest.count("M") + assert n_verts < n, f"fill path not simplified ({n_verts} vertices)" + assert "