【问题标题】:In matplotlib, how can I plot a multi-colored line, like a rainbow在 matplotlib 中,我如何绘制一条多色线,如彩虹
【发布时间】:2017-11-18 20:03:15
【问题描述】:

我想绘制不同颜色的平行线。例如。而不是一条粗细为 6 的红线,我想要两条粗细为 3 的平行线,一条红色一条蓝色。 任何想法将不胜感激。
谢谢

即使使用智能偏移(如下所示),在连续点之间具有锐角的视图中仍然存在问题。

智能偏移的缩放视图:

不同粗细的叠加线:

【问题讨论】:

  • 我不确定礼仪。但我想知道是否有可能说出你为什么投反对票?
  • 在搜索中显示以前的努力总是有帮助的。见how to ask
  • 好的,谢谢。我之前的努力是在互联网上搜索了几个小时,但无济于事。我在任何地方都找不到关于这个主题的帖子,这似乎很奇怪,因为最初这个问题对我来说似乎很普通。我能找到的只是关于如何沿着线的长度改变颜色的东西。我也尝试将线绘制为多边形 - 但这似乎(a)一种非常迂回的方式并且(b)一点也不容易做到(至少对我来说不是:o)所以我想我会问在这里。
  • 我也尝试绘制同一条线,但其顶部的颜色和粗细不同。但这样一来,结果总是对称的,即一侧的颜色总是与另一侧的颜色相同(但内部可能有不同的颜色。
  • 你可以在这里上传你的情节并解释你不喜欢什么以及你希望它是怎样的。

标签: python matplotlib


【解决方案1】:

绘制平行线并非易事。使用简单的统一偏移量当然不会显示所需的结果。如下左图所示。
transformation tutorial所示,可以在matplotlib中生成这样一个简单的偏移量。

方法1

更好的解决方案可能是使用右侧的草图。要计算nth 点的偏移量,我们可以使用n-1st 和n+1st 点之间的线的法线向量,并使用沿该法线向量的相同距离来计算偏移点。

这种方法的优点是我们在原始线中的点数与偏移线中的点数相同。缺点是不完全准确,如图所示。

该方法在下面代码中的函数offset中实现。
为了使它对 matplotlib 绘图有用,我们需要考虑线宽应该独立于数据单元。线宽通常以点为单位给出,偏移量最好以相同的单位给出,例如可以满足问题的要求(“两条宽度为 3 的平行线”)。 因此,想法是使用ax.transData.transform 将坐标从数据转换为显示坐标。此外,o 点的偏移量可以转换为相同的单位:使用 dpi 和 ppi=72 的标准,显示坐标中的偏移量为o*dpi/ppi。应用显示坐标偏移后,逆变换 (ax.transData.inverted().transform) 允许进行反向变换。

现在问题的另一个维度:如何确保偏移量保持不变,而与图形的缩放和大小无关? 最后一点可以通过每次发生缩放事件时重新计算偏移量来解决。

这是用这种方法产生的彩虹曲线的样子。

这是生成图像的代码。

import numpy as np
import matplotlib.pyplot as plt

dpi = 100

def offset(x,y, o):
    """ Offset coordinates given by array x,y by o """
    X = np.c_[x,y].T
    m = np.array([[0,-1],[1,0]])
    R = np.zeros_like(X)
    S = X[:,2:]-X[:,:-2]
    R[:,1:-1] = np.dot(m, S)
    R[:,0] = np.dot(m, X[:,1]-X[:,0])
    R[:,-1] = np.dot(m, X[:,-1]-X[:,-2])
    On = R/np.sqrt(R[0,:]**2+R[1,:]**2)*o
    Out = On+X
    return Out[0,:], Out[1,:]


def offset_curve(ax, x,y, o):
    """ Offset array x,y in data coordinates
        by o in points """
    trans = ax.transData.transform
    inv = ax.transData.inverted().transform
    X = np.c_[x,y]
    Xt = trans(X)
    xto, yto = offset(Xt[:,0],Xt[:,1],o*dpi/72. )
    Xto = np.c_[xto, yto]
    Xo = inv(Xto)
    return Xo[:,0], Xo[:,1]


# some single points
y = np.array([1,2,2,3,3,0])    
x = np.arange(len(y))
#or try a sinus
x = np.linspace(0,9)
y=np.sin(x)*x/3.


fig, ax=plt.subplots(figsize=(4,2.5), dpi=dpi)

cols = ["#fff40b", "#00e103", "#ff9921", "#3a00ef", "#ff2121", "#af00e7"]
lw = 2.
lines = []
for i in range(len(cols)):
    l, = plt.plot(x,y, lw=lw, color=cols[i])
    lines.append(l)


def plot_rainbow(event=None):
    xr = range(6); yr = range(6); 
    xr[0],yr[0] = offset_curve(ax, x,y, lw/2.)
    xr[1],yr[1] = offset_curve(ax, x,y, -lw/2.)
    xr[2],yr[2] = offset_curve(ax, xr[0],yr[0], lw)
    xr[3],yr[3] = offset_curve(ax, xr[1],yr[1], -lw)
    xr[4],yr[4] = offset_curve(ax, xr[2],yr[2], lw)
    xr[5],yr[5] = offset_curve(ax, xr[3],yr[3], -lw)

    for i  in range(6):     
        lines[i].set_data(xr[i], yr[i])


plot_rainbow()

fig.canvas.mpl_connect("resize_event", plot_rainbow)
fig.canvas.mpl_connect("button_release_event", plot_rainbow)

plt.savefig(__file__+".png", dpi=dpi)
plt.show()


方法2

为避免重叠线,必须使用更复杂的解决方案。 可以首先偏移与它所属的两条线段垂直的每个点(下图中的绿点)。然后计算通过这些偏移点的线并找到它们的交点。

一个特殊的情况是两个连续线段的斜率相等。必须注意这一点(下面代码中的eps)。

from __future__ import division
import numpy as np
import matplotlib.pyplot as plt

dpi = 100

def intersect(p1, p2, q1, q2, eps=1.e-10):
    """ given two lines, first through points pn, second through qn,
        find the intersection """
    x1 = p1[0]; y1 = p1[1]; x2 = p2[0]; y2 = p2[1]
    x3 = q1[0]; y3 = q1[1]; x4 = q2[0]; y4 = q2[1]
    nomX = ((x1*y2-y1*x2)*(x3-x4)- (x1-x2)*(x3*y4-y3*x4)) 
    denom = float(  (x1-x2)*(y3-y4) - (y1-y2)*(x3-x4) )
    nomY = (x1*y2-y1*x2)*(y3-y4) - (y1-y2)*(x3*y4-y3*x4)
    if np.abs(denom) < eps:
        #print "intersection undefined", p1
        return np.array( p1 )
    else:
        return np.array( [ nomX/denom , nomY/denom ])


def offset(x,y, o, eps=1.e-10):
    """ Offset coordinates given by array x,y by o """
    X = np.c_[x,y].T
    m = np.array([[0,-1],[1,0]])
    S = X[:,1:]-X[:,:-1]
    R = np.dot(m, S)
    norm = np.sqrt(R[0,:]**2+R[1,:]**2) / o
    On = R/norm
    Outa = On+X[:,1:]
    Outb = On+X[:,:-1]
    G = np.zeros_like(X)
    for i in xrange(0, len(X[0,:])-2):
        p = intersect(Outa[:,i], Outb[:,i], Outa[:,i+1], Outb[:,i+1], eps=eps)
        G[:,i+1] = p
    G[:,0] = Outb[:,0]
    G[:,-1] = Outa[:,-1]
    return G[0,:], G[1,:]


def offset_curve(ax, x,y, o, eps=1.e-10):
    """ Offset array x,y in data coordinates
        by o in points """
    trans = ax.transData.transform
    inv = ax.transData.inverted().transform
    X = np.c_[x,y]
    Xt = trans(X)
    xto, yto = offset(Xt[:,0],Xt[:,1],o*dpi/72., eps=eps )
    Xto = np.c_[xto, yto]
    Xo = inv(Xto)
    return Xo[:,0], Xo[:,1]


# some single points
y = np.array([1,1,2,0,3,2,1.,4,3]) *1.e9   
x = np.arange(len(y))
x[3]=x[4]
#or try a sinus
#x = np.linspace(0,9)
#y=np.sin(x)*x/3.


fig, ax=plt.subplots(figsize=(4,2.5), dpi=dpi)

cols = ["r", "b"]
lw = 11.
lines = []
for i in range(len(cols)):
    l, = plt.plot(x,y, lw=lw, color=cols[i], solid_joinstyle="miter")
    lines.append(l)


def plot_rainbow(event=None):
    xr = range(2); yr = range(2); 
    xr[0],yr[0] = offset_curve(ax, x,y,  lw/2.)
    xr[1],yr[1] = offset_curve(ax, x,y, -lw/2.)

    for i  in range(2):     
        lines[i].set_data(xr[i], yr[i])


plot_rainbow()

fig.canvas.mpl_connect("resize_event", plot_rainbow)
fig.canvas.mpl_connect("button_release_event", plot_rainbow)

plt.show()

请注意,只要线之间的偏移小于线上后续点之间的距离,此方法就可以正常工作。否则方法 1 可能更适合。

【讨论】:

  • 看起来很棒 - 非常感谢!仍然存在问题(如上所述),但会尝试在此基础上找到解决方案。
  • 我在答案中添加了另一种方法,它应该更适合解决问题图中显示的问题。
  • 您对问题和解决方案(尤其是方法#2)的说明非常令人印象深刻。为graphical design中很常见的一类问题做了很多工作。
【解决方案2】:

我能想到的最好的方法是获取您的数据,生成一系列小偏移量,然后使用fill_between 制作您喜欢的任何颜色的波段。

我写了一个函数来做到这一点。我不知道您要绘制什么形状,所以这可能适合您,也可能不适合您。我在抛物线上对其进行了测试,并得到了不错的结果。您还可以使用颜色列表。

def rainbow_plot(x, y, spacing=0.1):
    fig, ax = plt.subplots()
    colors = ['red', 'yellow', 'green', 'cyan','blue']
    top = max(y)
    lines = []
    for i in range(len(colors)+1):
        newline_data = y - top*spacing*i
        lines.append(newline_data)
    for i, c in enumerate(colors):
        ax.fill_between(x, lines[i], lines[i+1], facecolor=c)
    return fig, ax

x = np.linspace(0,1,51)
y = 1-(x-0.5)**2
rainbow_plot(x,y)

【讨论】:

  • 这是一个很好的开始。有两个问题我不知道如何解决 - 一是厚度不同(取决于曲线的陡度);放大或缩小时它会变得更厚或更薄。会仔细考虑的。
  • my answer相同的偏移方法当然也可以应用于fill_between
猜你喜欢
  • 2017-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-19
  • 2021-04-18
  • 2019-05-25
  • 2021-04-04
相关资源
最近更新 更多