Skip to content

Commit 6a6e8d8

Browse files
authored
Merge pull request #29940 from meeseeksmachine/auto-backport-of-pr-29919-on-v3.10.x
Backport PR #29919 on branch v3.10.x (Handle MOVETO's, CLOSEPOLY's and empty paths in Path.interpolated)
2 parents a595375 + bdba952 commit 6a6e8d8

File tree

2 files changed

+106
-4
lines changed

2 files changed

+106
-4
lines changed

lib/matplotlib/path.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -668,14 +668,35 @@ def intersects_bbox(self, bbox, filled=True):
668668

669669
def interpolated(self, steps):
670670
"""
671-
Return a new path resampled to length N x *steps*.
671+
Return a new path with each segment divided into *steps* parts.
672672
673-
Codes other than `LINETO` are not handled correctly.
673+
Codes other than `LINETO`, `MOVETO`, and `CLOSEPOLY` are not handled correctly.
674+
675+
Parameters
676+
----------
677+
steps : int
678+
The number of segments in the new path for each in the original.
679+
680+
Returns
681+
-------
682+
Path
683+
The interpolated path.
674684
"""
675-
if steps == 1:
685+
if steps == 1 or len(self) == 0:
676686
return self
677687

678-
vertices = simple_linear_interpolation(self.vertices, steps)
688+
if self.codes is not None and self.MOVETO in self.codes[1:]:
689+
return self.make_compound_path(
690+
*(p.interpolated(steps) for p in self._iter_connected_components()))
691+
692+
if self.codes is not None and self.CLOSEPOLY in self.codes and not np.all(
693+
self.vertices[self.codes == self.CLOSEPOLY] == self.vertices[0]):
694+
vertices = self.vertices.copy()
695+
vertices[self.codes == self.CLOSEPOLY] = vertices[0]
696+
else:
697+
vertices = self.vertices
698+
699+
vertices = simple_linear_interpolation(vertices, steps)
679700
codes = self.codes
680701
if codes is not None:
681702
new_codes = np.full((len(codes) - 1) * steps + 1, Path.LINETO,

lib/matplotlib/tests/test_path.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,3 +541,84 @@ def test_cleanup_closepoly():
541541
cleaned = p.cleaned(remove_nans=True)
542542
assert len(cleaned) == 1
543543
assert cleaned.codes[0] == Path.STOP
544+
545+
546+
def test_interpolated_moveto():
547+
# Initial path has two subpaths with two LINETOs each
548+
vertices = np.array([[0, 0],
549+
[0, 1],
550+
[1, 2],
551+
[4, 4],
552+
[4, 5],
553+
[5, 5]])
554+
codes = [Path.MOVETO, Path.LINETO, Path.LINETO] * 2
555+
556+
path = Path(vertices, codes)
557+
result = path.interpolated(3)
558+
559+
# Result should have two subpaths with six LINETOs each
560+
expected_subpath_codes = [Path.MOVETO] + [Path.LINETO] * 6
561+
np.testing.assert_array_equal(result.codes, expected_subpath_codes * 2)
562+
563+
564+
def test_interpolated_closepoly():
565+
codes = [Path.MOVETO] + [Path.LINETO]*2 + [Path.CLOSEPOLY]
566+
vertices = [(4, 3), (5, 4), (5, 3), (0, 0)]
567+
568+
path = Path(vertices, codes)
569+
result = path.interpolated(2)
570+
571+
expected_vertices = np.array([[4, 3],
572+
[4.5, 3.5],
573+
[5, 4],
574+
[5, 3.5],
575+
[5, 3],
576+
[4.5, 3],
577+
[4, 3]])
578+
expected_codes = [Path.MOVETO] + [Path.LINETO]*5 + [Path.CLOSEPOLY]
579+
580+
np.testing.assert_allclose(result.vertices, expected_vertices)
581+
np.testing.assert_array_equal(result.codes, expected_codes)
582+
583+
# Usually closepoly is the last vertex but does not have to be.
584+
codes += [Path.LINETO]
585+
vertices += [(2, 1)]
586+
587+
path = Path(vertices, codes)
588+
result = path.interpolated(2)
589+
590+
extra_expected_vertices = np.array([[3, 2],
591+
[2, 1]])
592+
expected_vertices = np.concatenate([expected_vertices, extra_expected_vertices])
593+
594+
expected_codes += [Path.LINETO] * 2
595+
596+
np.testing.assert_allclose(result.vertices, expected_vertices)
597+
np.testing.assert_array_equal(result.codes, expected_codes)
598+
599+
600+
def test_interpolated_moveto_closepoly():
601+
# Initial path has two closed subpaths
602+
codes = ([Path.MOVETO] + [Path.LINETO]*2 + [Path.CLOSEPOLY]) * 2
603+
vertices = [(4, 3), (5, 4), (5, 3), (0, 0), (8, 6), (10, 8), (10, 6), (0, 0)]
604+
605+
path = Path(vertices, codes)
606+
result = path.interpolated(2)
607+
608+
expected_vertices1 = np.array([[4, 3],
609+
[4.5, 3.5],
610+
[5, 4],
611+
[5, 3.5],
612+
[5, 3],
613+
[4.5, 3],
614+
[4, 3]])
615+
expected_vertices = np.concatenate([expected_vertices1, expected_vertices1 * 2])
616+
expected_codes = ([Path.MOVETO] + [Path.LINETO]*5 + [Path.CLOSEPOLY]) * 2
617+
618+
np.testing.assert_allclose(result.vertices, expected_vertices)
619+
np.testing.assert_array_equal(result.codes, expected_codes)
620+
621+
622+
def test_interpolated_empty_path():
623+
path = Path(np.zeros((0, 2)))
624+
assert path.interpolated(42) is path

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy