【问题标题】:Matplotlib plot with variable line width具有可变线宽的 Matplotlib 图
【发布时间】:2018-08-24 01:08:01
【问题描述】:

是否可以在 matplotlib 中绘制具有可变线宽的线?例如:

from pylab import *
x = [1, 2, 3, 4, 5]
y = [1, 2, 2, 0, 0]
width = [.5, 1, 1.5, .75, .75]

plot(x, y, linewidth=width)

这不起作用,因为 linewidth 需要一个标量。

注意: 我知道 *fill_between()* 和 *fill_betweenx()*。因为这些仅填充 x 或 y 方向,所以这些对于您有斜线的情况并不公平。希望填充始终垂直于线。这就是为什么要寻找可变宽度线的原因。

【问题讨论】:

    标签: python matplotlib plot


    【解决方案1】:

    使用 LineCollections。一种按照this Matplotlib 示例的方式进行操作的方法是

    import numpy as np
    from matplotlib.collections import LineCollection
    import matplotlib.pyplot as plt
    x = np.linspace(0,4*np.pi,10000)
    y = np.cos(x)
    lwidths=1+x[:-1]
    points = np.array([x, y]).T.reshape(-1, 1, 2)
    segments = np.concatenate([points[:-1], points[1:]], axis=1)
    lc = LineCollection(segments, linewidths=lwidths,color='blue')
    fig,a = plt.subplots()
    a.add_collection(lc)
    a.set_xlim(0,4*np.pi)
    a.set_ylim(-1.1,1.1)
    fig.show()
    

    【讨论】:

    • 不错!因此,您将线切割成一系列片段并使用LineCollection 指定每个片段中的属性。
    • 完全不是我想要的,但这很酷,所以我投了赞成票:)
    • matplotlib 的学习曲线很陡峭 - 试图弄清楚如何将其调整到 x 轴包含 timestamps 并且显然段期望 float,而不是 timestamp 的图形。 ..任何线索?这正是我正在寻找的,除了无法用它实际生成图表......
    • 我建议发布一个新问题,链接到这个问题作为参考
    【解决方案2】:

    Giulio Ghirardo 的答案的替代方法是将线条分成几段,您可以使用 matplotlib 的内置 scatter 函数,该函数通过使用圆圈来构造线条:

    from matplotlib import pyplot as plt
    import numpy as np
    
    x = np.linspace(0,10,10000)
    y = 2 - 0.5*np.abs(x-4)
    lwidths = (1+x)**2 # scatter 'o' marker size is specified by area not radius 
    plt.scatter(x,y, s=lwidths, color='blue')
    plt.xlim(0,9)
    plt.ylim(0,2.1)
    plt.show()
    

    根据我的经验,我发现将线分成段有两个问题:

    1. 由于某种原因,这些段总是被非常细的白线分割。当使用大量线段时,这些线的颜色会与线段的颜色混合。因此,线条的颜色与预期的颜色不同。

    2. 它不能很好地处理非常尖锐的不连续性。

    【讨论】:

    • 关于您的问题 (1) 和 Gulio 回答的问题 (2),我找到了一种解决方法,将参数 antialiaseds=False 传递给 LineCollection 。
    【解决方案3】:

    您可以分别绘制线条的每一段,并使用其单独的线宽,例如:

    from pylab import *
    x = [1, 2, 3, 4, 5]
    y = [1, 2, 2, 0, 0]
    width = [.5, 1, 1.5, .75, .75]
    
    for i in range(len(x)-1):
        plot(x[i:i+2], y[i:i+2], linewidth=width[i])
    show()
    

    【讨论】:

    • 虽然这可行,但它有两个问题:1) 对于大型数据集(例如 10,000 个点),这会创建大约相同数量的线对象,这是渲染的负担。 2)连接看起来不太好,因为它们是由重叠的矩形角组成的。
    【解决方案4】:

    gg349 的回答效果很好,但会将线条分成许多部分,这通常会导致渲染效果不佳。

    下面是一个在宽度均匀时生成连续线的替代示例:

    import numpy as np
    import matplotlib.pyplot as plt
    
    fig, ax = plt.subplots(1)
    xs = np.cos(np.linspace(0, 8 * np.pi, 200)) * np.linspace(0, 1, 200)
    ys = np.sin(np.linspace(0, 8 * np.pi, 200)) * np.linspace(0, 1, 200)
    widths = np.round(np.linspace(1, 5, len(xs)))
    
    def plot_widths(xs, ys, widths, ax=None, color='b', xlim=None, ylim=None,
                    **kwargs):
        if not (len(xs) == len(ys) == len(widths)):
            raise ValueError('xs, ys, and widths must have identical lengths')
        fig = None
        if ax is None:
            fig, ax = plt.subplots(1)
    
        segmentx, segmenty = [xs[0]], [ys[0]]
        current_width = widths[0]
        for ii, (x, y, width) in enumerate(zip(xs, ys, widths)):
            segmentx.append(x)
            segmenty.append(y)
            if (width != current_width) or (ii == (len(xs) - 1)):
                ax.plot(segmentx, segmenty, linewidth=current_width, color=color,
                        **kwargs)
                segmentx, segmenty = [x], [y]
                current_width = width
        if xlim is None:
            xlim = [min(xs), max(xs)]
        if ylim is None:
            ylim = [min(ys), max(ys)]
        ax.set_xlim(xlim)
        ax.set_ylim(ylim)
    
        return ax if fig is None else fig
    
    plot_widths(xs, ys, widths)
    plt.show()
    

    【讨论】:

    • 这种实现在某些情况下可能很好,但是,如果您在 gg349 给出的正弦示例上尝试它,它会受到影响。两者看起来都不太好,而且速度很慢,因为它为每个线段添加了一个新的线对象,因为宽度在不断变化。
    猜你喜欢
    • 2011-11-23
    • 1970-01-01
    • 2018-11-02
    • 2015-03-27
    • 2022-01-05
    • 2012-03-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多