在 matplotlib 中,如何绘制多色线,如彩虹 [英] In matplotlib, how can I plot a multi-colored line, like a rainbow

查看:27
本文介绍了在 matplotlib 中,如何绘制多色线,如彩虹的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

智能偏移的放大视图:

覆盖不同粗细的线条:

解决方案

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

方法一

更好的解决方案可能是使用右侧勾勒的想法.为了计算第 n 个点的偏移,我们可以使用法向量到 n-1st 和 n+1 之间的线st 点并沿该法向量使用相同的距离来计算偏移点.

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

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

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

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

这是生成图像的代码.

将 numpy 导入为 np导入 matplotlib.pyplot 作为 pltdpi = 100定义偏移量(x,y,o):""" 数组 x,y 由 o 给出的偏移坐标 """X = np.c_[x,y].Tm = 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出 = 开 + X返回输出[0,:],输出[1,:]def offset_curve(ax, x,y, o):""" 偏移数组 x,y 在数据坐标中by o in 点 """trans = ax.transData.transforminv = ax.transData.inverted().transformX = np.c_[x,y]Xt = 反式(X)xto, yto = offset(Xt[:,0],Xt[:,1],o*dpi/72.)Xto = np.c_[xto, yto]Xo = inv(Xto)返回 Xo[:,0], Xo[:,1]# 一些单点y = np.array([1,2,2,3,3,0])x = np.arange(len(y))#或者试试鼻窦炎x = np.linspace(0,9)y=np.sin(x)*x/3.图, ax=plt.subplots(figsize=(4,2.5), dpi=dpi)cols = ["#fff40b", "#00e103", "#ff9921", "#3a00ef", "#ff2121", "#af00e7"]lw = 2.行 = []对于我在范围内(len(cols)):l, = plt.plot(x,y, lw=lw, color=cols[i])行.附加(l)def plot_rainbow(事件=无):xr = 范围(6);年 = 范围(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)对于范围内的 i (6):行[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__ 导入师将 numpy 导入为 np导入 matplotlib.pyplot 作为 pltdpi = 100def intersect(p1, p2, q1, q2, eps=1.e-10):""" 给定两条线,第一条通过点 pn,第二条通过 qn,找到交点"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)如果 np.abs(denom) <每股收益:#print "intersection undefined", p1返回 np.array( p1 )别的:返回 np.array( [ nomX/denom , nomY/denom ])定义偏移量(x,y,o,eps=1.e-10):""" 数组 x,y 由 o 给出的偏移坐标 """X = np.c_[x,y].Tm = np.array([[0,-1],[1,0]])S = X[:,1:]-X[:,:-1]R = np.dot(m, S)范数 = np.sqrt(R[0,:]**2+R[1,:]**2)/o开 = R/范数Outa = On+X[:,1:]Outb = On+X[:,:-1]G = np.zeros_like(X)对于 xrange(0, len(X[0,:])-2) 中的 i:p = 相交(Outa[:,i], Outb[:,i], Outa[:,i+1], Outb[:,i+1], eps=eps)G[:,i+1] = pG[:,0] = Outb[:,0]G[:,-1] = Outa[:,-1]返回 G[0,:], G[1,:]def offset_curve(ax, x,y, o, eps=1.e-10):""" 偏移数组 x,y 在数据坐标中by o in 点 """trans = ax.transData.transforminv = ax.transData.inverted().transformX = np.c_[x,y]Xt = 反式(X)xto, yto = offset(Xt[:,0],Xt[:,1],o*dpi/72., eps=eps)Xto = np.c_[xto, yto]Xo = inv(Xto)返回 Xo[:,0], Xo[:,1]# 一些单点y = np.array([1,1,2,0,3,2,1.,4,3]) *1.e9x = np.arange(len(y))x[3]=x[4]#或者试试鼻窦炎#x = np.linspace(0,9)#y=np.sin(x)*x/3.图, ax=plt.subplots(figsize=(4,2.5), dpi=dpi)cols = ["r", "b"]lw = 11.行 = []对于我在范围内(len(cols)):l, = plt.plot(x,y, lw=lw, color=cols[i],solid_joinstyle="miter")行.附加(l)def plot_rainbow(事件=无):xr = 范围(2);年 = 范围(2);xr[0],yr[0] = offset_curve(ax, x,y, lw/2.)xr[1],yr[1] = offset_curve(ax, x,y, -lw/2.)对于范围内的我(2):行[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 可能更适合.

I would like to plot parallel lines with different colors. E.g. rather than a single red line of thickness 6, I would like to have two parallel lines of thickness 3, with one red and one blue. Any thoughts would be appreciated.
Merci

Even with the smart offsetting (s. below), there is still an issue in a view that has sharp angles between consecutive points.

Zoomed view of smart offsetting:

Overlaying lines of varying thickness:

解决方案

Plotting parallel lines is not an easy task. Using a simple uniform offset will of course not show the desired result. This is shown in the left picture below.
Such a simple offset can be produced in matplotlib as shown in the transformation tutorial.

Method1

A better solution may be to use the idea sketched on the right side. To calculate the offset of the nth point we can use the normal vector to the line between the n-1st and the n+1st point and use the same distance along this normal vector to calculate the offset point.

The advantage of this method is that we have the same number of points in the original line as in the offset line. The disadvantage is that it is not completely accurate, as can be see in the picture.

This method is implemented in the function offset in the code below.
In order to make this useful for a matplotlib plot, we need to consider that the linewidth should be independent of the data units. Linewidth is usually given in units of points, and the offset would best be given in the same unit, such that e.g. the requirement from the question ("two parallel lines of width 3") can be met. The idea is therefore to transform the coordinates from data to display coordinates, using ax.transData.transform. Also the offset in points o can be transformed to the same units: Using the dpi and the standard of ppi=72, the offset in display coordinates is o*dpi/ppi. After the offset in display coordinates has been applied, the inverse transform (ax.transData.inverted().transform) allows a backtransformation.

Now there is another dimension of the problem: How to assure that the offset remains the same independent of the zoom and size of the figure? This last point can be addressed by recalculating the offset each time a zooming of resizing event has taken place.

Here is how a rainbow curve would look like produced by this method.

And here is the code to produce the image.

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.

A particular case would be when the slopes of two subsequent line segments equal. This has to be taken care of (eps in the code below).

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

Note that this method should work well as long as the offset between the lines is smaller then the distance between subsequent points on the line. Otherwise method 1 may be better suited.

这篇关于在 matplotlib 中,如何绘制多色线,如彩虹的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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