8000 Fix clipping of markers in PDF backend. · matplotlib/matplotlib@ed79844 · GitHub
[go: up one dir, main page]

Skip to content

Commit ed79844

Browse files
committed
Fix clipping of markers in PDF backend.
The bbox only contains the points of the marker, but the line will extend outside by half the line width, unless it's mitered. The PDF miter limit is 10, so pad by 5*line width (half the miter length). This fixes corners on 'v', '^', '<', '>', 'p', 'h', 'H', 'D', 'd', 'X'. Fixes #9829.
1 parent 52e04f5 commit ed79844

File tree

3 files changed

+49
-1
lines changed

3 files changed

+49
-1
lines changed

lib/matplotlib/backends/backend_pdf.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1546,7 +1546,14 @@ def markerObject(self, path, trans, fill, stroke, lw, joinstyle,
15461546
def writeMarkers(self):
15471547
for ((pathops, fill, stroke, joinstyle, capstyle),
15481548
(name, ob, bbox, lw)) in self.markers.items():
1549-
bbox = bbox.padded(lw * 0.5)
1549+
# bbox wraps the exact limits of the control points, so half a line
1550+
# will appear outside it. If the join style is miter and the line
1551+
# is not parallel to the edge, then the line will extend even
1552+
# further. From the PDF specification, Section 8.4.3.5, the miter
1553+
# limit is miterLength / lineWidth and from Table 52, the default
1554+
# is 10. With half the miter length outside, that works out to the
1555+
# following padding:
1556+
bbox = bbox.padded(lw * 5)
15501557
self.beginStream(
15511558
ob.id, None,
15521559
{'Type': Name('XObject'), 'Subtype': Name('Form'),

lib/matplotlib/tests/test_marker.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,44 @@ def draw_ref_marker(y, style, size):
133133

134134
ax_test.set(xlim=(-0.5, 1.5), ylim=(-0.5, 1.5))
135135
ax_ref.set(xlim=(-0.5, 1.5), ylim=(-0.5, 1.5))
136+
137+
138+
@check_figures_equal()
139+
def test_marker_clipping(fig_ref, fig_test):
140+
# Plotting multiple markers can trigger different optimized paths in
141+
# backends, so compare single markers vs multiple to ensure they are
142+
# clipped correctly.
143+
marker_count = len(markers.MarkerStyle.markers)
144+
marker_size = 50
145+
ncol = 7
146+
nrow = marker_count // ncol + 1
147+
148+
width = 2 * marker_size * ncol
149+
height = 2 * marker_size * nrow * 2
150+
fig_ref.set_size_inches((width / fig_ref.dpi, height / fig_ref.dpi))
151+
ax_ref = fig_ref.add_axes([0, 0, 1, 1])
152+
fig_test.set_size_inches((width / fig_test.dpi, height / fig_ref.dpi))
153+
ax_test = fig_test.add_axes([0, 0, 1, 1])
154+
155+
for i, marker in enumerate(markers.MarkerStyle.markers):
156+
x = i % ncol
157+
y = i // ncol * 2
158+
159+
# Singular markers per call.
160+
ax_ref.plot([x, x], [y, y + 1], c='k', linestyle='-', lw=3)
161+
ax_ref.plot(x, y, c='k',
162+
marker=marker, markersize=marker_size, markeredgewidth=10,
163+
fillstyle='full', markerfacecolor='white')
164+
ax_ref.plot(x, y + 1, c='k',
165+
marker=marker, markersize=marker_size, markeredgewidth=10,
166+
fillstyle='full', markerfacecolor='white')
167+
168+
# Multiple markers in a single call.
169+
ax_test.plot([x, x], [y, y + 1], c='k', linestyle='-', lw=3,
170+
marker=marker, markersize=marker_size, markeredgewidth=10,
171+
fillstyle='full', markerfacecolor='white')
172+
173+
ax_ref.set(xlim=(-0.5, ncol), ylim=(-0.5, 2 * nrow))
174+
ax_test.set(xlim=(-0.5, ncol), ylim=(-0.5, 2 * nrow))
175+
ax_ref.axis('off')
176+
ax_test.axis('off')
Binary file not shown.

0 commit comments

Comments
 (0)
0