简体   繁体   中英

How can I get the outline of the overlapping patches in matplotlib without maths

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?

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()

在此处输入图片说明

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).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM