Reputation: 1894
I'm experimenting with matplotlib
to draw figures in figures in figures. Since squares are the most straight-forward to draw, I started with those. In the end I want to write a generator for polygons with a certain width. In the given example, this would be a 4-corner polygon with straight angles and width 1.
My current code plots the following, which is as expected and almost as desired.
Note there is a line between 2,2
and 2,3
which I think can be removed if this is done with a correct algorithm instead of the current code.
A summary of the above is a square boxed in two boxes with an amplitude increasing with 1
, assuming the the larger boxes are 'behind' the rest of the boxes.
The method of which I wrote the code producing the above is, well, not really a function. It's a darn ugly collection of points which happen to resemble hollow squares.
import matplotlib.path as mpath
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
INNER_AMPLITUDE = 1.0
OUTER_AMPLITUDE = 3.0
Path_in = mpath.Path
path_in_data = [
(Path_in.MOVETO, (INNER_AMPLITUDE, -INNER_AMPLITUDE)),
(Path_in.LINETO, (-INNER_AMPLITUDE, -INNER_AMPLITUDE)),
(Path_in.LINETO, (-INNER_AMPLITUDE, INNER_AMPLITUDE)),
(Path_in.LINETO, (INNER_AMPLITUDE, INNER_AMPLITUDE)),
(Path_in.CLOSEPOLY, (INNER_AMPLITUDE, -INNER_AMPLITUDE)),
]
codes, verts = zip(*path_in_data)
path_in = mpath.Path(verts, codes)
patch_in = mpatches.PathPatch(path_in, facecolor='g', alpha=0.3)
ax.add_patch(patch_in)
x, y = zip(*path_in.vertices)
line, = ax.plot(x, y, 'go-')
Path_out = mpath.Path
path_out_data = [
(Path_out.MOVETO, (OUTER_AMPLITUDE, -OUTER_AMPLITUDE)),
(Path_out.LINETO, (-OUTER_AMPLITUDE, -OUTER_AMPLITUDE)),
(Path_out.LINETO, (-OUTER_AMPLITUDE, OUTER_AMPLITUDE)),
(Path_out.LINETO, (OUTER_AMPLITUDE, OUTER_AMPLITUDE)),
(Path_out.LINETO, (OUTER_AMPLITUDE, OUTER_AMPLITUDE-INNER_AMPLITUDE)),
(Path_out.LINETO, (-(OUTER_AMPLITUDE-INNER_AMPLITUDE), OUTER_AMPLITUDE-INNER_AMPLITUDE)),
(Path_out.LINETO, (-(OUTER_AMPLITUDE-INNER_AMPLITUDE), -(OUTER_AMPLITUDE-INNER_AMPLITUDE))),
(Path_out.LINETO, (OUTER_AMPLITUDE-INNER_AMPLITUDE, -(OUTER_AMPLITUDE-INNER_AMPLITUDE))),
(Path_out.LINETO, (OUTER_AMPLITUDE-INNER_AMPLITUDE, OUTER_AMPLITUDE-INNER_AMPLITUDE)),
(Path_out.LINETO, (OUTER_AMPLITUDE, OUTER_AMPLITUDE-INNER_AMPLITUDE)),
(Path_out.CLOSEPOLY, (OUTER_AMPLITUDE, OUTER_AMPLITUDE-INNER_AMPLITUDE)),
]
codes, verts = zip(*path_out_data)
path_out = mpath.Path(verts, codes)
patch_out = mpatches.PathPatch(path_out, facecolor='r', alpha=0.3)
ax.add_patch(patch_out)
plt.title('Square in a square in a square')
ax.grid()
ax.axis('equal')
plt.show()
Do note I consider this off-topic for Code Review since I'm looking for extending my functionality, not just a re-write which is up to best-practices. I feel like I'm doing it completely the wrong way. First things first.
How should I draw polygons with a certain width using matplotlib
, assuming the polygon will be surrounded on the outside with a band of the same form and at least the same width and completely filled on the inside?
Upvotes: 3
Views: 151
Reputation: 12711
Handling polygons purely in matplotlib can be quite tedious. Luckily there is a very nice library for these kind of operations: shapely.
For your purposes the parallel_offset
function is the way to go.
The bounds of the polygons which you're interested in, are defined by ring1
, ring2
and ring3
:
import numpy as np
import matplotlib.pyplot as plt
import shapely.geometry as sg
from descartes.patch import PolygonPatch
# if I understood correctly you mainly need the difference d here
INNER_AMPLITUDE = 0.1
OUTER_AMPLITUDE = 0.2
d = OUTER_AMPLITUDE - INNER_AMPLITUDE
# fix seed, for reproducability
np.random.seed(11111)
# a function to produce a "random" polygon
def random_polygon():
nr_p = np.random.randint(7,15)
angle = np.sort(np.random.rand(nr_p)*2*np.pi)
dist = 0.3*np.random.rand(nr_p) + 0.5
return np.vstack((np.cos(angle)*dist, np.sin(angle)*dist)).T
# your input polygon
p = random_polygon()
# create a shapely ring object
ring1 = sg.LinearRing(p)
ring2 = ring1.parallel_offset(d, 'right', join_style=2, mitre_limit=10.)
ring3 = ring1.parallel_offset(2*d, 'right', join_style=2, mitre_limit=10.)
# revert the third ring. This is necessary to use it to procude a hole
ring3.coords = list(ring3.coords)[::-1]
# inner and outer polygon
inner_poly = sg.Polygon(ring1)
outer_poly = sg.Polygon(ring2, [ring3])
# create the figure
fig, ax = plt.subplots(1)
# convert them to matplotlib patches and add them to the axes
ax.add_patch(PolygonPatch(inner_poly, facecolor=(0,1,0,0.4),
edgecolor=(0,1,0,1), linewidth=3))
ax.add_patch(PolygonPatch(outer_poly, facecolor=(1,0,0,0.4),
edgecolor=(1,0,0,1), linewidth=3))
# cosmetics
ax.set_aspect(1)
plt.axis([-1.5, 1.5, -1.5, 1.5])
plt.grid()
plt.show()
Result:
Upvotes: 1