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

Skip to content

Commit f666d95

Browse files
committed
incorporate @anntzer's BezierSegment feedback
1 parent 02ef3b2 commit f666d95

File tree

2 files changed

+72
-42
lines changed

2 files changed

+72
-42
lines changed

lib/matplotlib/bezier.py

Lines changed: 67 additions & 36 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,33 +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-
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
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)
235239

236240
def point_at_t(self, t):
237241
"""Return the point on the Bezier curve for parameter *t*."""
238242
return tuple(
239243
self._px @ (((1 - t) ** self._orders)[::-1] * t ** self._orders))
240244

241-
def __call__(self, t):
242-
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
243292

244293
@property
245294
def interior_extrema(self):
@@ -255,33 +304,15 @@ def interior_extrema(self):
255304
dzeros : float, array_like
256305
of same size as dims. the $t$ such that $d/dx_i B(t) = 0$
257306
"""
258-
if self.n <= 2: # a line's extrema are always its tips
259-
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:
260311
return np.array([]), np.array([])
261-
elif self.n == 3: # quadratic curve
262-
# the bezier curve in standard form is
263-
# cp[0] * (1 - t)^2 + cp[1] * 2t(1-t) + cp[2] * t^2
264-
# can be re-written as
265-
# cp[0] + 2 (cp[1] - cp[0]) t + (cp[2] - 2 cp[1] + cp[0]) t^2
266-
# which has simple derivative
267-
# 2*(cp[2] - 2*cp[1] + cp[0]) t + 2*(cp[1] - cp[0])
268-
p = [2*(self.cpoints[2] - 2*self.cpoints[1] + self.cpoints[0]),
269-
2*(self.cpoints[1] - self.cpoints[0])]
270-
elif self.n == 4: # cubic curve
271-
P = self.cpoints
272-
# derivative of cubic bezier curve has coefficients
273-
a = 3*(P[3] - 3*P[2] + 3*P[1] - P[0])
274-
b = 6*(P[2] - 2*P[1] + P[0])
275-
c = 3*(P[1] - P[0])
276-
p = [a, b, c]
277-
else: # self.n > 4:
278-
# a general formula exists, but is not useful for matplotlib
279-
raise NotImplementedError("Zero finding only implemented up to "
280-
"cubic curves.")
281312
dims = []
282313
roots = []
283-
for i, pi in enumerate(np.array(p).T):
284-
r = np.roots(pi)
314+
for i, pi in enumerate(dCj.T):
315+
r = np.roots(pi[::-1])
285316
roots.append(r)
286317
dims.append(i*np.ones_like(r))
287318
roots = np.concatenate(roots)

lib/matplotlib/path.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -348,12 +348,11 @@ def make_compound_path(cls, *args):
348348
codes = np.empty(len(vertices), dtype=cls.code_type)
349349
i = 0
350350
for path in args:
351-
codes = path.codes
352-
if codes is None:
353-
codes = np.full(len(path.vertices), Path.LINETO,
354-
dtype=Path.code_type)
355-
codes[0] = Path.MOVETO # so concatenated strokes stay separate
356-
codes[i:i + len(path.codes)] = codes
351+
if path.codes is None:
352+
codes[i] = cls.MOVETO
353+
codes[i + 1:i + len(path.vertices)] = cls.LINETO
354+
else:
355+
codes[i:i + len(path.codes)] = path.codes
357356
i += len(path.vertices)
358357
# remove STOP's, since internal STOPs are a bug
359358
not_stop_mask = codes != cls.STOP

0 commit comments

Comments
 (0)