Skip to content

Commit 1a7b35b

Browse files
committed
refactor Bezier so it doesn't depend on Path
1 parent 757fb24 commit 1a7b35b

File tree

6 files changed

+172
-154
lines changed

6 files changed

+172
-154
lines changed

doc/api/next_api_changes/deprecations.rst

+11-11
Original file line numberDiff line numberDiff line change
@@ -227,19 +227,9 @@ The following validators, defined in `.rcsetup`, are deprecated:
227227
``validate_axes_titlelocation``, ``validate_toolbar``,
228228
``validate_ps_papersize``, ``validate_legend_loc``,
229229
``validate_bool_maybe_none``, ``validate_hinting``,
230-
``validate_movie_writers``.
230+
``validate_movie_writers``, ``validate_webagg_address``.
231231
To test whether an rcParam value would be acceptable, one can test e.g. ``rc =
232232
RcParams(); rc[k] = v`` raises an exception.
233-
||||||| constructed merge base
234-
``validate_ps_papersize``, ``validate_legend_log``. To test whether an rcParam
235-
value would be acceptable, one can test e.g. ``rc = RcParams(); rc[k] = v``
236-
raises an exception.
237-
=======
238-
``validate_ps_papersize``, ``validate_legend_loc``,
239-
``validate_webagg_address``.
240-
To test whether an rcParam value would be acceptable, one can test e.g. ``rc =
241-
RcParams(); rc[k] = v`` raises an exception.
242-
>>>>>>> Deprecate validate_webagg_address.
243233

244234
Stricter rcParam validation
245235
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -370,3 +360,13 @@ mathtext ``Glue`` helper classes
370360
The ``Fil``, ``Fill``, ``Filll``, ``NegFil``, ``NegFill``, ``NegFilll``, and
371361
``SsGlue`` classes in the :mod:`matplotlib.mathtext` module are deprecated.
372362
As an alternative, directly construct glue instances with ``Glue("fil")``, etc.
363+
364+
Path helpers in :mod:`.bezier`
365+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
366+
- ``bezier.make_path_regular`` is deprecated. Use ``Path.cleaned()`` (or
367+
``Path.cleaned(curves=True)``, etc.) instead (but note that these methods add
368+
a ``STOP`` code at the end of the path).
369+
- ``bezier.concatenate_paths`` is deprecated. Use ``Path.make_compound_path()``
370+
instead.
371+
- ``bezier.split_path_inout`` (use ``Path.split_path_inout`` instead)
372+
- ``bezier.inside_circle()`` (no replacement)

doc/api/next_api_changes/removals.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ Classes and methods
8484

8585
- ``image.BboxImage.interp_at_native`` property (no replacement)
8686
- ``lines.Line2D.verticalOffset`` property (no replacement)
87-
- ``bezier.find_r_to_boundary_of_closedpath()`` (no relacement)
87+
- ``bezier.find_r_to_boundary_of_closedpath()`` (no replacement)
8888

8989
- ``quiver.Quiver.color()`` (use ``Quiver.get_facecolor()`` instead)
9090
- ``quiver.Quiver.keyvec`` property (no replacement)

lib/matplotlib/bezier.py

+57-111
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,54 @@ def get_normal_points(cx, cy, cos_t, sin_t, length):
6868
return x1, y1, x2, y2
6969

7070

71+
# deprecated routines (moved to `.Path`)
72+
73+
74+
@cbook.deprecated("3.3")
75+
def inside_circle(cx, cy, r):
76+
"""
77+
Return a function that checks whether a point is in a circle with center
78+
(*cx*, *cy*) and radius *r*.
79+
80+
The returned function has the signature::
81+
82+
f(xy: Tuple[float, float]) -> bool
83+
"""
84+
r2 = r ** 2
85+
86+
def _f(xy):
87+
x, y = xy
88+
return (x - cx) ** 2 + (y - cy) ** 2 < r2
89+
return _f
90+
91+
92+
@cbook.deprecated("3.3", alternative="Path.make_compound_path()")
93+
def split_path_inout(path, inside, tolerance=0.01, reorder_inout=False):
94+
"""
95+
Divide a path into two segments at the point where ``inside(x, y)``
96+
becomes False.
97+
"""
98+
return path.split_path_inout(inside, tolerance, reorder_inout)
99+
100+
101+
@cbook.deprecated(
102+
"3.3", alternative="Path.cleaned() and remove the final STOP if needed")
103+
def make_path_regular(path):
104+
"""
105+
If the ``codes`` attribute of `.Path` *p* is None, return a copy of *p*
106+
with ``codes`` set to (MOVETO, LINETO, LINETO, ..., LINETO); otherwise
107+
return *p* itself.
108+
"""
109+
return path.make_path_regular()
110+
111+
112+
@cbook.deprecated("3.3", alternative="Path.make_compound_path()")
113+
def concatenate_paths(paths):
114+
"""Concatenate a list of paths into a single path."""
115+
from .path import Path
116+
return Path.make_compound_path(*paths)
117+
118+
71119
# BEZIER routines
72120

73121
# subdividing bezier curve
@@ -177,12 +225,13 @@ class BezierSegment:
177225
"""
178226

179227
def __init__(self, control_points):
180-
n = len(control_points)
181-
self._orders = np.arange(n)
182-
coeff = [math.factorial(n - 1)
183-
// (math.factorial(i) * math.factorial(n - 1 - i))
184-
for i in range(n)]
185-
self._px = np.asarray(control_points).T * coeff
228+
self.cpoints = np.asarray(control_points)
229+
self.n, self.d = self.cpoints.shape
230+
self._orders = np.arange(self.n)
231+
coeff = [math.factorial(self.n - 1)
232+
// (math.factorial(i) * math.factorial(self.n - 1 - i))
233+
for i in range(self.n)]
234+
self._px = self.cpoints.T * coeff
186235

187236
def point_at_t(self, t):
188237
"""Return the point on the Bezier curve for parameter *t*."""
@@ -222,90 +271,9 @@ def split_bezier_intersecting_with_closedpath(
222271
return _left, _right
223272

224273

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-
290-
def inside_circle(cx, cy, r):
291-
"""
292-
Return a function that checks whether a point is in a circle with center
293-
(*cx*, *cy*) and radius *r*.
294-
295-
The returned function has the signature::
296-
297-
f(xy: Tuple[float, float]) -> bool
298-
"""
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
305-
306-
307274
# quadratic Bezier lines
308275

276+
309277
def get_cos_sin(x0, y0, x1, y1):
310278
dx, dy = x1 - x0, y1 - y0
311279
d = (dx * dx + dy * dy) ** .5
@@ -478,25 +446,3 @@ def make_wedged_bezier2(bezier2, width, w1=1., wm=0.5, w2=0.):
478446
c3x_right, c3y_right)
479447

480448
return path_left, path_right
481-
482-
483-
def make_path_regular(p):
484-
"""
485-
If the ``codes`` attribute of `.Path` *p* is None, return a copy of *p*
486-
with ``codes`` set to (MOVETO, LINETO, LINETO, ..., LINETO); otherwise
487-
return *p* itself.
488-
"""
489-
c = p.codes
490-
if c is None:
491-
c = np.full(len(p.vertices), Path.LINETO, dtype=Path.code_type)
492-
c[0] = Path.MOVETO
493-
return Path(p.vertices, c)
494-
else:
495-
return p
496-
497-
498-
def concatenate_paths(paths):
499-
"""Concatenate a list of paths into a single path."""
500-
vertices = np.concatenate([p.vertices for p in paths])
501-
codes = np.concatenate([make_path_regular(p).codes for p in paths])
502-
return Path(vertices, codes)

lib/matplotlib/patches.py

+32-21
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,28 @@
1010
import matplotlib as mpl
1111
from . import artist, cbook, colors, docstring, lines as mlines, transforms
1212
from .bezier import (
13-
NonIntersectingPathException, concatenate_paths, get_cos_sin,
14-
get_intersection, get_parallels, inside_circle, make_path_regular,
15-
make_wedged_bezier2, split_bezier_intersecting_with_closedpath,
16-
split_path_inout)
13+
NonIntersectingPathException, get_cos_sin, get_intersection, get_parallels,
14+
make_wedged_bezier2, split_bezier_intersecting_with_closedpath)
1715
from .path import Path
1816

1917

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+
2035
@cbook._define_aliases({
2136
"antialiased": ["aa"],
2237
"edgecolor": ["ec"],
@@ -2414,7 +2429,7 @@ def insideA(xy_display):
24142429
return patchA.contains(xy_event)[0]
24152430

24162431
try:
2417-
left, right = split_path_inout(path, insideA)
2432+
left, right = path.split_path_inout(insideA)
24182433
except ValueError:
24192434
right = path
24202435

@@ -2426,7 +2441,7 @@ def insideB(xy_display):
24262441
return patchB.contains(xy_event)[0]
24272442

24282443
try:
2429-
left, right = split_path_inout(path, insideB)
2444+
left, right = path.split_path_inout(insideB)
24302445
except ValueError:
24312446
left = path
24322447

@@ -2439,15 +2454,15 @@ def _shrink(self, path, shrinkA, shrinkB):
24392454
Shrink the path by fixed size (in points) with shrinkA and shrinkB.
24402455
"""
24412456
if shrinkA:
2442-
insideA = inside_circle(*path.vertices[0], shrinkA)
2457+
insideA = _inside_circle(*path.vertices[0], shrinkA)
24432458
try:
2444-
left, path = split_path_inout(path, insideA)
2459+
left, path = path.split_path_inout(insideA)
24452460
except ValueError:
24462461
pass
24472462
if shrinkB:
2448-
insideB = inside_circle(*path.vertices[-1], shrinkB)
2463+
insideB = _inside_circle(*path.vertices[-1], shrinkB)
24492464
try:
2450-
path, right = split_path_inout(path, insideB)
2465+
path, right = path.split_path_inout(insideB)
24512466
except ValueError:
24522467
pass
24532468
return path
@@ -2872,9 +2887,6 @@ def __call__(self, path, mutation_size, linewidth,
28722887
The __call__ method is a thin wrapper around the transmute method
28732888
and takes care of the aspect ratio.
28742889
"""
2875-
2876-
path = make_path_regular(path)
2877-
28782890
if aspect_ratio is not None:
28792891
# Squeeze the given height by the aspect_ratio
28802892
vertices = path.vertices / [1, aspect_ratio]
@@ -2886,10 +2898,9 @@ def __call__(self, path, mutation_size, linewidth,
28862898
if np.iterable(fillable):
28872899
path_list = []
28882900
for p in zip(path_mutated):
2889-
v, c = p.vertices, p.codes
28902901
# Restore the height
2891-
v[:, 1] = v[:, 1] * aspect_ratio
2892-
path_list.append(Path(v, c))
2902+
path_list.append(
2903+
Path(p.vertices * [1, aspect_ratio], p.codes))
28932904
return path_list, fillable
28942905
else:
28952906
return path_mutated, fillable
@@ -3340,7 +3351,7 @@ def transmute(self, path, mutation_size, linewidth):
33403351

33413352
# divide the path into a head and a tail
33423353
head_length = self.head_length * mutation_size
3343-
in_f = inside_circle(x2, y2, head_length)
3354+
in_f = _inside_circle(x2, y2, head_length)
33443355
arrow_path = [(x0, y0), (x1, y1), (x2, y2)]
33453356

33463357
try:
@@ -3423,7 +3434,7 @@ def transmute(self, path, mutation_size, linewidth):
34233434
arrow_path = [(x0, y0), (x1, y1), (x2, y2)]
34243435

34253436
# path for head
3426-
in_f = inside_circle(x2, y2, head_length)
3437+
in_f = _inside_circle(x2, y2, head_length)
34273438
try:
34283439
path_out, path_in = split_bezier_intersecting_with_closedpath(
34293440
arrow_path, in_f, tolerance=0.01)
@@ -3438,7 +3449,7 @@ def transmute(self, path, mutation_size, linewidth):
34383449
path_head = path_in
34393450

34403451
# path for head
3441-
in_f = inside_circle(x2, y2, head_length * .8)
3452+
in_f = _inside_circle(x2, y2, head_length * .8)
34423453
path_out, path_in = split_bezier_intersecting_with_closedpath(
34433454
arrow_path, in_f, tolerance=0.01)
34443455
path_tail = path_out
@@ -3456,7 +3467,7 @@ def transmute(self, path, mutation_size, linewidth):
34563467
w1=1., wm=0.6, w2=0.3)
34573468

34583469
# path for head
3459-
in_f = inside_circle(x0, y0, tail_width * .3)
3470+
in_f = _inside_circle(x0, y0, tail_width * .3)
34603471
path_in, path_out = split_bezier_intersecting_with_closedpath(
34613472
arrow_path, in_f, tolerance=0.01)
34623473
tail_start = path_in[-1]
@@ -4125,7 +4136,7 @@ def get_path(self):
41254136
"""
41264137
_path, fillable = self.get_path_in_displaycoord()
41274138
if np.iterable(fillable):
4128-
_path = concatenate_paths(_path)
4139+
_path = Path.make_compound_path(*_path)
41294140
return self.get_transform().inverted().transform_path(_path)
41304141

41314142
def get_path_in_displaycoord(self):

0 commit comments

Comments
 (0)