使用matplotlib/pyplot绘制具有混合线颜色的曲线图

5
我想从一种颜色开始,逐渐混合到另一种颜色,直到结束。下面是我的MCVE中的函数,它可以工作,但肯定还有更好的方法我还没有发现吧?!
import numpy as np
import matplotlib.pyplot as plt

def colorlist(color1, color2, num):
    """Generate list of num colors blending from color1 to color2"""
    result = [np.array(color1), np.array(color2)]
    while len(result) < num:
        temp = [result[0]]
        for i in range(len(result)-1):
            temp.append(np.sqrt((result[i]**2+result[i+1]**2)/2))
            temp.append(result[i+1])
        result = temp
    indices = np.linspace(0, len(result)-1, num).round().astype(int)
    return [result[i] for i in indices]

x = np.linspace(0, 2*np.pi, 100)
y = np.sin(x)
colors = colorlist((1, 0, 0), (0, 0, 1), len(x))

for i in range(len(x)-1):
    xi = x[i:i+1+1]
    yi = y[i:i+1+1]
    ci = colors[i]
    plt.plot(xi, yi, color=ci, linestyle='solid', linewidth='10')

plt.show()

1

2个回答

10

不确定“更好的方式”指的是什么。与使用多个线条绘制相比,一种代码更少、绘图更快的解决方案是使用LineCollection与colormap。

colormap可以由两种颜色定义,其中任何中间颜色都会自动插值。

cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", [(1, 0, 0), (0, 0, 1)])

LineCollection可以用来一次性绘制很多条线。作为ScalarMappable,它可以使用一个colormap根据某个array对每条线进行不同的着色-在这种情况下,可以只使用x值来实现。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import LinearSegmentedColormap

x = np.linspace(0, 2*np.pi, 100)
y = np.sin(x)

cmap = LinearSegmentedColormap.from_list("", [(1, 0, 0), (0, 0, 1)])

points = np.array([x, y]).T.reshape(-1,1,2)
segments = np.concatenate([points[:-1],points[1:]], axis=1)

lc = LineCollection(segments, cmap=cmap, linewidth=10)
lc.set_array(x)
plt.gca().add_collection(lc)
plt.gca().autoscale()
plt.show()

在此输入图片描述

如图所示,该解决方案的缺陷在于各个线条没有很好地连接起来。

因此,为了避免这种情况,可以将这些点绘制成重叠的形式,使用

segments = np.concatenate([points[:-2],points[1:-1], points[2:]], axis=1)

这里输入图片描述


以上示例中,颜色在两个给定颜色之间进行线性插值。因此,该绘图与某些自定义插值使用的问题不同。

这里输入图片描述

要获取与问题中相同的颜色,您可以使用相同的函数来创建LineCollection所用的色彩映射中使用的颜色。如果目标是简化此函数,则可以直接计算通道中颜色差的平方根以获得相应数值。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import LinearSegmentedColormap

x = np.linspace(0, 2*np.pi, 100)
y = np.sin(x)

def colorlist2(c1, c2, num):
    l = np.linspace(0,1,num)
    a = np.abs(np.array(c1)-np.array(c2))
    m = np.min([c1,c2], axis=0)
    s  = np.sign(np.array(c2)-np.array(c1)).astype(int)
    s[s==0] =1
    r = np.sqrt(np.c_[(l*a[0]+m[0])[::s[0]],(l*a[1]+m[1])[::s[1]],(l*a[2]+m[2])[::s[2]]])
    return r

cmap = LinearSegmentedColormap.from_list("", colorlist2((1, 0, 0), (0, 0, 1),100))

points = np.array([x, y]).T.reshape(-1,1,2)
segments = np.concatenate([points[:-2],points[1:-1], points[2:]], axis=1)

lc = LineCollection(segments, cmap=cmap, linewidth=10)
lc.set_array(x)
plt.gca().add_collection(lc)
plt.gca().autoscale()
plt.show()

输入图像描述


如何将其更改为适用于Y轴值? - Tim Stack
@TimStack 你可以使用类似于 colors = [cmap(k) for k in y[:-1]]lc = LineCollection(segments, colors=colors, linewidth=10) 的东西。 - Not_a_programmer
@Not_a_programmer 我担心这只会改变颜色的色调。 - Tim Stack
@TimStack 看来我误解了你的问题。我以为你的问题是关于根据线条的 y 值改变其颜色。这就是我的代码意图。然而,我忘记提到 y 值必须被标准化,就像在这篇帖子中所示。 - Not_a_programmer
@Not_a_programmer 没错。然而,使用上述代码和您的修改,本帖中最后一个图形的色调已经改变,并且仍然依赖于X轴。 - Tim Stack
@TimStack,我在下面添加了我的代码作为答案。它是否达到你的要求? - Not_a_programmer

0
回应上面的评论:如果您想根据 y 值更改颜色,可以使用以下代码:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import LinearSegmentedColormap

x = np.linspace(0, 2 * np.pi, 100)
y = np.sin(x)
ynorm = (y - y.min()) / (y.max() - y.min())


def colorlist2(c1, c2, num):
    l = np.linspace(0, 1, num)
    a = np.abs(np.array(c1) - np.array(c2))
    m = np.min([c1, c2], axis=0)
    s = np.sign(np.array(c2) - np.array(c1)).astype(int)
    s[s == 0] = 1
    r = np.sqrt(np.c_[(l * a[0] + m[0])[::s[0]],
                      (l * a[1] + m[1])[::s[1]], (l * a[2] + m[2])[::s[2]]])
    return r


cmap = LinearSegmentedColormap.from_list(
    "", colorlist2((1, 0, 0), (0, 0, 1), 100))
colors = [cmap(k) for k in ynorm[:-1]]

points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-2], points[1:-1], points[2:]], axis=1)

lc = LineCollection(segments, colors=colors, linewidth=10)
lc.set_array(x)
plt.gca().add_collection(lc)
plt.gca().autoscale()
plt.show()

这将输出以下图表:

根据 y 值的不同颜色显示的图表


我运行了你发布的代码,但是结果图仍然具有依赖于x轴的颜色..奇怪! - Tim Stack
你确定你使用了y值作为颜色输入而不是x值吗?这是我能想到的唯一可能。 - Not_a_programmer
我只是简单地复制了你的代码,没有做任何修改。 - Tim Stack
这很奇怪,因为您的图形的颜色映射也不同。恐怕我对出了什么问题已经没有更多的想法了。抱歉,我无法帮助您。 - Not_a_programmer

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