Matplotlib:如何有效地将大量线段着色为独立的渐变 [英] Matplotlib: How to colorize a large number of line segments as independent gradients, efficiently

查看:141
本文介绍了Matplotlib:如何有效地将大量线段着色为独立的渐变的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Python. matplotlib :如何有效地将大量线段着色为独立的渐变?
已经阅读> this this 和其他内容;他们都不是我们的答案!

Python.matplotlib: How to colorize a large number of line segments as independent gradients, efficiently?
Already, read this and this and other stuff; none of them is our answer!

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

We have a number of separate lines wish to plot each in gradient color.

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

The solution mentioned in first link above, does not work if you have more than one string of line. In other words, changing the color cycle affects everything in the plot not the only line of interest. This is not of our interest at all.

到matplotlib站点的第二个链接使用将每一行分割成许多行.这不是一个好方法,因为对于大量的行(例如10000甚至更多)而言;即使每行仅选择10个细分,结果也太大了!即使这样,最终产生的线条也不会完全平滑地着色!如果将分割数作为线段的函数以获得更好的渐变,则结果将非常巨大!难以显示,难以正确保存为文件.

The second link which is to the matplotlib site uses segmentation of each lines into many. This is not good approach because for a huge number of lines, say, 10000 or even more; even if you choose only 10 segments per line the result is too huge! Even then the resulting lines are not smoothly colored at all! If you make the number of segmentation a function of the line segments for better gradient, the resulting will be really huge! Hard to display, difficult to save as file properly.

推荐答案

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

One (minor) speedup would be adding a single line collection instead of 10000 separate line collections.

只要所有线条共享相同的颜色图,就可以将它们分组为一个线条集合,并且每个线条仍可以具有独立的渐变.

As long as all of the lines share the same colormap, you can group them into a single line collection, and each can still have an independent gradient.

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

Matplotlib is still slow for this sort of thing. It's optimized for quality output, rather than fast draw time. However, you can speed things up a bit (~3x).

因此,举一个我认为您现在正在(?)进行举动的示例:

So, as an example of how I think you're probably (?) doing it now:

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()

相反,我建议按照以下方式做点什么(唯一的区别是在main函数中):

Instead, I would reccomend doing something along these lines (the only differences are in the main function):

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()

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

Both versions generate an identical plot:

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

If we crank the number of lines up to 10000, though, we'll start to see significant differences in performance.

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

Using 10000 lines, with 3 points each and an additional 20 points interpolated throughout for the color gradient (23 segments in each line) and looking at the time it takes to save a figure to a png:

Took 10.866694212 sec with a single collection
Took 28.594727993 sec with multiple collections

因此,在这种特殊情况下,使用单行集合将使速度提高不到3倍.这不是恒星,但总比没有好.

So, using a single line collection will give a bit less than a 3x speedup in this particular case. It's not stellar, but it's better than nothing.

这是时序代码和输出图形,不管它的值多少(由于图形的顺序不同,输出图形也不完全相同.如果需要控制z电平,则必须坚持分开行集合):

Here's the timing code and the output figure, for whatever it's worth (The output figures aren't quite identical due to different orderings of the drawing. If you need control over z-level, you'll have to stick to separate line collections):

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()

这篇关于Matplotlib:如何有效地将大量线段着色为独立的渐变的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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