用Matplotlib模仿Origin中的瀑布图 [英] Imitating the waterfall plots in Origin with Matplotlib

查看:375
本文介绍了用Matplotlib模仿Origin中的瀑布图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用Python和Matplotlib创建Origin(见下图)制作的瀑布图.

I am attempting to create the waterfall plots made by Origin (see image below) with Python and Matplotlib.

一般方案对我来说很有意义,您可以从2D矩阵开始,就好像您要绘制表面图一样,然后可以按照

The general scheme makes sense to me, you start with a 2D matrix as if you wanted to make a surface plot, and then you can follow any of the recipes shown in the StackOverflow question here. The idea is to plot each line of the matrix as an individual curve in 3D space.

此matplotlib方法生成的图如下所示:

This matplotlib method results in a plot like the one below:

我所遇到的困难是,在matplotlib版本中丢失了Origin图中清晰的透视感.您可以说这部分是由于摄像机的角度引起的,但我认为更重要的是它来自于距离较远的线前面"出现的距离较近的线.

The struggle I am having is that the sense of perspective that is clear in the Origin plot is lost in the matplotlib version. You can argue that this is partially due to the camera angle, but I think more importantly it comes from the closer lines appearing "in front" of the lines that are farther away.

我的问题是,您如何通过透视效果正确地模仿Matplotlib中Origin的瀑布图?我不太了解这两个图有什么不同,所以即使定义确切的问题也很困难.

My question is, how would you properly imitate the waterfall plot from Origin in Matplotlib with the perspective effect? I don't really understand what it is about the two plots that's so different, so even defining the exact problem is difficult.

推荐答案

更新:绘制这样的数据,它们都有很多优点和缺点. 一般要点(至少对我来说是 !)是matplotlib在3D中是,尤其是在创建可发布图形时(同样,我个人认为,您的里程可能会有所不同.)

Update: as you've now updated your question to make clearer what you're after, let me demonstrate three different ways to plot such data, which all have lots of pros and cons. The general gist (at least for me!) is that matplotlib is bad in 3D, especially when it comes to creating publishable figures (again, my personal opinion, your mileage may vary.)

我的工作:我使用了您发布的第二张图片后面的原始数据.在所有情况下,我都使用zorder并添加了多边形数据(在2D中:fill_between(),在3D中:PolyCollection)来增强"3D效果",即启用在彼此面前进行绘图".下面的代码显示:

What I did: I've used the original data behind the second image you've posted. In all cases, I used zorder and added polygon data (in 2D: fill_between(), in 3D: PolyCollection) to enhance the "3D effect", i.e. to enable "plotting in front of each other". The code below shows:

  • plot_2D_a()使用颜色指示角度,因此保持原始的y轴;尽管从技术上讲,这现在只能用来读出最重要的折线图,但它仍然使读者对y比例有一种感觉".
  • plot_2D_a() uses color to indicate angle, hence keeping the original y-axis; though this technically can now only be used to read out the foremost line plot, it still gives the reader a "feeling" for the y scale.

  • plot_2D_b()删除了不必要的棘刺/尖刺,而是将角度添加为文本标签;这最接近您发布的第二张图片
  • plot_2D_b() removes unnecessary spines/ticks and rather adds the angle as text labels; this comes closest to the second image you've posted

  • plot_3D()使用mplot3d绘制"3D"图;虽然现在可以旋转它来分析数据,但在尝试缩放时(至少对我来说)会中断,从而产生截止数据和/或隐藏轴.
  • plot_3D() uses mplot3d to make a "3D" plot; while this can now be rotated to analyze the data, it breaks (at least for me) when trying to zoom, yielding cut-off data and/or hidden axes.

最后,有许多方法可以在matplotlib中实现瀑布图,并且您必须自己决定要做什么.就我个人而言,大多数时候我可能会选择plot_2D_a(),因为它允许在或多或少所有3维"中轻松缩放,同时还保留了允许读者使用的正确轴(+ colorbar)一旦将其作为静态图片发布到某处 即可获取所有相关信息.

In the end there are many ways to achieve a waterfall plot in matplotlib, and you have to decide yourself what you're after. Personally, I'd probably us plot_2D_a() most of the time, since it allows for easy rescaling in more or less "all 3 dimensions" while also keeping proper axes (+colorbar) that allow the reader to get all relevant information once you publish it somewhere as a static image.

代码:

import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.collections import PolyCollection
import numpy as np


def offset(myFig,myAx,n=1,yOff=60):
    dx, dy = 0., yOff/myFig.dpi 
    return myAx.transData + mpl.transforms.ScaledTranslation(dx,n*dy,myFig.dpi_scale_trans)

## taken from 
## http://www.gnuplotting.org/data/head_related_impulse_responses.txt
df=pd.read_csv('head_related_impulse_responses.txt',delimiter="\t",skiprows=range(2),header=None)
df=df.transpose()

def plot_2D_a():
    """ a 2D plot which uses color to indicate the angle"""
    fig,ax=plt.subplots(figsize=(5,6))
    sampling=2
    thetas=range(0,360)[::sampling]

    cmap = mpl.cm.get_cmap('viridis')
    norm = mpl.colors.Normalize(vmin=0,vmax=360)

    for idx,i in enumerate(thetas):
        z_ind=360-idx ## to ensure each plot is "behind" the previous plot
        trans=offset(fig,ax,idx,yOff=sampling)

        xs=df.loc[0]
        ys=df.loc[i+1]

        ## note that I am using both .plot() and .fill_between(.. edgecolor="None" ..) 
        #  in order to circumvent showing the "edges" of the fill_between 
        ax.plot(xs,ys,color=cmap(norm(i)),linewidth=1, transform=trans,zorder=z_ind)
        ## try alpha=0.05 below for some "light shading"
        ax.fill_between(xs,ys,-0.5,facecolor="w",alpha=1, edgecolor="None",transform=trans,zorder=z_ind)

    cbax = fig.add_axes([0.9, 0.15, 0.02, 0.7]) # x-position, y-position, x-width, y-height
    cb1 = mpl.colorbar.ColorbarBase(cbax, cmap=cmap, norm=norm, orientation='vertical')
    cb1.set_label('Angle')

    ## use some sensible viewing limits
    ax.set_xlim(-0.2,2.2)
    ax.set_ylim(-0.5,5)

    ax.set_xlabel('time [ms]')

def plot_2D_b():
    """ a 2D plot which removes the y-axis and replaces it with text labels to indicate angles """
    fig,ax=plt.subplots(figsize=(5,6))
    sampling=2
    thetas=range(0,360)[::sampling]

    for idx,i in enumerate(thetas):
        z_ind=360-idx ## to ensure each plot is "behind" the previous plot
        trans=offset(fig,ax,idx,yOff=sampling)

        xs=df.loc[0]
        ys=df.loc[i+1]

        ## note that I am using both .plot() and .fill_between(.. edgecolor="None" ..) 
        #  in order to circumvent showing the "edges" of the fill_between 
        ax.plot(xs,ys,color="k",linewidth=0.5, transform=trans,zorder=z_ind)
        ax.fill_between(xs,ys,-0.5,facecolor="w", edgecolor="None",transform=trans,zorder=z_ind)

        ## for every 10th line plot, add a text denoting the angle. 
        #  There is probably a better way to do this.
        if idx%10==0:
            textTrans=mpl.transforms.blended_transform_factory(ax.transAxes, trans)
            ax.text(-0.05,0,u'{0}º'.format(i),ha="center",va="center",transform=textTrans,clip_on=False)

    ## use some sensible viewing limits
    ax.set_xlim(df.loc[0].min(),df.loc[0].max())
    ax.set_ylim(-0.5,5)

    ## turn off the spines
    for side in ["top","right","left"]:
        ax.spines[side].set_visible(False)
    ## and turn off the y axis
    ax.set_yticks([])

    ax.set_xlabel('time [ms]')

#--------------------------------------------------------------------------------
def plot_3D():
    """ a 3D plot of the data, with differently scaled axes"""
    fig=plt.figure(figsize=(5,6))
    ax= fig.gca(projection='3d')

    """                                                                                                                                                    
    adjust the axes3d scaling, taken from https://stackoverflow.com/a/30419243/565489
    """
    # OUR ONE LINER ADDED HERE:                to scale the    x, y, z   axes
    ax.get_proj = lambda: np.dot(Axes3D.get_proj(ax), np.diag([1, 2, 1, 1]))

    sampling=2
    thetas=range(0,360)[::sampling]
    verts = []
    count = len(thetas)

    for idx,i in enumerate(thetas):
        z_ind=360-idx

        xs=df.loc[0].values
        ys=df.loc[i+1].values

        ## To have the polygons stretch to the bottom, 
        #  you either have to change the outermost ydata here, 
        #  or append one "x" pixel on each side and then run this.
        ys[0] = -0.5 
        ys[-1]= -0.5

        verts.append(list(zip(xs, ys)))        

    zs=thetas

    poly = PolyCollection(verts, facecolors = "w", edgecolors="k",linewidth=0.5 )
    ax.add_collection3d(poly, zs=zs, zdir='y')

    ax.set_ylim(0,360)
    ax.set_xlim(df.loc[0].min(),df.loc[0].max())
    ax.set_zlim(-0.5,1)

    ax.set_xlabel('time [ms]')

# plot_2D_a()
# plot_2D_b()
plot_3D()
plt.show()

这篇关于用Matplotlib模仿Origin中的瀑布图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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