在matplotlib中实现地平线图

10
我正在尝试在matplotlib中实现水平图表(请参见:http://square.github.com/cubism/)。
基本思路是将时间序列以较小的宽高比显示,并且随着值的增加(超过y轴限制),它们会以较暗的颜色从底部重新开始(想象一下旧的Atari游戏,当你超过屏幕顶部时,会从底部弹出)。
我的基本方法是将y数据分成块,并使用ax.twinx()在新的轴上绘制每个垂直组,并适当设置限制。
对于仅具有正或负数据,这似乎运作良好。
正数:

positive horizon chart

负面:

negative bar chart

但出于某些原因,两者一起做就会出问题:

# setup the environment
import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, np.pi*4, 137)
y = (2*np.random.normal(size=137) + x**2)

# reflect everything around the origin
xx = np.hstack([-1*x[::-1], x])
yy = np.hstack([-1*y[::-1], y])

# function to do the plot
def horizonPlot(ax, x, y, nfolds=3, inverty=False, color='CornflowerBlue'):
    axes = [ax]
    if inverty:
        ylims = np.linspace(y.min(), y.max(), nfolds + 1)[::-1]
    else:
        ylims = np.linspace(y.min(), y.max(), nfolds + 1)

    for n in range(1, nfolds):
        newax = axes[-1].twinx()
        axes.append(newax)

    for n, ax in enumerate(axes):        
        ax.fill_between(x, y, y2=ylims[n], facecolor=color, alpha=1.0/nfolds, zorder=n)
        ax.set_ylim([ylims[n], ylims[n+1]])
        ax.set_yticklabels([])
        ax.set_yticks([])

        if inverty:
            ax.invert_yaxis()

    ax.set_xlim([x.min(), x.max()])
    return fig

fig, baseax = plt.subplots(figsize=(6.5,1.5))
posax = baseax.twinx()
negax = posax.twinx()
fig = horizonPlot(posax, xx, np.ma.masked_less(yy, 0), inverty=False, color='DarkGreen')
fig = horizonPlot(negax, xx, np.ma.masked_greater(yy, 0), inverty=True,   color='CornflowerBlue')
for ax in fig.get_axes():
    ax.set_yticklabels([])

fig.tight_layout()
plt.show()

糟糕的图表(注意正面缺乏多个层次):

both horizon charts

任何想法都将不胜感激!

我考虑过实现立体派风格。一旦代码运行起来,我肯定会使用它;非常感谢。请问一下 zstart 参数是什么作用?当我在自己的电脑上运行时,图表看起来甚至更糟,最高层绘制在图表的外面并抛出了 tight_layout: falling back to Agg renderer 的错误提示。 - Milla Well
1
它确实有帮助,实际上:你能告诉我你的“糟糕图表”有什么问题吗?如果唯一的错误是绿色层没有绘制在彼此之上,那么在我的机器上这个工作正常!你试过注释掉tight_layout吗?[图片](http://tinypic.com/view.php?pic=51c7ko&s=6) - Milla Well
@Milla 是的,问题在于分层在正面出现了问题。本质上,我希望前两个图表可以相互叠加。 - Paul H
好的,那对我来说似乎可以工作 =) 你愿意发布这段代码吗?或者你介意我使用它吗? - Milla Well
让我们在聊天中继续这个讨论。点击此处进入聊天室 - Milla Well
显示剩余4条评论
1个回答

5

我其实不知道为什么你的代码不能工作,因为在我的电脑上它可以正常运行。但是,由于我真的很感兴趣这个绘图,所以我尝试着自己实现,而不需要所有这些花哨的 twinx 功能。

我只是将这些区域叠加在彼此之上,因为这实际上是这个绘图的伟大之处。因此,我不需要调整透明度,它们只会相加。

import numpy as np
from matplotlib.pyplot import *

def layer(y,height):
    neg=0.0;pos=0.0
    if y>0:
        if y-height>=0:
            pos=height
            y-= pos
        else : 
            pos = y
    elif y<0:
        if y+height<=0:
            neg=height
            y += neg
        else : 
            neg = -y
    return pos,neg

def horizonPlot(x,y,height=50.0,colors=['CornflowerBlue','DarkGreen']):
    alpha = .10
    vlayer = np.vectorize(layer)
    while (y != 0).any():
        l = vlayer(y,height)
        y -= l[0];y += l[1]
        fill_between(x,0,l[0],color=colors[0], alpha=alpha)
        fill_between(x,height-l[1],height,color=colors[1], alpha=alpha)

def main():
    x = np.linspace(0, np.pi*4, 137)
    y = (2*np.random.normal(size=137) + x**2)
    xx = np.hstack([-1*x[::-1], x])
    yy = np.hstack([-1*y[::-1], y])
    horizonPlot(xx,yy)
    show()

在我的机器上,它看起来像下面这样。希望在你的机器上也能正常运行,但我只使用基本的绘图方法。 enter image description here

谢谢。这在我的MPL 1.2系统上运行良好。有趣的是,两个实现都在MPL 1.1上工作。希望我能找到时间做一个“git bisect”并追踪差异出现的地方。此外,我采用了你的实现并进行了一些改进。https://gist.github.com/phobson/5073171 - Paul H
就此而言,这个问题在所有版本大于v1.2.1的matplotlib中都已经得到了修复。 - pelson
这个实现有问题。尝试 y = np.array([0,100,-100,100,0]).T ; horizonPlot(range(5), y) - 问题在于你必须在每对跨越轴的y值之间添加一个x值。 - naught101

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接