如何在matplotlib中绘制“双向宽度线”

6
如何使用matplotlib或pyqtgraph绘制这样的图形: two dirrections widths line AB线是一条双向街道,绿色部分表示从点A到点B的方向,红色部分表示从B到A的方向,每个部分的宽度表示交通量。宽度以点为单位测量,在不同的缩放级别或dpi设置下不会改变。
这只是一个示例,实际上我有数百条街道。这种类型的图表在许多交通软件中非常常见。我尝试使用matplotlib的patheffect,但结果令人沮丧:
from matplotlib import pyplot as plt
import matplotlib.patheffects as path_effects

x=[0,1,2,3]
y=[1,0,0,-1]
ab_width=20
ba_width=30

fig, axes= plt.subplots(1,1)
center_line, = axes.plot(x,y,color='k',linewidth=2)

center_line.set_path_effects(
[path_effects.SimpleLineShadow(offset=(0, -ab_width/2),shadow_color='g', alpha=1, linewidth=ab_width),
path_effects.SimpleLineShadow(offset=(0, ba_width/2), shadow_color='r', alpha=1, linewidth=ba_width),
path_effects.SimpleLineShadow(offset=(0, -ab_width), shadow_color='k', alpha=1, linewidth=2),
path_effects.SimpleLineShadow(offset=(0, ba_width), shadow_color='k', alpha=1, linewidth=2),
path_effects.Normal()])

axes.set_xlim(-1,4)
axes.set_ylim(-1.5,1.5)

enter image description here

我的一个想法是将每条线的每个部分作为独立的线来处理,并在更改缩放级别时重新计算其位置,但这太复杂且速度较慢。

是否有使用matplotlib或pyqtgraph绘制我想要的内容的简单方法?任何建议将不胜感激!


1
你的数字不起作用。 - Bart
抱歉,我会修好的。@Bart - Macer
不行,还是不起作用。为什么不使用stackoverflow提供的图像上传器呢? - Bart
1
请参见例如http://stackoverflow.com/a/28497025/3581217。 - Bart
1
谢谢,我不知道这个。@Bart - Macer
2个回答

4
如果您可以每行独立处理,那么使用fill_between函数很容易实现。
from matplotlib import pyplot as plt
import numpy as np

x=np.array([0,1,2,3])
y=np.array([1,0,0,-1])

y1width=-1
y2width=3
y1 = y + y1width
y2 = y + y2width

fig = plt.figure()
ax = fig.add_subplot(111)

plt.plot(x,y, 'k', x,y1, 'k',x,y2, 'k',linewidth=2)
ax.fill_between(x, y1, y, color='g')
ax.fill_between(x, y2, y, color='r')

plt.xlim(-1,4)
plt.ylim(-3,6)
plt.show()

在这里,我将中心线视为参考线(因此是负的y1width),但可以采用不同的方法。结果如下:

<code>fill_between</code> result.

如果线条“复杂”,最终在某个点相交,则必须使用关键字参数interpolate=True来正确填充交叉区域。另一个可能对您的用例有用的有趣参数是where,用于条件化区域,例如where=y1 < 0。有关更多信息,请查看文档


谢谢您的回答,但您可能没有完全理解我的需求。宽度应该以点为单位进行测量,并且在不同的缩放级别下不会改变。它用于交互式应用程序中,用户经常需要缩放和平移以观察不同的线条。在您的答案中,宽度在缩放后并不固定。同时,端点也存在一些差异。 - Macer
没错,我没有理解“常数宽度”的部分。为此,您需要捕获缩放事件,设置适当的阴影比例(通过增加或减少原始宽度),并重新绘制。请参考此代码片段以查看相关内容(相关但不完全符合您所需)。至于结束部分,这种“fill_between”方法已经无法再帮助了。 - rll

3
解决问题的一种方法是使用填充多边形、一些线性代数和微积分。主要思路是沿着你的x和y坐标绘制一个多边形,并沿着平移的坐标关闭并填充多边形。
以下是我的结果:Filled polygons along path 以下是代码:
from __future__ import division
import numpy
from matplotlib import pyplot, patches


def road(x, y, w, scale=0.005, **kwargs):
    # Makes sure input coordinates are arrays.
    x, y = numpy.asarray(x, dtype=float), numpy.asarray(y, dtype=float)
    # Calculate derivative.
    dx = x[2:] - x[:-2]
    dy = y[2:] - y[:-2]
    dy_dx = numpy.concatenate([
        [(y[1] - y[0]) / (x[1] - x[0])],
        dy / dx,
        [(y[-1] - y[-2]) / (x[-1] - x[-2])]
    ])
    # Offsets the input coordinates according to the local derivative.
    offset = -dy_dx + 1j
    offset =  w * scale * offset / abs(offset)
    y_offset = y + w * scale
    #
    AB = zip(
        numpy.concatenate([x + offset.real, x[::-1]]),
        numpy.concatenate([y + offset.imag, y[::-1]]),
    )
    p = patches.Polygon(AB, **kwargs)

    # Returns polygon.
    return p


if __name__ == '__main__':
    # Some plot initializations
    pyplot.close('all')
    pyplot.ion()

    # This is the list of coordinates of each point
    x = [0, 1, 2, 3, 4]
    y = [1, 0, 0, -1, 0]

    # Creates figure and axes.
    fig, ax = pyplot.subplots(1,1)
    ax.axis('equal')
    center_line, = ax.plot(x, y, color='k', linewidth=2)

    AB = road(x, y, 20, color='g')
    BA = road(x, y, -30, color='r')
    ax.add_patch(AB)
    ax.add_patch(BA)

计算每个数据点的偏移量的第一步是计算离散导数dy / dx。我喜欢使用复数符号处理Python中的向量,即A = 1 - 1j。这使得某些数学操作变得更加容易。
接下来要记住,导数给出曲线的切线,从线性代数可知,切线的法线是n=-dy_dx + 1j,使用复数符号。
确定偏移坐标的最后一步是确保法向量具有单位大小n_norm = n / abs(n)并乘以多边形的所需宽度。
现在我们已经获得了多边形中所有点的坐标,剩下的就很简单了。使用patches.Polygon并将其添加到绘图中。
此代码还允许您定义是否希望将补丁置于路线之上或之下。只需为宽度提供正值或负值。如果您想根据缩放级别和/或分辨率更改多边形的宽度,则可以调整scale参数。它还允许您自由地向补丁添加其他参数,例如填充模式、透明度等。

太好了!但仍然无法解决“缩放后重量不变”的问题。也许没有指令方式可以使用matplotlib来解决这个问题。我需要捕捉缩放事件,重新计算和重新绘制它。 - Macer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接