From a53d4f1a363890ea7685dad6cff364f49768b67c Mon Sep 17 00:00:00 2001 From: Ruth Comer Date: Tue, 15 Apr 2025 19:05:14 +0100 Subject: [PATCH 1/3] Handle MOVETO's in Path.interpolated --- lib/matplotlib/path.py | 18 ++++++++++++++++-- lib/matplotlib/tests/test_path.py | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/path.py b/lib/matplotlib/path.py index 4784410f0f5e..c7b4db0f6f74 100644 --- a/lib/matplotlib/path.py +++ b/lib/matplotlib/path.py @@ -667,13 +667,27 @@ def intersects_bbox(self, bbox, filled=True): def interpolated(self, steps): """ - Return a new path resampled to length N x *steps*. + Return a new path with each segment divided into *steps* parts. - Codes other than `LINETO` are not handled correctly. + Codes other than `LINETO` and `MOVETO` are not handled correctly. + + Parameters + ---------- + steps : int + The number of segments in the new path for each in the original. + + Returns + ------- + Path + The interpolated path. """ if steps == 1: return self + if self.codes is not None and self.MOVETO in self.codes[1:]: + return self.make_compound_path( + *(p.interpolated(steps) for p in self._iter_connected_components())) + vertices = simple_linear_interpolation(self.vertices, steps) codes = self.codes if codes is not None: diff --git a/lib/matplotlib/tests/test_path.py b/lib/matplotlib/tests/test_path.py index 5424160dad93..82a36902b489 100644 --- a/lib/matplotlib/tests/test_path.py +++ b/lib/matplotlib/tests/test_path.py @@ -541,3 +541,21 @@ def test_cleanup_closepoly(): cleaned = p.cleaned(remove_nans=True) assert len(cleaned) == 1 assert cleaned.codes[0] == Path.STOP + + +def test_interpolated_moveto(): + # Initial path has two subpaths with two LINETOs each + vertices = np.array([[0, 0], + [0, 1], + [1, 2], + [4, 4], + [4, 5], + [5, 5]]) + codes = [Path.MOVETO, Path.LINETO, Path.LINETO] * 2 + + path = Path(vertices, codes) + result = path.interpolated(3) + + # Result should have two subpaths with six LINETOs each + expected_subpath_codes = [Path.MOVETO] + [Path.LINETO] * 6 + np.testing.assert_array_equal(result.codes, expected_subpath_codes * 2) From 4c4357075e4a46a77d603341fd2a92fcf71d4afd Mon Sep 17 00:00:00 2001 From: Ruth Comer Date: Wed, 16 Apr 2025 09:49:05 +0100 Subject: [PATCH 2/3] Handle CLOSEPOLY's in Path.interpolated --- lib/matplotlib/path.py | 11 ++++-- lib/matplotlib/tests/test_path.py | 58 +++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/path.py b/lib/matplotlib/path.py index c7b4db0f6f74..b355849327f5 100644 --- a/lib/matplotlib/path.py +++ b/lib/matplotlib/path.py @@ -669,7 +669,7 @@ def interpolated(self, steps): """ Return a new path with each segment divided into *steps* parts. - Codes other than `LINETO` and `MOVETO` are not handled correctly. + Codes other than `LINETO`, `MOVETO`, and `CLOSEPOLY` are not handled correctly. Parameters ---------- @@ -688,7 +688,14 @@ def interpolated(self, steps): return self.make_compound_path( *(p.interpolated(steps) for p in self._iter_connected_components())) - vertices = simple_linear_interpolation(self.vertices, steps) + if self.codes is not None and self.CLOSEPOLY in self.codes and not np.all( + self.vertices[self.codes == self.CLOSEPOLY] == self.vertices[0]): + vertices = self.vertices.copy() + vertices[self.codes == self.CLOSEPOLY] = vertices[0] + else: + vertices = self.vertices + + vertices = simple_linear_interpolation(vertices, steps) codes = self.codes if codes is not None: new_codes = np.full((len(codes) - 1) * steps + 1, Path.LINETO, diff --git a/lib/matplotlib/tests/test_path.py b/lib/matplotlib/tests/test_path.py index 82a36902b489..28fc8cfbe891 100644 --- a/lib/matplotlib/tests/test_path.py +++ b/lib/matplotlib/tests/test_path.py @@ -559,3 +559,61 @@ def test_interpolated_moveto(): # Result should have two subpaths with six LINETOs each expected_subpath_codes = [Path.MOVETO] + [Path.LINETO] * 6 np.testing.assert_array_equal(result.codes, expected_subpath_codes * 2) + + +def test_interpolated_closepoly(): + codes = [Path.MOVETO] + [Path.LINETO]*2 + [Path.CLOSEPOLY] + vertices = [(4, 3), (5, 4), (5, 3), (0, 0)] + + path = Path(vertices, codes) + result = path.interpolated(2) + + expected_vertices = np.array([[4, 3], + [4.5, 3.5], + [5, 4], + [5, 3.5], + [5, 3], + [4.5, 3], + [4, 3]]) + expected_codes = [Path.MOVETO] + [Path.LINETO]*5 + [Path.CLOSEPOLY] + + np.testing.assert_allclose(result.vertices, expected_vertices) + np.testing.assert_array_equal(result.codes, expected_codes) + + # Usually closepoly is the last vertex but does not have to be. + codes += [Path.LINETO] + vertices += [(2, 1)] + + path = Path(vertices, codes) + result = path.interpolated(2) + + extra_expected_vertices = np.array([[3, 2], + [2, 1]]) + expected_vertices = np.concatenate([expected_vertices, extra_expected_vertices]) + + expected_codes += [Path.LINETO] * 2 + + np.testing.assert_allclose(result.vertices, expected_vertices) + np.testing.assert_array_equal(result.codes, expected_codes) + + +def test_interpolated_moveto_closepoly(): + # Initial path has two closed subpaths + codes = ([Path.MOVETO] + [Path.LINETO]*2 + [Path.CLOSEPOLY]) * 2 + vertices = [(4, 3), (5, 4), (5, 3), (0, 0), (8, 6), (10, 8), (10, 6), (0, 0)] + + path = Path(vertices, codes) + result = path.interpolated(2) + + expected_vertices1 = np.array([[4, 3], + [4.5, 3.5], + [5, 4], + [5, 3.5], + [5, 3], + [4.5, 3], + [4, 3]]) + expected_vertices = np.concatenate([expected_vertices1, expected_vertices1 * 2]) + expected_codes = ([Path.MOVETO] + [Path.LINETO]*5 + [Path.CLOSEPOLY]) * 2 + + np.testing.assert_allclose(result.vertices, expected_vertices) + np.testing.assert_array_equal(result.codes, expected_codes) From f24001aea8df011ecf36a393febfa5a19539239a Mon Sep 17 00:00:00 2001 From: Ruth Comer Date: Thu, 17 Apr 2025 15:16:53 +0100 Subject: [PATCH 3/3] FIX: do not try to interpolate empty paths --- lib/matplotlib/path.py | 2 +- lib/matplotlib/tests/test_path.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/path.py b/lib/matplotlib/path.py index b355849327f5..a021706fb1e5 100644 --- a/lib/matplotlib/path.py +++ b/lib/matplotlib/path.py @@ -681,7 +681,7 @@ def interpolated(self, steps): Path The interpolated path. """ - if steps == 1: + if steps == 1 or len(self) == 0: return self if self.codes is not None and self.MOVETO in self.codes[1:]: diff --git a/lib/matplotlib/tests/test_path.py b/lib/matplotlib/tests/test_path.py index 28fc8cfbe891..21f4c33794af 100644 --- a/lib/matplotlib/tests/test_path.py +++ b/lib/matplotlib/tests/test_path.py @@ -617,3 +617,8 @@ def test_interpolated_moveto_closepoly(): np.testing.assert_allclose(result.vertices, expected_vertices) np.testing.assert_array_equal(result.codes, expected_codes) + + +def test_interpolated_empty_path(): + path = Path(np.zeros((0, 2))) + assert path.interpolated(42) is path