Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit dfc5ea0

Browse files
committed
incorporate @anntzer's BezierSegment feedback
1 parent b54dbc6 commit dfc5ea0

File tree

1 file changed

+67
-35
lines changed

1 file changed

+67
-35
lines changed

lib/matplotlib/bezier.py

Lines changed: 67 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
"""
44

55
import math
6+
import warnings
67

78
import numpy as np
89

910
import matplotlib.cbook as cbook
1011

12+
_comb = np.vectorize(math.comb)
1113

1214
class NonIntersectingPathException(ValueError):
1315
pass
@@ -213,32 +215,80 @@ def find_bezier_t_intersecting_with_closedpath(
213215
start = middle
214216
start_inside = middle_inside
215217

216-
217218
class BezierSegment:
218219
"""
219-
A D-dimensional Bezier segment.
220+
A d-dimensional Bezier segment.
220221
221222
Parameters
222223
----------
223-
control_points : (N, D) array
224+
control_points : (N, d) array
224225
Location of the *N* control points.
225226
"""
226227

227228
def __init__(self, control_points):
228-
n = len(control_points)
229-
self._orders = np.arange(n)
230-
coeff = [math.factorial(n - 1)
231-
// (math.factorial(i) * math.factorial(n - 1 - i))
232-
for i in range(n)]
233-
self._px = np.asarray(control_points).T * coeff
229+
self._cpoints = np.asarray(control_points)
230+
self._N, self._d = self._cpoints.shape
231+
self._orders = np.arange(self._N)
232+
coeff = [math.factorial(self._N - 1)
233+
// (math.factorial(i) * math.factorial(self._N - 1 - i))
234+
for i in range(self._N)]
235+
self._px = self._cpoints.T * coeff
236+
237+
def __call__(self, t):
238+
return self.point_at_t(t)
234239

235240
def point_at_t(self, t):
236241
"""Return the point on the Bezier curve for parameter *t*."""
237242
return tuple(
238243
self._px @ (((1 - t) ** self._orders)[::-1] * t ** self._orders))
239244

240-
def __call__(self, t):
241-
return self.point_at_t(t)
245+
@property
246+
def control_points(self):
247+
"""The control points of the curve."""
248+
return self._cpoints
249+
250+
@property
251+
def dimension(self):
252+
"""The dimension of the curve."""
253+
return self._d
254+
255+
@property
256+
def degree(self):
257+
"""The number of control points in the curve."""
258+
return self._N - 1
259+
260+
@property
261+
def polynomial_coefficients(self):
262+
r"""The polynomial coefficients of the Bezier curve.
263+
264+
Returns
265+
-------
266+
coefs : float, (n+1, d) array_like
267+
coefficients after expanding in polynomial basis, where n is the
268+
degree of the bezier curve
269+
270+
Notes
271+
-----
272+
The coefficients are calculated as
273+
274+
.. math::
275+
276+
{n \choose j} \sum_{i=0}^j (-1)^{i+j} {j \choose i} P_i
277+
278+
"""
279+
n = self.degree
280+
if n > 10:
281+
warnings.warn("Polynomial coefficients formula unstable for high "
282+
"order Bezier curves!", RuntimeWarning)
283+
d = self.dimension
284+
P = self.control_points
285+
coefs = np.zeros((n+1, d))
286+
for j in range(n+1):
287+
i = np.arange(j+1)
288+
prefactor = np.power(-1, i + j) * _comb(j, i)
289+
prefactor = np.tile(prefactor, (d, 1)).T
290+
coefs[j] = _comb(n, j) * np.sum(prefactor*P[i], axis=0)
291+
return coefs
242292

243293
@property
244294
def interior_extrema(self):
@@ -254,33 +304,15 @@ def interior_extrema(self):
254304
dzeros : float, array_like
255305
of same size as dims. the $t$ such that $d/dx_i B(t) = 0$
256306
"""
257-
if self.n <= 2: # a line's extrema are always its tips
258-
p = [0] # roots returns empty array for zero polynomial
307+
n = self.degree
308+
Cj = self.polynomial_coefficients
309+
dCj = np.atleast_2d(np.arange(1, n+1)).T * Cj[1:]
310+
if len(dCj) == 0:
259311
return np.array([]), np.array([])
260-
elif self.n == 3: # quadratic curve
261-
# the bezier curve in standard form is
262-
# cp[0] * (1 - t)^2 + cp[1] * 2t(1-t) + cp[2] * t^2
263-
# can be re-written as
264-
# cp[0] + 2 (cp[1] - cp[0]) t + (cp[2] - 2 cp[1] + cp[0]) t^2
265-
# which has simple derivative
266-
# 2*(cp[2] - 2*cp[1] + cp[0]) t + 2*(cp[1] - cp[0])
267-
p = [2*(self.cpoints[2] - 2*self.cpoints[1] + self.cpoints[0]),
268-
2*(self.cpoints[1] - self.cpoints[0])]
269-
elif self.n == 4: # cubic curve
270-
P = self.cpoints
271-
# derivative of cubic bezier curve has coefficients
272-
a = 3*(P[3] - 3*P[2] + 3*P[1] - P[0])
273-
b = 6*(P[2] - 2*P[1] + P[0])
274-
c = 3*(P[1] - P[0])
275-
p = [a, b, c]
276-
else: # self.n > 4:
277-
# a general formula exists, but is not useful for matplotlib
278-
raise NotImplementedError("Zero finding only implemented up to "
279-
"cubic curves.")
280312
dims = []
281313
roots = []
282-
for i, pi in enumerate(np.array(p).T):
283-
r = np.roots(pi)
314+
for i, pi in enumerate(dCj.T):
315+
r = np.roots(pi[::-1])
284316
roots.append(r)
285317
dims.append(i*np.ones_like(r))
286318
roots = np.concatenate(roots)

0 commit comments

Comments
 (0)