Skip to content

Commit 696b64c

Browse files
committed
refactor Bezier so it doesn't depend on Path
1 parent 50959b5 commit 696b64c

File tree

5 files changed

+118
-98
lines changed

5 files changed

+118
-98
lines changed

doc/api/next_api_changes/deprecations.rst

+7-7
Original file line numberDiff line numberDiff line change
@@ -369,10 +369,10 @@ also be accessible as ``toolbar.parent()``.
369369

370370
Path helpers in :mod:`.bezier`
371371
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
372-
373-
``bezier.make_path_regular`` is deprecated. Use ``Path.cleaned()`` (or
374-
``Path.cleaned(curves=True)``, etc.) instead (but note that these methods add a
375-
``STOP`` code at the end of the path).
376-
377-
``bezier.concatenate_paths`` is deprecated. Use ``Path.make_compound_path()``
378-
instead.
372+
- ``bezier.make_path_regular`` is deprecated. Use ``Path.cleaned()`` (or
373+
``Path.cleaned(curves=True)``, etc.) instead (but note that these methods add
374+
a ``STOP`` code at the end of the path).
375+
- ``bezier.concatenate_paths`` is deprecated. Use ``Path.make_compound_path()``
376+
instead.
377+
- ``bezier.split_path_inout`` (use ``Path.split_path_inout`` instead)
378+
- ``bezier.inside_circle()`` (no replacement)

doc/api/next_api_changes/removals.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ Classes and methods
9292

9393
- ``image.BboxImage.interp_at_native`` property (no replacement)
9494
- ``lines.Line2D.verticalOffset`` property (no replacement)
95-
- ``bezier.find_r_to_boundary_of_closedpath()`` (no relacement)
95+
- ``bezier.find_r_to_boundary_of_closedpath()`` (no replacement)
9696

9797
- ``quiver.Quiver.color()`` (use ``Quiver.get_facecolor()`` instead)
9898
- ``quiver.Quiver.keyvec`` property (no replacement)

lib/matplotlib/bezier.py

+17-75
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
import numpy as np
88

99
import matplotlib.cbook as cbook
10-
from matplotlib.path import Path
1110

1211

1312
class NonIntersectingPathException(ValueError):
1413
pass
1514

15+
1616
# some functions
1717

1818

@@ -68,6 +68,15 @@ def get_normal_points(cx, cy, cos_t, sin_t, length):
6868
return x1, y1, x2, y2
6969

7070

71+
@cbook.deprecated("3.3", alternative="Path.split_path_inout()")
72+
def split_path_inout(path, inside, tolerance=0.01, reorder_inout=False):
73+
"""
74+
Divide a path into two segments at the point where ``inside(x, y)``
75+
becomes False.
76+
"""
77+
return path.split_path_inout(inside, tolerance, reorder_inout)
78+
79+
7180
# BEZIER routines
7281

7382
# subdividing bezier curve
@@ -222,71 +231,7 @@ def split_bezier_intersecting_with_closedpath(
222231
return _left, _right
223232

224233

225-
# matplotlib specific
226-
227-
228-
def split_path_inout(path, inside, tolerance=0.01, reorder_inout=False):
229-
"""
230-
Divide a path into two segments at the point where ``inside(x, y)`` becomes
231-
False.
232-
"""
233-
path_iter = path.iter_segments()
234-
235-
ctl_points, command = next(path_iter)
236-
begin_inside = inside(ctl_points[-2:]) # true if begin point is inside
237-
238-
ctl_points_old = ctl_points
239-
240-
concat = np.concatenate
241-
242-
iold = 0
243-
i = 1
244-
245-
for ctl_points, command in path_iter:
246-
iold = i
247-
i += len(ctl_points) // 2
248-
if inside(ctl_points[-2:]) != begin_inside:
249-
bezier_path = concat([ctl_points_old[-2:], ctl_points])
250-
break
251-
ctl_points_old = ctl_points
252-
else:
253-
raise ValueError("The path does not intersect with the patch")
254-
255-
bp = bezier_path.reshape((-1, 2))
256-
left, right = split_bezier_intersecting_with_closedpath(
257-
bp, inside, tolerance)
258-
if len(left) == 2:
259-
codes_left = [Path.LINETO]
260-
codes_right = [Path.MOVETO, Path.LINETO]
261-
elif len(left) == 3:
262-
codes_left = [Path.CURVE3, Path.CURVE3]
263-
codes_right = [Path.MOVETO, Path.CURVE3, Path.CURVE3]
264-
elif len(left) == 4:
265-
codes_left = [Path.CURVE4, Path.CURVE4, Path.CURVE4]
266-
codes_right = [Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4]
267-
else:
268-
raise AssertionError("This should never be reached")
269-
270-
verts_left = left[1:]
271-
verts_right = right[:]
272-
273-
if path.codes is None:
274-
path_in = Path(concat([path.vertices[:i], verts_left]))
275-
path_out = Path(concat([verts_right, path.vertices[i:]]))
276-
277-
else:
278-
path_in = Path(concat([path.vertices[:iold], verts_left]),
279-
concat([path.codes[:iold], codes_left]))
280-
281-
path_out = Path(concat([verts_right, path.vertices[i:]]),
282-
concat([codes_right, path.codes[i:]]))
283-
284-
if reorder_inout and not begin_inside:
285-
path_in, path_out = path_out, path_in
286-
287-
return path_in, path_out
288-
289-
234+
@cbook.deprecated("3.3")
290235
def inside_circle(cx, cy, r):
291236
"""
292237
Return a function that checks whether a point is in a circle with center
@@ -296,16 +241,13 @@ def inside_circle(cx, cy, r):
296241
297242
f(xy: Tuple[float, float]) -> bool
298243
"""
299-
r2 = r ** 2
300-
301-
def _f(xy):
302-
x, y = xy
303-
return (x - cx) ** 2 + (y - cy) ** 2 < r2
304-
return _f
244+
from .patches import _inside_circle
245+
return _inside_circle(cx, cy, r)
305246

306247

307248
# quadratic Bezier lines
308249

250+
309251
def get_cos_sin(x0, y0, x1, y1):
310252
dx, dy = x1 - x0, y1 - y0
311253
d = (dx * dx + dy * dy) ** .5
@@ -488,6 +430,7 @@ def make_path_regular(p):
488430
with ``codes`` set to (MOVETO, LINETO, LINETO, ..., LINETO); otherwise
489431
return *p* itself.
490432
"""
433+
from .path import Path
491434
c = p.codes
492435
if c is None:
493436
c = np.full(len(p.vertices), Path.LINETO, dtype=Path.code_type)
@@ -500,6 +443,5 @@ def make_path_regular(p):
500443
@cbook.deprecated("3.3", alternative="Path.make_compound_path()")
501444
def concatenate_paths(paths):
502445
"""Concatenate a list of paths into a single path."""
503-
vertices = np.concatenate([p.vertices for p in paths])
504-
codes = np.concatenate([make_path_regular(p).codes for p in paths])
505-
return Path(vertices, codes)
446+
from .path import Path
447+
return Path.make_compound_path(*paths)

lib/matplotlib/patches.py

+29-14
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,28 @@
1010
import matplotlib as mpl
1111
from . import artist, cbook, colors, docstring, lines as mlines, transforms
1212
from .bezier import (
13-
NonIntersectingPathException, get_cos_sin, get_intersection,
14-
get_parallels, inside_circle, make_wedged_bezier2,
15-
split_bezier_intersecting_with_closedpath, split_path_inout)
13+
NonIntersectingPathException, get_cos_sin, get_intersection, get_parallels,
14+
make_wedged_bezier2, split_bezier_intersecting_with_closedpath)
1615
from .path import Path
1716

1817

18+
def _inside_circle(cx, cy, r):
19+
"""
20+
Return a function that checks whether a point is in a circle with center
21+
(*cx*, *cy*) and radius *r*.
22+
23+
The returned function has the signature::
24+
25+
f(xy: Tuple[float, float]) -> bool
26+
"""
27+
r2 = r ** 2
28+
29+
def _f(xy):
30+
x, y = xy
31+
return (x - cx) ** 2 + (y - cy) ** 2 < r2
32+
return _f
33+
34+
1935
@cbook._define_aliases({
2036
"antialiased": ["aa"],
2137
"edgecolor": ["ec"],
@@ -2411,7 +2427,7 @@ def insideA(xy_display):
24112427
return patchA.contains(xy_event)[0]
24122428

24132429
try:
2414-
left, right = split_path_inout(path, insideA)
2430+
left, right = path.split_path_inout(insideA)
24152431
except ValueError:
24162432
right = path
24172433

@@ -2423,7 +2439,7 @@ def insideB(xy_display):
24232439
return patchB.contains(xy_event)[0]
24242440

24252441
try:
2426-
left, right = split_path_inout(path, insideB)
2442+
left, right = path.split_path_inout(insideB)
24272443
except ValueError:
24282444
left = path
24292445

@@ -2436,15 +2452,15 @@ def _shrink(self, path, shrinkA, shrinkB):
24362452
Shrink the path by fixed size (in points) with shrinkA and shrinkB.
24372453
"""
24382454
if shrinkA:
2439-
insideA = inside_circle(*path.vertices[0], shrinkA)
2455+
insideA = _inside_circle(*path.vertices[0], shrinkA)
24402456
try:
2441-
left, path = split_path_inout(path, insideA)
2457+
left, path = path.split_path_inout(insideA)
24422458
except ValueError:
24432459
pass
24442460
if shrinkB:
2445-
insideB = inside_circle(*path.vertices[-1], shrinkB)
2461+
insideB = _inside_circle(*path.vertices[-1], shrinkB)
24462462
try:
2447-
path, right = split_path_inout(path, insideB)
2463+
path, right = path.split_path_inout(insideB)
24482464
except ValueError:
24492465
pass
24502466
return path
@@ -2869,7 +2885,6 @@ def __call__(self, path, mutation_size, linewidth,
28692885
The __call__ method is a thin wrapper around the transmute method
28702886
and takes care of the aspect ratio.
28712887
"""
2872-
28732888
if aspect_ratio is not None:
28742889
# Squeeze the given height by the aspect_ratio
28752890
vertices = path.vertices / [1, aspect_ratio]
@@ -3334,7 +3349,7 @@ def transmute(self, path, mutation_size, linewidth):
33343349

33353350
# divide the path into a head and a tail
33363351
head_length = self.head_length * mutation_size
3337-
in_f = inside_circle(x2, y2, head_length)
3352+
in_f = _inside_circle(x2, y2, head_length)
33383353
arrow_path = [(x0, y0), (x1, y1), (x2, y2)]
33393354

33403355
try:
@@ -3417,7 +3432,7 @@ def transmute(self, path, mutation_size, linewidth):
34173432
arrow_path = [(x0, y0), (x1, y1), (x2, y2)]
34183433

34193434
# path for head
3420-
in_f = inside_circle(x2, y2, head_length)
3435+
in_f = _inside_circle(x2, y2, head_length)
34213436
try:
34223437
path_out, path_in = split_bezier_intersecting_with_closedpath(
34233438
arrow_path, in_f, tolerance=0.01)
@@ -3432,7 +3447,7 @@ def transmute(self, path, mutation_size, linewidth):
34323447
path_head = path_in
34333448

34343449
# path for head
3435-
in_f = inside_circle(x2, y2, head_length * .8)
3450+
in_f = _inside_circle(x2, y2, head_length * .8)
34363451
path_out, path_in = split_bezier_intersecting_with_closedpath(
34373452
arrow_path, in_f, tolerance=0.01)
34383453
path_tail = path_out
@@ -3450,7 +3465,7 @@ def transmute(self, path, mutation_size, linewidth):
34503465
w1=1., wm=0.6, w2=0.3)
34513466

34523467
# path for head
3453-
in_f = inside_circle(x0, y0, tail_width * .3)
3468+
in_f = _inside_circle(x0, y0, tail_width * .3)
34543469
path_in, path_out = split_bezier_intersecting_with_closedpath(
34553470
arrow_path, in_f, tolerance=0.01)
34563471
tail_start = path_in[-1]

lib/matplotlib/path.py

+64-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import matplotlib as mpl
1818
from . import _path, cbook
1919
from .cbook import _to_unmasked_float_array, simple_linear_interpolation
20+
from .bezier import split_bezier_intersecting_with_closedpath
2021

2122

2223
class Path:
@@ -586,6 +587,67 @@ def interpolated(self, steps):
586587
new_codes = None
587588
return Path(vertices, new_codes)
588589

590+
def split_path_inout(self, inside, tolerance=0.01, reorder_inout=False):
591+
"""
592+
Divide a path into two segments at the point where ``inside(x, y)``
593+
becomes False.
594+
"""
595+
path_iter = self.iter_segments()
596+
597+
ctl_points, command = next(path_iter)
598+
begin_inside = inside(ctl_points[-2:]) # true if begin point is inside
599+
600+
ctl_points_old = ctl_points
601+
602+
concat = np.concatenate
603+
604+
iold = 0
605+
i = 1
606+
607+
for ctl_points, command in path_iter:
608+
iold = i
609+
i += len(ctl_points) // 2
610+
if inside(ctl_points[-2:]) != begin_inside:
611+
bezier_path = concat([ctl_points_old[-2:], ctl_points])
612+
break
613+
ctl_points_old = ctl_points
614+
else:
615+
raise ValueError("The path does not intersect with the patch")
616+
617+
bp = bezier_path.reshape((-1, 2))
618+
left, right = split_bezier_intersecting_with_closedpath(
619+
bp, inside, tolerance)
620+
if len(left) == 2:
621+
codes_left = [Path.LINETO]
622+
codes_right = [Path.MOVETO, Path.LINETO]
623+
elif len(left) == 3:
624+
codes_left = [Path.CURVE3, Path.CURVE3]
625+
codes_right = [Path.MOVETO, Path.CURVE3, Path.CURVE3]
626+
elif len(left) == 4:
627+
codes_left = [Path.CURVE4, Path.CURVE4, Path.CURVE4]
628+
codes_right = [Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4]
629+
else:
630+
raise AssertionError("This should never be reached")
631+
632+
verts_left = left[1:]
633+
verts_right = right[:]
634+
635+
if self.codes is None:
636+
path_in = Path(concat([self.vertices[:i], verts_left]))
637+
path_out = Path(concat([verts_right, self.vertices[i:]]))
638+
639+
else:
640+
path_in = Path(concat([self.vertices[:iold], verts_left]),
641+
concat([self.codes[:iold], codes_left]))
642+
643+
path_out = Path(concat([verts_right, self.vertices[i:]]),
644+
concat([codes_right, self.codes[i:]]))
645+
646+
if reorder_inout and not begin_inside:
647+
path_in, path_out = path_out, path_in
648+
649+
return path_in, path_out
650+
589651
def to_polygons(self, transform=None, width=0, height=0, closed_only=True):
590652
"""
591653
Convert this path to a list of polygons or polylines. Each
@@ -648,7 +710,8 @@ def unit_rectangle(cls):
648710
def unit_regular_polygon(cls, numVertices):
649711
"""
650712
Return a :class:`Path` instance for a unit regular polygon with the
651-
given *numVertices* and radius of 1.0, centered at (0, 0).
713+
given *numVertices* such that the circumscribing circle has radius 1.0,
714+
centered at (0, 0).
652715
"""
653716
if numVertices <= 16:
654717
path = cls._unit_regular_polygons.get(numVertices)

0 commit comments

Comments
 (0)