我正在尝试使用Python和Matplotlib创建Origin(见下图)制作的瀑布图。
一般方案对我来说很有意义,您可以从2D矩阵开始,就像要绘制表面图一样,然后可以遵循the StackOverflow question here中显示的任何配方。这个想法是在3D空间中将矩阵的每一条线绘制为一条单独的曲线。
此matplotlib方法生成的图如下所示:
我所遇到的困难是,在matplotlib版本中失去了Origin图中清晰的透视感。您可以说这部分是由于摄像机角度引起的,但我认为更重要的是它来自于距离较远的线“前面”出现的距离较近的线。
我的问题是,您如何通过透视效果正确地模仿Matplotlib中Origin的瀑布图?我不太了解这两个图有什么不同,所以即使定义确切的问题也很困难。
答案 0 :(得分:2)
更新:由于您现在已经更新了问题以使您更清楚地了解问题所在,所以让我演示三种绘制此类数据的不同方法,这些方法各有利弊。
一般要点(至少对我来说是 !)是matplotlib
在3D中是坏,尤其是在创建可发布图形时>(再次,我个人认为,您的里程可能会有所不同。)
我的工作:我使用了您发布的第二张图片后面的原始数据。在所有情况下,我都使用zorder
并添加了多边形数据(在2D:fill_between()
中,在3D:PolyCollection
中)来增强“ 3D效果”,即启用“在图形前面绘制”彼此”。下面的代码显示:
plot_2D_a()
使用颜色表示角度,因此保持原始y轴;尽管从技术上讲,这现在只能用于读出最重要的折线图,但它仍然使读者对y比例有“感觉”。 plot_2D_b()
删除了不必要的棘刺/刻度,而是将角度添加为文本标签;这最接近您发布的第二张图片plot_3D()
使用mplot3d
绘制“ 3D”图;虽然现在可以旋转它来分析数据,但在尝试缩放时(至少对我来说)会中断,从而产生截止数据和/或隐藏轴。最后,有{em>许多方法可以在matplotlib
中实现瀑布图,并且您必须自己决定要做什么。就我个人而言,我可能大多数时候会plot_2D_a()
,因为它可以轻松地在或多或少“所有3维”中缩放,同时还保留了允许的正确轴(+ colorbar)一旦读者将其作为静态图片发布到某处 ,读者就会获取所有相关信息。
代码:
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()
答案 1 :(得分:1)
编辑:根据要求,我添加了生成下图的代码
前段时间,当我为我正在撰写的论文创建情节时,我确实遇到了这个问题。我基本上得到了与 Asmus 相同的答案,所以我不会告诉你如何实现它的细节,因为已经涵盖了,但是我已经添加了具有高度相关颜色映射而不是角度相关颜色映射的功能.下面的例子:
这可能是您想要添加的内容,也可能不是,但它有助于了解数据的实际 y 值,在创建这样的瀑布图时,在混合 y 轴和 z 轴时会丢失该值。
这是我用来生成它的代码:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.collections import LineCollection
from matplotlib.colors import ListedColormap, BoundaryNorm
# generate data: sine wave (x-y) with 1/z frequency dependency
Nx = 200
Nz = 91
x = np.linspace(-10, 10, Nx)
z = 0.1*np.linspace(-10, 10, Nz)**2 + 4
w = 2*np.pi # omega
y = np.zeros((Nx, Nz))
for i in range(Nz):
y[:, i] = np.cos(w*x/z[i]**0.5)/z[i]**0.2
# create waterfall plot
fig = plt.figure()
ax = fig.add_subplot(111)
for side in ['right', 'top', 'left']:
ax.spines[side].set_visible(False)
# some usefull parameters
highest = np.max(y)
lowest = np.min(y)
delta = highest-lowest
t = np.sqrt(abs(delta))/10 # a tuning parameter for the offset of each dataset
for i in np.flip(range(Nz)):
yi_ = y[:,i] # the y data set
yi = yi_ + i*t # the shifted y data set used for plotting
zindex = Nz-i # used to set zorder
# fill with white from the (shifted) y data down to the lowest value
# for good results, don't make the alpha too low, otherwise you'll get confusing blending of lines
ax.fill_between(x, lowest, yi, facecolor="white", alpha=0.5, zorder=zindex)
# cut the data into segments that can be colored individually
points = np.array([x, yi]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
# Create a continuous norm to map from data points to colors
norm = plt.Normalize(lowest, highest)
lc = LineCollection(segments, cmap='plasma', norm=norm)
# Set the values used for colormapping
lc.set_array(yi_)
lc.set_zorder(zindex)
lc.set_linewidth(1)
line = ax.add_collection(lc)
# print text indicating angle
delta_x = max(x)-min(x)
if (i)%10==0:
ax.text(min(x)-5e-2*delta_x, t*i, "$\\theta=%i^\\circ$"%i, horizontalAlignment="right")
# set limits, as using LineCollection does not automatically set these
ax.set_ylim(lowest, highest + Nz*t)
ax.set_xlim(-10, 10)
fig.colorbar(line, ax=ax)
plt.yticks([])
ax.yaxis.set_ticks_position('none')
fig.savefig("waterfall_plot_cmap")
我从官方 matplotlib 示例 here
中发现了如何从本教程中获取高度映射如果有人有兴趣,我也上传了生成黑白版本的代码到我的github