是否可以将matplotlib路径的线宽与图形缩放/比例级别联系起来?
我正在绘制一张地图,其中matplotlib路径(带有贝塞尔曲线)在地图上绘制道路。在放大时,我希望放大路径的宽度。
在附加脚本中,多边形近似可以正确缩放,但路径(红线)不能缩放(宽度)。
是否可以将线宽与某种比例转换联系起来并通过回调重绘?
import matplotlib.pyplot as plt
from matplotlib.path import Path
import matplotlib.patches as patches
import numpy as np
def main():
ax = plt.subplot(111)
verts = np.array([ (0., 0.), (0.5, .5), (1., 0.8), (0.8, 0.)])
codes = np.array([Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.LINETO ])
# Can this curve have zoomable width
path = Path(verts, codes)
patch = patches.PathPatch(path, fc='none', color='r', lw=4, zorder=3)
ax.add_patch(patch)
ax.plot(verts[:,0], verts[:,1], 'o--', lw=2, color='k', zorder=2)
# these will be polygonal approx that will have proper zoom
v=np.array([]).reshape((-1,2))
c=[]
for i in range(len(verts)-1):
vtmp, ctmp = line2poly(verts[[i,i+1],:],0.03)
v = np.vstack( (v,vtmp) )
c = np.concatenate( (c,ctmp) )
path_zoom = Path(v,c)
patch_zoom = patches.PathPatch(path_zoom, fc='r', ec='k', zorder=1, alpha=0.4)
ax.add_patch(patch_zoom)
ax.set_xlim(-0.1, 1.1)
ax.set_ylim(-0.1, 1.1)
plt.show()
def line2poly(line, width):
dx,dy = np.hstack(np.diff(line,axis=0)).tolist()
theta = np.arctan2(dy,dx)
print(np.hstack(np.diff(line,axis=0)).tolist())
print(np.degrees(theta))
s = width/2 * np.sin(theta)
c = width/2 * np.cos(theta)
trans = np.array([(-s,c),(s,-c),(s,-c),(-s,c)])
verts = line[[0,0,1,1],:]+trans
verts = np.vstack((verts, verts[0,:]))
codes = np.array([Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY])
return verts,codes
if __name__=='__main__':
main()
答案 0 :(得分:5)
据我所知,在matplotlib中无法做到这一点,因为行的笔划宽度不能直接与数据坐标相关联。 (正如您所提到的,您可以将回调连接到绘制事件并完成此操作。但这会导致很大的性能损失。)
但是,快速解决方法是使用shapely
通过缓冲街道路径来生成多边形。
作为一个简单的例子:
import shapely.geometry
import descartes
import matplotlib.pyplot as plt
lines = ([(0, 0), (1, 0), (0, 1)],
[(0, 0), (1, 1)],
[(0.5, 0.5), (1, 0.5)],
)
lines = shapely.geometry.MultiLineString(lines)
# "0.05" is the _radius_ in data coords, so the width will be 0.1 units.
poly = lines.buffer(0.05)
fig, ax = plt.subplots()
patch = descartes.PolygonPatch(poly, fc='gray', ec='black')
ax.add_artist(patch)
# Rescale things to leave a bit of room around the edges...
ax.margins(0.1)
plt.show()
如果您确实想要采用回调路线,可能会执行以下操作:
import matplotlib.pyplot as plt
def main():
lines = ([(0, 0), (1, 0), (0, 1)],
[(0, 0), (1, 1)],
[(0.5, 0.5), (1, 0.5)],
)
fig, ax = plt.subplots()
artists = []
for verts in lines:
x, y = zip(*verts)
line, = ax.plot(x, y)
artists.append(line)
scalar = StrokeScalar(artists, 0.1)
ax.callbacks.connect('xlim_changed', scalar)
ax.callbacks.connect('ylim_changed', scalar)
# Rescale things to leave a bit of room around the edges...
ax.margins(0.05)
plt.show()
class StrokeScalar(object):
def __init__(self, artists, width):
self.width = width
self.artists = artists
# Assume there's only one axes and one figure, for the moment...
self.ax = artists[0].axes
self.fig = self.ax.figure
def __call__(self, event):
"""Intended to be connected to a draw event callback."""
for artist in self.artists:
artist.set_linewidth(self.stroke_width)
@property
def stroke_width(self):
positions = [[0, 0], [self.width, self.width]]
to_inches = self.fig.dpi_scale_trans.inverted().transform
pixels = self.ax.transData.transform(positions)
points = to_inches(pixels) * 72
return points.ptp(axis=0).mean() # Not quite correct...
main()