Skip to content

Commit bdba952

Browse files
timhoffmmeeseeksmachine
authored andcommitted
Backport PR #29919: Handle MOVETO's, CLOSEPOLY's and empty paths in Path.interpolated
1 parent a595375 commit bdba952

File tree

2 files changed

+106
-4
lines changed

2 files changed

+106
-4
lines changed

lib/matplotlib/path.py

+25-4
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

+81
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)