在matplotlib中滚动/翻页查看一组二维热力图

5

我正在生成一组3D数据的2D热图。我希望能够有一种机制以交互方式浏览每个窗格。下面是一个简单的示例代码,我希望能够通过滑动条(或其他手段)交互地查看两个窗格(即z=[0,1])。使用matplotlib是否可能实现这一点,还是在生成图像文件后需要进行后处理?

import numpy as np
from matplotlib import pyplot as plt
data = np.random.randint(10, size=(5, 5, 2))
data_slice = np.zeros((5,5))
for i in range(0, 5):
  for j in range(0, 5):
     data_slice[i][j] = data[i][j][0]
plt.imshow(data_slice, cmap='hot', interpolation='nearest')
plt.show()

编辑:我希望能够以交互方式实现以下内容,并且看起来可能有重复,正试图自动完成此操作。


1
可能是如何交互式地更新matplotlib的imshow()窗口?的重复问题。 - Andrew
1
此问题询问如何手动更新matplotlib窗口,而建议的问题是如何自动更新。解决方案会有显著不同,因为在这里必须使用手动元素(例如答案中的滑块),而在自动更新中需要使用计时器(FuncAnimation)。 - ImportanceOfBeingErnest
2个回答

16

可以考虑使用类似@hashmuke的优秀答案中所述的滑块作为解决方案。在他的回答中他提到:

"滑块是连续的,而层索引是离散的整数[...]"

这促使我想到一种没有此限制并具有更加页面化外观和感觉的方案。

产生的结果是PageSlider。它继承了Slider类的功能,但是显示以1为起始的整数步骤的滑块。它接受页面数量numpages作为初始化参数,但除此之外,在外部看来像Slider一样运行。此外,它还提供了向前和向后按钮。

下面是一个类似于@hashmuke的示例。

import matplotlib.widgets
import matplotlib.patches
import mpl_toolkits.axes_grid1

class PageSlider(matplotlib.widgets.Slider):

    def __init__(self, ax, label, numpages = 10, valinit=0, valfmt='%1d', 
                 closedmin=True, closedmax=True,  
                 dragging=True, **kwargs):

        self.facecolor=kwargs.get('facecolor',"w")
        self.activecolor = kwargs.pop('activecolor',"b")
        self.fontsize = kwargs.pop('fontsize', 10)
        self.numpages = numpages

        super(PageSlider, self).__init__(ax, label, 0, numpages, 
                            valinit=valinit, valfmt=valfmt, **kwargs)

        self.poly.set_visible(False)
        self.vline.set_visible(False)
        self.pageRects = []
        for i in range(numpages):
            facecolor = self.activecolor if i==valinit else self.facecolor
            r  = matplotlib.patches.Rectangle((float(i)/numpages, 0), 1./numpages, 1, 
                                transform=ax.transAxes, facecolor=facecolor)
            ax.add_artist(r)
            self.pageRects.append(r)
            ax.text(float(i)/numpages+0.5/numpages, 0.5, str(i+1),  
                    ha="center", va="center", transform=ax.transAxes,
                    fontsize=self.fontsize)
        self.valtext.set_visible(False)

        divider = mpl_toolkits.axes_grid1.make_axes_locatable(ax)
        bax = divider.append_axes("right", size="5%", pad=0.05)
        fax = divider.append_axes("right", size="5%", pad=0.05)
        self.button_back = matplotlib.widgets.Button(bax, label=ur'$\u25C0$', 
                        color=self.facecolor, hovercolor=self.activecolor)
        self.button_forward = matplotlib.widgets.Button(fax, label=ur'$\u25B6$', 
                        color=self.facecolor, hovercolor=self.activecolor)
        self.button_back.label.set_fontsize(self.fontsize)
        self.button_forward.label.set_fontsize(self.fontsize)
        self.button_back.on_clicked(self.backward)
        self.button_forward.on_clicked(self.forward)

    def _update(self, event):
        super(PageSlider, self)._update(event)
        i = int(self.val)
        if i >=self.valmax:
            return
        self._colorize(i)

    def _colorize(self, i):
        for j in range(self.numpages):
            self.pageRects[j].set_facecolor(self.facecolor)
        self.pageRects[i].set_facecolor(self.activecolor)

    def forward(self, event):
        current_i = int(self.val)
        i = current_i+1
        if (i < self.valmin) or (i >= self.valmax):
            return
        self.set_val(i)
        self._colorize(i)

    def backward(self, event):
        current_i = int(self.val)
        i = current_i-1
        if (i < self.valmin) or (i >= self.valmax):
            return
        self.set_val(i)
        self._colorize(i)


if __name__ == "__main__":
    import numpy as np
    from matplotlib import pyplot as plt


    num_pages = 23
    data = np.random.rand(9, 9, num_pages)

    fig, ax = plt.subplots()
    fig.subplots_adjust(bottom=0.18)

    im = ax.imshow(data[:, :, 0], cmap='viridis', interpolation='nearest')

    ax_slider = fig.add_axes([0.1, 0.05, 0.8, 0.04])
    slider = PageSlider(ax_slider, 'Page', num_pages, activecolor="orange")

    def update(val):
        i = int(slider.val)
        im.set_data(data[:,:,i])

    slider.on_changed(update)

    plt.show() 

在这里输入图片描述


1
标签=ur'$\u25C0$'需要去掉"r"变成标签=u'$\u25C0$'。 标签=ur'$\u25B6$'需要去掉"r"变成标签=u'$\u25B6$'。 - donPablo

8

你可以按照安德鲁的评论所建议的那样对图层进行动画处理,或者您可以通过使用滑块手动浏览图层,方法如下:

import numpy as np
from matplotlib import pyplot as plt
from matplotlib.widgets import Slider

# generate a five layer data 
data = np.random.randint(10, size=(5, 5, 5))
# current layer index start with the first layer 
idx = 0

# figure axis setup 
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.15)

# display initial image 
im_h = ax.imshow(data[:, :, idx], cmap='hot', interpolation='nearest')

# setup a slider axis and the Slider
ax_depth = plt.axes([0.23, 0.02, 0.56, 0.04])
slider_depth = Slider(ax_depth, 'depth', 0, data.shape[2]-1, valinit=idx)

# update the figure with a change on the slider 
def update_depth(val):
    idx = int(round(slider_depth.val))
    im_h.set_data(data[:, :, idx])

slider_depth.on_changed(update_depth)

plt.show()

滑块是连续的,而层索引是离散整数,希望这不是问题。下面是生成的图像: enter image description here

除了使用 valfmt,是否有可能控制滑块旁边显示的值?在我的情况下,该值表示分钟数,但我想显示类似于“01:32”(即更易读的时间格式),而不是“92”。 - Filippo Bistaffa

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