Skip to content

Commit 3d4e498

Browse files
committed
Don't clip clip paths to Figure bbox.
This kind of path clipping just cuts the path on the bounding box, which produces an invalid clip path, since it is no longer closed or complete. Fixes #20127.
1 parent 9177280 commit 3d4e498

File tree

2 files changed

+25
-5
lines changed

2 files changed

+25
-5
lines changed

lib/matplotlib/tests/test_artist.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import matplotlib.transforms as mtransforms
1313
import matplotlib.collections as mcollections
1414
import matplotlib.artist as martist
15-
from matplotlib.testing.decorators import image_comparison
15+
from matplotlib.testing.decorators import check_figures_equal, image_comparison
1616

1717

1818
def test_patch_transform_of_none():
@@ -121,6 +121,25 @@ def test_clipping():
121121
ax1.set_ylim([-3, 3])
122122

123123

124+
@check_figures_equal(extensions=['png'])
125+
def test_clipping_zoom(fig_test, fig_ref):
126+
# This test places the Axes and sets its limits such that the clip path is
127+
# outside the figure entirely. This should not break the clip path.
128+
ax_test = fig_test.add_axes([0, 0, 1, 1])
129+
l, = ax_test.plot([-3, 3], [-3, 3])
130+
# Explicit Path instead of a Rectangle uses clip path processing, instead
131+
# of a clip box optimization.
132+
p = mpath.Path([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]])
133+
p = mpatches.PathPatch(p, transform=ax_test.transData)
134+
l.set_clip_path(p)
135+
136+
ax_ref = fig_ref.add_axes([0, 0, 1, 1])
137+
ax_ref.plot([-3, 3], [-3, 3])
138+
139+
ax_ref.set(xlim=(0.5, 0.75), ylim=(0.5, 0.75))
140+
ax_test.set(xlim=(0.5, 0.75), ylim=(0.5, 0.75))
141+
142+
124143
def test_cull_markers():
125144
x = np.random.random(20000)
126145
y = np.random.random(20000)

src/_backend_agg.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,10 @@ bool RendererAgg::render_clippath(py::PathIterator &clippath,
134134
{
135135
typedef agg::conv_transform<py::PathIterator> transformed_path_t;
136136
typedef PathNanRemover<transformed_path_t> nan_removed_t;
137-
typedef PathClipper<nan_removed_t> clipped_t;
138-
typedef PathSnapper<clipped_t> snapped_t;
137+
/* Unlike normal Paths, the clip path cannot be clipped to the Figure bbox,
138+
* because it needs to remain a complete closed path, so there is no
139+
* PathClipper<nan_removed_t> step. */
140+
typedef PathSnapper<nan_removed_t> snapped_t;
139141
typedef PathSimplifier<snapped_t> simplify_t;
140142
typedef agg::conv_curve<simplify_t> curve_t;
141143

@@ -151,8 +153,7 @@ bool RendererAgg::render_clippath(py::PathIterator &clippath,
151153
rendererBaseAlphaMask.clear(agg::gray8(0, 0));
152154
transformed_path_t transformed_clippath(clippath, trans);
153155
nan_removed_t nan_removed_clippath(transformed_clippath, true, clippath.has_curves());
154-
clipped_t clipped_clippath(nan_removed_clippath, !clippath.has_curves(), width, height);
155-
snapped_t snapped_clippath(clipped_clippath, snap_mode, clippath.total_vertices(), 0.0);
156+
snapped_t snapped_clippath(nan_removed_clippath, snap_mode, clippath.total_vertices(), 0.0);
156157
simplify_t simplified_clippath(snapped_clippath,
157158
clippath.should_simplify() && !clippath.has_curves(),
158159
clippath.simplify_threshold());

0 commit comments

Comments
 (0)