matplotlib路径线宽连接到图缩放 [英] matplotlib path linewidth connected to figure zoom

查看:95
本文介绍了matplotlib路径线宽连接到图缩放的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

是否可以将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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆