Skip to content

Commit 7615112

Browse files
committed
compute area enclosed by path
1 parent 1c23899 commit 7615112

File tree

2 files changed

+50
-0
lines changed

2 files changed

+50
-0
lines changed

lib/matplotlib/bezier.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import warnings
77

88
import numpy as np
9+
import scipy.integrate
910

1011
import matplotlib.cbook as cbook
1112

@@ -254,6 +255,20 @@ def point_at_t(self, t):
254255
"""Return the point on the Bezier curve for parameter *t*."""
255256
return tuple(self(t))
256257

258+
def arc_area(self):
259+
"""(Signed) area between curve an origin."""
260+
dB = self.differentiate(self)
261+
def integrand(t):
262+
dr = dB(t).T
263+
n = np.array([dr[1], -dr[0]])
264+
return (self(t).T @ n) / 2
265+
return scipy.integrate.quad(integrand, 0, 1)[0]
266+
267+
@classmethod
268+
def differentiate(cls, B):
269+
dcontrol_points = B.degree*np.diff(B.control_points, axis=0)
270+
return cls(dcontrol_points)
271+
257272
@property
258273
def control_points(self):
259274
"""The control points of the curve."""

lib/matplotlib/path.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,41 @@ def intersects_bbox(self, bbox, filled=True):
658658
return _path.path_intersects_rectangle(
659659
self, bbox.x0, bbox.y0, bbox.x1, bbox.y1, filled)
660660

661+
def signed_area(self, **kwargs):
662+
"""
663+
Get signed area filled by path.
664+
665+
All sub paths are treated as if they had been closed. That is, if there
666+
is a MOVETO without a preceding CLOSEPOLY, one is added.
667+
668+
Signed area means that if a path is self-intersecting, the drawing rule
669+
"even-odd" is used and only the filled area is counted.
670+
671+
Returns
672+
-------
673+
area : float
674+
The (signed) enclosed area of the path.
675+
"""
676+
area = 0
677+
prev_point = None
678+
prev_code = None
679+
start_point = None
680+
for cpoints, code in self.iter_curves(**kwargs):
681+
if code == Path.MOVETO:
682+
if prev_code is not None and prev_code is not Path.CLOSEPOLY:
683+
B = BezierSegment(np.array([prev_point, start_point]))
684+
area += B.arc_area()
685+
start_point = cpoints[0]
686+
B = BezierSegment(cpoints)
687+
area += B.arc_area()
688+
prev_point = cpoints[-1]
689+
prev_code = code
690+
if start_point is not None \
691+
and not np.all(np.isclose(start_point, prev_point)):
692+
B = BezierSegment(np.array([prev_point, start_point]))
693+
area += B.arc_area()
694+
return area
695+
661696
def interpolated(self, steps):
662697
"""
663698
Returns a new path resampled to length N x steps. Does not

0 commit comments

Comments
 (0)