From bfde4897d9110d429b24479489ba112408d3dfc0 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Tue, 10 Oct 2023 00:30:28 -0500 Subject: [PATCH 1/4] Ensure valid path mangling for ContourLabeler --- lib/matplotlib/contour.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index dc5ed5d626bc..6a4b27035dc1 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -418,8 +418,13 @@ def interp_vec(x, xp, fp): return [np.interp(x, xp, col) for col in fp.T] new_code_blocks = [] if is_closed_path: if i0 != -1 and i1 != -1: - new_xy_blocks.extend([[(x1, y1)], cc_xys[i1:i0+1], [(x0, y0)]]) - new_code_blocks.extend([[Path.MOVETO], [Path.LINETO] * (i0 + 2 - i1)]) + # This is probably wrong in the case that the entire contour would + # be discarded, but ensures that a valid path is returned and is + # consistent with behavior of mpl <3.8 + points = cc_xys[i1:i0+1] + new_xy_blocks.extend([[(x1, y1)], points, [(x0, y0)]]) + nlines = len(points) + 1 + new_code_blocks.extend([[Path.MOVETO], [Path.LINETO] * nlines]) else: if i0 != -1: new_xy_blocks.extend([cc_xys[:i0 + 1], [(x0, y0)]]) From b2027c3fcab34933b40bd2f9227ddd1f2b50fd20 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Tue, 10 Oct 2023 13:52:29 -0500 Subject: [PATCH 2/4] Add test of clabel with large padding --- lib/matplotlib/tests/test_contour.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/matplotlib/tests/test_contour.py b/lib/matplotlib/tests/test_contour.py index c911d499ea96..b3b7f575b07a 100644 --- a/lib/matplotlib/tests/test_contour.py +++ b/lib/matplotlib/tests/test_contour.py @@ -338,6 +338,22 @@ def test_clabel_zorder(use_clabeltext, contour_zorder, clabel_zorder): assert clabel.get_zorder() == expected_clabel_zorder +def test_clabel_with_large_spacing(): + # When the inline spacing is large relative to the contour, it may cause the + # entire contour to be removed. In current implementation, one line segment is + # retained between the identified points. + # This behavior may be worth reconsidering, but check to be sure we do not produce + # an invalid path, which results in an error at clabel call time. + # see gh-27045 for more information + x = y = np.arange(-3.0, 3.01, 0.05) + X, Y = np.meshgrid(x, y) + Z = np.exp(-X**2 - Y**2) + + fig, ax = plt.subplots() + contourset = ax.contour(X, Y, Z, levels=[0.01, 0.2, .5, .8]) + ax.clabel(contourset, inline_spacing=100) + + # tol because ticks happen to fall on pixel boundaries so small # floating point changes in tick location flip which pixel gets # the tick. From f0a3a09ed935bf937251372e201d91b4b670118c Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 11 Oct 2023 12:25:35 -0500 Subject: [PATCH 3/4] Only rotate the subsection of the Path Closes #27062 The code for rotation had referred to the original path, when it should only be rotating the connected segment, which had already been extracted. --- lib/matplotlib/contour.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index 6a4b27035dc1..adb72ef46017 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -383,7 +383,7 @@ def _split_path_and_get_label_rotation(self, path, idx, screen_pos, lw, spacing= # If the path is closed, rotate it s.t. it starts at the label. is_closed_path = codes[stop - 1] == Path.CLOSEPOLY if is_closed_path: - cc_xys = np.concatenate([xys[idx:-1], xys[:idx+1]]) + cc_xys = np.concatenate([cc_xys[idx:-1], cc_xys[:idx+1]]) idx = 0 # Like np.interp, but additionally vectorized over fp. From 5cf3da9a1f4086ab6d9e24a0a591701326b36850 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Thu, 19 Oct 2023 19:00:20 -0500 Subject: [PATCH 4/4] Add test for disconnected paths, remove invalidated paths --- lib/matplotlib/contour.py | 5 ++++ .../contour_disconnected_segments.png | Bin 0 -> 9795 bytes lib/matplotlib/tests/test_contour.py | 23 ++++++++++++++++++ 3 files changed, 28 insertions(+) create mode 100644 lib/matplotlib/tests/baseline_images/test_contour/contour_disconnected_segments.png diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index adb72ef46017..ec343763cce0 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -350,6 +350,11 @@ def _split_path_and_get_label_rotation(self, path, idx, screen_pos, lw, spacing= taken into account when breaking the path, but not when computing the angle. """ if hasattr(self, "_old_style_split_collections"): + vis = False + for coll in self._old_style_split_collections: + vis |= coll.get_visible() + coll.remove() + self.set_visible(vis) del self._old_style_split_collections # Invalidate them. xys = path.vertices diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_disconnected_segments.png b/lib/matplotlib/tests/baseline_images/test_contour/contour_disconnected_segments.png new file mode 100644 index 0000000000000000000000000000000000000000..ceb700e09de290f6c07c280904551e2b25114382 GIT binary patch literal 9795 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