Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit cd46813

Browse files
tacaswellmeeseeksmachine
authored andcommitted
Backport PR #27045: Ensure valid path mangling for ContourLabeler
1 parent 9e4ec86 commit cd46813

File tree

3 files changed

+52
-3
lines changed

3 files changed

+52
-3
lines changed

lib/matplotlib/contour.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,11 @@ def _split_path_and_get_label_rotation(self, path, idx, screen_pos, lw, spacing=
350350
taken into account when breaking the path, but not when computing the angle.
351351
"""
352352
if hasattr(self, "_old_style_split_collections"):
353+
vis = False
354+
for coll in self._old_style_split_collections:
355+
vis |= coll.get_visible()
356+
coll.remove()
357+
self.set_visible(vis)
353358
del self._old_style_split_collections # Invalidate them.
354359

355360
xys = path.vertices
@@ -383,7 +388,7 @@ def _split_path_and_get_label_rotation(self, path, idx, screen_pos, lw, spacing=
383388
# If the path is closed, rotate it s.t. it starts at the label.
384389
is_closed_path = codes[stop - 1] == Path.CLOSEPOLY
385390
if is_closed_path:
386-
cc_xys = np.concatenate([xys[idx:-1], xys[:idx+1]])
391+
cc_xys = np.concatenate([cc_xys[idx:-1], cc_xys[:idx+1]])
387392
idx = 0
388393

389394
# Like np.interp, but additionally vectorized over fp.
@@ -418,8 +423,13 @@ def interp_vec(x, xp, fp): return [np.interp(x, xp, col) for col in fp.T]
418423
new_code_blocks = []
419424
if is_closed_path:
420425
if i0 != -1 and i1 != -1:
421-
new_xy_blocks.extend([[(x1, y1)], cc_xys[i1:i0+1], [(x0, y0)]])
422-
new_code_blocks.extend([[Path.MOVETO], [Path.LINETO] * (i0 + 2 - i1)])
426+
# This is probably wrong in the case that the entire contour would
427+
# be discarded, but ensures that a valid path is returned and is
428+
# consistent with behavior of mpl <3.8
429+
points = cc_xys[i1:i0+1]
430+
new_xy_blocks.extend([[(x1, y1)], points, [(x0, y0)]])
431+
nlines = len(points) + 1
432+
new_code_blocks.extend([[Path.MOVETO], [Path.LINETO] * nlines])
423433
else:
424434
if i0 != -1:
425435
new_xy_blocks.extend([cc_xys[:i0 + 1], [(x0, y0)]])

lib/matplotlib/tests/test_contour.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,31 @@ def test_contour_manual_labels(split_collections):
116116

117117
plt.figure(figsize=(6, 2), dpi=200)
118118
cs = plt.contour(x, y, z)
119+
120+
_maybe_split_collections(split_collections)
121+
119122
pts = np.array([(1.0, 3.0), (1.0, 4.4), (1.0, 6.0)])
120123
plt.clabel(cs, manual=pts)
121124
pts = np.array([(2.0, 3.0), (2.0, 4.4), (2.0, 6.0)])
122125
plt.clabel(cs, manual=pts, fontsize='small', colors=('r', 'g'))
123126

127+
128+
@pytest.mark.parametrize("split_collections", [False, True])
129+
@image_comparison(['contour_disconnected_segments'],
130+
remove_text=True, style='mpl20', extensions=['png'])
131+
def test_contour_label_with_disconnected_segments(split_collections):
132+
x, y = np.mgrid[-1:1:21j, -1:1:21j]
133+
z = 1 / np.sqrt(0.01 + (x + 0.3) ** 2 + y ** 2)
134+
z += 1 / np.sqrt(0.01 + (x - 0.3) ** 2 + y ** 2)
135+
136+
plt.figure()
137+
cs = plt.contour(x, y, z, levels=[7])
138+
139+
# Adding labels should invalidate the old style
140+
_maybe_split_collections(split_collections)
141+
142+
cs.clabel(manual=[(0.2, 0.1)])
143+
124144
_maybe_split_collections(split_collections)
125145

126146

@@ -232,6 +252,9 @@ def test_labels(split_collections):
232252
disp_units = [(216, 177), (359, 290), (521, 406)]
233253
data_units = [(-2, .5), (0, -1.5), (2.8, 1)]
234254

255+
# Adding labels should invalidate the old style
256+
_maybe_split_collections(split_collections)
257+
235258
CS.clabel()
236259

237260
for x, y in data_units:
@@ -338,6 +361,22 @@ def test_clabel_zorder(use_clabeltext, contour_zorder, clabel_zorder):
338361
assert clabel.get_zorder() == expected_clabel_zorder
339362

340363

364+
def test_clabel_with_large_spacing():
365+
# When the inline spacing is large relative to the contour, it may cause the
366+
# entire contour to be removed. In current implementation, one line segment is
367+
# retained between the identified points.
368+
# This behavior may be worth reconsidering, but check to be sure we do not produce
369+
# an invalid path, which results in an error at clabel call time.
370+
# see gh-27045 for more information
371+
x = y = np.arange(-3.0, 3.01, 0.05)
372+
X, Y = np.meshgrid(x, y)
373+
Z = np.exp(-X**2 - Y**2)
374+
375+
fig, ax = plt.subplots()
376+
contourset = ax.contour(X, Y, Z, levels=[0.01, 0.2, .5, .8])
377+
ax.clabel(contourset, inline_spacing=100)
378+
379+
341380
# tol because ticks happen to fall on pixel boundaries so small
342381
# floating point changes in tick location flip which pixel gets
343382
# the tick.

0 commit comments

Comments
 (0)