Reputation: 449
http://matplotlib.org/examples/pylab_examples/ellipse_rotated.html.
I want to add lines along the outline of the overlapped area (the quasi-polygon) of those patches as shown in the above example. How can I achieve this without solving equations?
Upvotes: 1
Views: 2976
Reputation: 879291
Using only matplotlib (and its dependency, NumPy),
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.patches as patches
fig = plt.figure()
ax = plt.subplot(111, aspect='equal')
num_ellipses = 4
angles = np.linspace(0, 180, num_ellipses, endpoint=False)
ellipses = [patches.Ellipse((1, 1), 4, 2, a) for a in angles]
# The transform used for rendering the Ellipse as points depends on the
# axes. So you must add the Ellipses to an axes to fix its transform before
# calling any method that returns coordinates.
for e in ellipses:
e.set_alpha(0.1)
ax.add_artist(e)
# get the points on the ellipses in axes coordinates
axes_points = [e.get_verts() for e in ellipses]
# find which points are inside ALL the paths
all_axes_points = np.row_stack(axes_points)
# create the boolean mask using the points and paths in axes coordinates
in_all_ellipses = np.all(
[e.get_path().contains_points(all_axes_points, e.get_transform(), radius=0.0)
for e in ellipses], axis=0)
# find the points in data coordinates
transdata = ax.transData.inverted()
data_points = [transdata.transform(points) for points in axes_points]
all_data_points = np.row_stack(data_points)
intersection = all_data_points[in_all_ellipses]
# Finding the convex hull of `intersection` would be more robust, but adds another dependency
# import scipy.spatial as spatial
# import matplotlib.collections as mcoll
# hull = spatial.ConvexHull(intersection)
# # Draw a black outline around the intersection
# lc = mcoll.LineCollection(intersection[hull.simplices],
# colors='black', linewidth=2, zorder=5)
# ax.add_collection(lc)
# Instead, we can find the convex hull in this special case by ordering the points
# according to its angle away from (1,1)
idx = np.argsort(np.arctan2(intersection[:,1]-1, intersection[:,0]-1))
# use np.r_[idx, idx[0]] to append the first point to the end, thus connecting
# the outline
intersection = intersection[np.r_[idx, idx[0]]]
plt.plot(intersection[:, 0], intersection[:, 1], 'k', lw=2, zorder=5)
# Draw an outline around each ellipse just for fun.
for points in data_points:
plt.plot(points[:, 0], points[:, 1])
plt.xlim(-2, 4)
plt.ylim(-1, 3)
plt.show()
The main idea, above, is to use the path.contains_points
method to test if a
point is inside the path. Each ellipses has a path, and each ellipse is
approximately composed of points. If we collect all those points and test them
with path.contains_points
for each path, then the points which are contains in
all paths are in the intersection.
Note that if you zoom in on the corners of the intesected region, you'll see a
little bit of the intersection is missing. This is due to e.get_verts()
lacking high enough resolution. We could overcome that by using NumPy to compute
the points on the ellipses, but that might violate the spirit of the
"without solving equations" requirement.
You might be wondering how does matplotlib draw the ellipses in high
resolution and yet not provide access to the high resolution points themselves.
The "magic" is done in the draw
method, which uses a somewhat complex
algorithm to approximate parts of the ellipse by arcs. Unfortunately, the code
which does this only renders the arcs, it does not give you access to the arcs or the points in the arcs (as
far as I can see).
Upvotes: 1
Reputation: 56634
You might find Coloring Intersection of Circles/Patches in Matplotlib useful.
It uses the Shapely library to create the desired geometry, then renders it for display with matplotlib.
To get an angled ellipse in Shapely you can create a buffered point (a circle), then scale and rotate it.
Edit: I have been unable to install Shapely (Anaconda 3.4 64bit + Windows 10 + Shapely == GRRRR
) so this is untested:
import numpy as np
import shapely.geometry as sg
import shapely.affinity as sa
import descartes
import matplotlib.pyplot as plt
from functools import reduce
def shapely_ellipse(center, major_axis, minor_axis, rotation=0.):
el = sg.Point(0., 0.).buffer(1.) # a unit circle
el = sa.scale(el, major_axis/2, minor_axis/2) # make it elliptic
el = sa.rotate(el, rotation)
el = sa.translate(el, *center)
return el
def intersect(a, b):
return a.intersection(b)
def main():
# make ellipses
delta = 45. # degrees
angles = np.arange(0., 360. + delta, delta)
ells = [shapely_ellipse((1,1), 4, 2, a) for a in angles]
# find intersection
center = reduce(intersect, ells)
# convert to matplotlib patches
center = descartes.PolygonPatch(center, ec='k', alpha=0.5)
# plot it
# ax = plt.subplot(111, aspect='equal')
ax = plt.gca()
for e in ells:
e = descartes.PolygonPatch(e)
e.set_alpha(0.1)
ax.add_artist(e)
ax.add_patch(center)
ax.set_xlim(-2, 4)
ax.set_ylim(-1, 3)
ax.set_aspect('equal')
plt.show()
if __name__ == "__main__":
main()
Upvotes: 3