Matplotlib:如何有效地将大量线段着色为独立渐变

Dev*_*per 7 python gradient matplotlib

蟒蛇.matplotlib:如何有效地将大量线段着色为独立渐变?
目前,阅读和其他的东西; 没有一个是我们的答案!

我们有许多单独的线希望以渐变颜色绘制每个线.

如果您有多个字符串,则上面第一个链接中提到的解决方案不起作用.换句话说,改变颜色循环会影响绘图中的所有内容,而不是唯一感兴趣的行.这根本不是我们感兴趣的.

matplotlib站点的第二个链接使用每行的分段为多个.这不是一个好方法,因为对于大量的线路,比如说10000甚至更多; 即使你每行只选择10个段,结果也太大了!即便如此,生成的线条根本没有平滑的颜色!如果您将分段数量作为线段的函数以获得更好的渐变,那么结果将非常巨大!难以显示,难以正确保存为文件.

Joe*_*ton 8

一个(次要)加速将添加单个行集合而不是10000个单独的行集合.

只要所有线共享相同的色彩映射表,您就可以将它们分组为单个行集合,并且每个行仍然可以具有独立的渐变.

Matplotlib对于这类事情仍然很慢.它针对高质量输出进行了优化,而不是快速绘制时间.但是,你可以加快速度(约3倍).

所以,作为我认为你现在可能(?)这样做的一个例子:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
# Make random number generation consistent between runs
np.random.seed(5)

def main():
    numlines, numpoints = 2, 3
    lines = np.random.random((numlines, numpoints, 2))

    fig, ax = plt.subplots()
    for line in lines:
        # Add "num" additional segments to the line
        segments, color_scalar = interp(line, num=20)
        coll = LineCollection(segments)
        coll.set_array(color_scalar)
        ax.add_collection(coll)
    plt.show()

def interp(data, num=20):
    """Add "num" additional points to "data" at evenly spaced intervals and
    separate into individual segments."""
    x, y = data.T
    dist = np.hypot(np.diff(x - x.min()), np.diff(y - y.min())).cumsum()
    t = np.r_[0, dist] / dist.max()

    ti = np.linspace(0, 1, num, endpoint=True)
    xi = np.interp(ti, t, x)
    yi = np.interp(ti, t, y)

    # Insert the original vertices
    indices = np.searchsorted(ti, t)
    xi = np.insert(xi, indices, x)
    yi = np.insert(yi, indices, y)

    return reshuffle(xi, yi), ti

def reshuffle(x, y):
    """Reshape the line represented by "x" and "y" into an array of individual
    segments."""
    points = np.vstack([x, y]).T.reshape(-1,1,2)
    points = np.concatenate([points[:-1], points[1:]], axis=1)
    return points

if __name__ == '__main__':
    main()
Run Code Online (Sandbox Code Playgroud)

相反,我会建议沿着这些方向做一些事情(唯一的区别在于main函数):

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
# Make random number generation consistent between runs
np.random.seed(5)

def main():
    numlines, numpoints = 2, 3
    points = np.random.random((numlines, numpoints, 2))

    # Add "num" additional segments to each line
    segments, color_scalar = zip(*[interp(item, num=20) for item in points])

    segments = np.vstack(segments)
    color_scalar = np.hstack(color_scalar)

    fig, ax = plt.subplots()
    coll = LineCollection(segments)
    coll.set_array(color_scalar)
    ax.add_collection(coll)

    plt.show()

def interp(data, num=20):
    """Add "num" additional points to "data" at evenly spaced intervals and
    separate into individual segments."""
    x, y = data.T
    dist = np.hypot(np.diff(x - x.min()), np.diff(y - y.min())).cumsum()
    t = np.r_[0, dist] / dist.max()

    ti = np.linspace(0, 1, num, endpoint=True)
    xi = np.interp(ti, t, x)
    yi = np.interp(ti, t, y)

    # Insert the original vertices
    indices = np.searchsorted(ti, t)
    xi = np.insert(xi, indices, x)
    yi = np.insert(yi, indices, y)

    return reshuffle(xi, yi), ti

def reshuffle(x, y):
    """Reshape the line represented by "x" and "y" into an array of individual
    segments."""
    points = np.vstack([x, y]).T.reshape(-1,1,2)
    points = np.concatenate([points[:-1], points[1:]], axis=1)
    return points

if __name__ == '__main__':
    main()
Run Code Online (Sandbox Code Playgroud)

两个版本都生成相同的图:

在此输入图像描述


但是,如果我们将行数增加到10000,我们将开始看到性能的显着差异.

使用10000行,每行3个点,并在整个颜色渐变中插入额外的20个点(每行23个段),并查看将图形保存到png所需的时间:

Took 10.866694212 sec with a single collection
Took 28.594727993 sec with multiple collections
Run Code Online (Sandbox Code Playgroud)

因此,在这种特殊情况下,使用单行收集将提供少于3倍的加速.它不是一流的,但它总比没有好.

这是时间码和输出数字,无论它的价值如何(由于图纸的顺序不同,输出数字不完全相同.如果你需要控制z级别,你将不得不坚持使用单独的线条集合) :

在此输入图像描述

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
import time
# Make random number generation consistent between runs
np.random.seed(5)

def main():
    numlines, numpoints = 10000, 3
    lines = np.random.random((numlines, numpoints, 2))

    # Overly simplistic timing, but timeit is overkill for this exmaple
    tic = time.time()
    single_collection(lines).savefig('/tmp/test_single.png')
    toc = time.time()
    print 'Took {} sec with a single collection'.format(toc-tic)

    tic = time.time()
    multiple_collections(lines).savefig('/tmp/test_multiple.png')
    toc = time.time()
    print 'Took {} sec with multiple collections'.format(toc-tic)

def single_collection(lines):
    # Add "num" additional segments to each line
    segments, color_scalar = zip(*[interp(item, num=20) for item in lines])
    segments = np.vstack(segments)
    color_scalar = np.hstack(color_scalar)

    fig, ax = plt.subplots()
    coll = LineCollection(segments)
    coll.set_array(color_scalar)
    ax.add_collection(coll)
    return fig

def multiple_collections(lines):
    fig, ax = plt.subplots()
    for line in lines:
        # Add "num" additional segments to the line
        segments, color_scalar = interp(line, num=20)
        coll = LineCollection(segments)
        coll.set_array(color_scalar)
        ax.add_collection(coll)
    return fig

def interp(data, num=20):
    """Add "num" additional points to "data" at evenly spaced intervals and
    separate into individual segments."""
    x, y = data.T
    dist = np.hypot(np.diff(x - x.min()), np.diff(y - y.min())).cumsum()
    t = np.r_[0, dist] / dist.max()

    ti = np.linspace(0, 1, num, endpoint=True)
    xi = np.interp(ti, t, x)
    yi = np.interp(ti, t, y)

    # Insert the original vertices
    indices = np.searchsorted(ti, t)
    xi = np.insert(xi, indices, x)
    yi = np.insert(yi, indices, y)

    return reshuffle(xi, yi), ti

def reshuffle(x, y):
    """Reshape the line represented by "x" and "y" into an array of individual
    segments."""
    points = np.vstack([x, y]).T.reshape(-1,1,2)
    points = np.concatenate([points[:-1], points[1:]], axis=1)
    return points

if __name__ == '__main__':
    main()
Run Code Online (Sandbox Code Playgroud)

  • @sebix - 该解决方案与此解决方案相同。插值是这个是可选的。如果您想在原始顶点之间平滑过渡颜色,您将需要它。 (2认同)