matplotlib路径线宽连接到图缩放 [英] matplotlib path linewidth connected to figure zoom
问题描述
是否可以将matplotlib路径的线宽绑定到图形缩放/缩放级别?
Is it possible to tie the linewidth of a matplotlib path to the figure zoom/scale level?
我正在绘制一张地图,其中matplotlib路径(带有贝塞尔曲线)在地图上绘制了道路.放大时,我希望放大路径的宽度.
I am drawing a map where the matplotlib path (with bezier curves) draws the road on the map. Upon zooming in I would like the width of the path to zoom in.
在附加的脚本中,多边形近似可以正确缩放,但路径(红线)不能缩放(宽度).
In attached script, the polygonal approximation can properly zoom, but the path (red line) cannot zoom (in width).
是否可以将线宽绑定到一些缩放转换并通过回调重绘?
Is it possible to tie the linewidth to some scale transformation and redraw via callback ?
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()
推荐答案
据我所知,在 matplotlib 中没有办法做到这一点,因为线的笔触宽度不能直接与数据坐标相关联.(正如您所提到的,您可以将回调连接到绘制事件并完成此操作.不过,这会导致很大的性能损失.)
To the best of my knowledge, there's no way to do this in matplotlib, as the stroke width of a line cannot be directly tied to data coordinates. (As you mentioned, you could connect a callback to the draw event and accomplish this. It would incur a large performance penalty, though.)
但是,一个快速的解决方法是使用 shapely
通过缓冲街道路径来生成多边形.
However, a quick workaround would be to use shapely
to generate polygons by buffering your street paths.
作为一个简单的例子:
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()
如果你确实想走回调路线,你可以这样做:
If you did want to take the callback route, you might do something like this:
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()
这篇关于matplotlib路径线宽连接到图缩放的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!