在matplotlib中,如何绘制多色线,如彩虹

2024-04-26

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

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

Zoomed view of smart offsetting: issue with sharp angles in line

Overlaying lines of varying thickness: the inbuilt linewidth functionality sorts this automatically, somehow


绘制平行线并不是一件容易的事。使用简单的统一偏移当然不会显示出所需的结果。如下左图所示。
这样一个简单的偏移量可以在 matplotlib 中生成,如下所示改造教程 http://matplotlib.org/users/transforms_tutorial.html#using-offset-transforms-to-create-a-shadow-effect.

Method1

更好的解决方案可能是使用右侧概述的想法。计算偏移量n我们可以使用该点之间的线的法向量n-1圣和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()

Method2

To avoid overlapping lines, one has to use a more complicated solution. One could first offset every point normal to the two line segments it is part of (green points in the picture below). Then calculate the line through those offset points and find their intersection. enter image description here

一种特殊情况是当两个连续线段的斜率相等时。必须注意这一点(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 可能更适合。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在matplotlib中,如何绘制多色线,如彩虹 的相关文章

随机推荐