传递新数据时无法更新图形(“动态图”)

Posted

技术标签:

【中文标题】传递新数据时无法更新图形(“动态图”)【英文标题】:Unable to update figure when new data are passed ("dynamic plot") 【发布时间】:2021-02-17 10:04:41 【问题描述】:

我找不到在移动滑块时更新图表的解决方案(在 Jupyter 笔记本中)。该图显示了正弦和余弦函数,以及它们的总和。正弦和余弦通过 0 到 1 之间的滑块 (k) 的值进行加权:

正弦 = k * sin(x) 余弦 = (1-k) * cos(x)

要绘制的数组已正确更新,例如对于 k = 0.2:

np.max(graph.ysin), np.max(graph.ycos)
0.19997482553477502, 0.7995972339065481)

绘图不会更新,它们继续显示 k = .5(初始值)的曲线。

请注意,我不希望(尽可能)在每次滑块更改时重新创建整个图形,只是通过使用 Line2D.set_data 传递更新后的数组来更新绘图。

我尝试了什么:

添加plt.show ()调用(建议here) 添加fig.canvas.draw()(建议here) 添加%matplotlib notebook(建议here) 添加ax.relim() and ax.autoscale_view()(建议here)

以及它们的一些组合......

class DynamicGraph ():
    def __init__ (self, ratio):
        # Define x range
        self.x = np.linspace (-np.pi, np.pi, 100)
        # Compute and display values
        self.init_figure (ratio)

    # Set figure, axe and plots
    def init_figure (self, ratio):
        self.fig, self.ax = plt.subplots (figsize=(8,3))

        self.ax.xaxis.set_major_locator(mpl.ticker.MultipleLocator(np.pi/2))
        self.ax.xaxis.set_major_formatter(mpl.ticker.FuncFormatter(lambda  x,pos:f'x/np.pi $\pi$'))
        self.ax.yaxis.set_major_locator(mpl.ticker.MultipleLocator(.25))

        self.ax.axhline(-.5, color='grey', lw=.5)
        self.ax.axhline(0, color='grey', lw=.5)
        self.ax.axhline(.5, color='grey', lw=.5)
        self.ax.axvline(0, color='grey', lw=.5)

        self.show_plots (ratio, init=True)
        plt.legend ()
        return

    # Create/update plots
    def show_plots (self, ratio, init=False):
        self._compute_values_ (ratio)        
        if init:
            self.sin_plot, = self.ax.plot(self.x, self.ysin, color='grey', ls=':', label='sin');
            self.cos_plot, = self.ax.plot(self.x, self.ycos, color='grey', ls='--', label='cos');
            self.sum_plot, = self.ax.plot(self.x, self.ysum, color='green', label='sin+cos');
        else:
            self.sin_plot.set_data (self.x, self.ysin)
            self.cos_plot.set_data (self.x, self.ycos)
            self.sum_plot.set_data (self.x, self.ysum)        
        return

    # Compute values which change with the slider value
    def _compute_values_ (self, ratio):
        self.ysin = ratio*np.sin(self.x)
        self.ycos = (1-ratio)*np.cos(self.x)
        self.ysum = self.ysin + self.ycos
        return


# Create slider, listen to changes
slider = widgets.FloatSlider (min=0, max=1, value=.5)
slider. observe(lambda change: graph.show_plots (slider.value), names='value')

# Show slider and graph
display(slider)
graph = DynamicGraph (slider.value)

我希望对解决方案进行一些解释,例如如果这与特定上下文或版本等有关,请了解上述建议为何不适用。

【问题讨论】:

【参考方案1】:

经过多次尝试,我发现了这种有效的指令组合:

在代码开头添加 %matplotlib notebook 以允许 Jupyter notebook 上的交互模式(与我假设的 Jupyterlab 相反)。

更新数据后添加fig.canvas.draw()

这些说明似乎没用,但正如我看到它们经常使用的那样,它们可能会产生一些我忽略的影响:

plt.ion() 开启交互模式 fig.canvas.flush_events()fig.canvas.draw() 之后
%matplotlib notebook    
#plt.ion()    

class DynamicGraph ():
    def __init__ (self, ratio):
        # Define x range
        self.x = np.linspace (-np.pi, np.pi, 100)
        # Compute and display values
        self.init_figure (ratio)

    # Set figure, axe and plots
    def init_figure (self, ratio):
        self.fig, self.ax = plt.subplots (figsize=(8,3))

        self.ax.xaxis.set_major_locator(mpl.ticker.MultipleLocator(np.pi/2))
        self.ax.xaxis.set_major_formatter(mpl.ticker.FuncFormatter(lambda  x,pos:f'x/np.pi $\pi$'))
        self.ax.yaxis.set_major_locator(mpl.ticker.MultipleLocator(.25))

        self.ax.axhline(-.5, color='grey', lw=.5)
        self.ax.axhline(0, color='grey', lw=.5)
        self.ax.axhline(.5, color='grey', lw=.5)
        self.ax.axvline(0, color='grey', lw=.5)

        self.show_plots (ratio, init=True)
        plt.legend ()
        return

    # Create/update plots
    def show_plots (self, ratio, init=False):
        self._compute_values_ (ratio)        
        if init:
            self.sin_plot, = self.ax.plot(self.x, self.ysin, color='grey', ls=':', label='sin');
            self.cos_plot, = self.ax.plot(self.x, self.ycos, color='grey', ls='--', label='cos');
            self.sum_plot, = self.ax.plot(self.x, self.ysum, color='green', label='sin+cos');
        else:
            self.sin_plot.set_data (self.x, self.ysin)
            self.cos_plot.set_data (self.x, self.ycos)
            self.sum_plot.set_data (self.x, self.ysum)        
            self.fig.canvas.draw()
            #self.fig.canvas.flush_events()   
        return

    # Compute values which change with the slider value
    def _compute_values_ (self, ratio):
        self.ysin = ratio*np.sin(self.x)
        self.ycos = (1-ratio)*np.cos(self.x)
        self.ysum = self.ysin + self.ycos
        return


# Create slider, listen to changes
slider = widgets.FloatSlider (min=0, max=1, value=.5)
slider. observe(lambda change: graph.show_plots (slider.value), names='value')

# Show slider and graph
display(slider)
graph = DynamicGraph (slider.value)

【讨论】:

以上是关于传递新数据时无法更新图形(“动态图”)的主要内容,如果未能解决你的问题,请参考以下文章

无法使用新数据点更新 Pyqtgraph 图

为实时数据绘图实现 pyqtgraph

无法将 vuex 存储数据传递给图形

接收错误 #1452 - 无法添加或更新子行”

ubuntu10.10无法更新

使用更新的参数集无法从 VB 关闭或刷新访问报表