Python/Matplotlib - 快速更新坐标轴文本

5 投票
2 回答
4978 浏览
提问于 2025-04-16 18:03

我在一个wxpython窗口里有一个matplotlib的图形/画布。我想在鼠标移动的时候更新图上的一些信息。我已经连接了一个叫做'motion_notify_event'的事件来获取这些信息。

在下面的代码中,绘制了很多随机数据,然后在窗口的状态栏里显示了光标的x,y位置。这一切都很流畅,效果很好。不过,我其实想把这些信息显示在图的顶部。如果你取消注释cbUpdateCursor函数最后两行代码,就能看到我想要的效果。但是这样做后,光标移动的响应时间就变得非常慢(因为需要调用绘图函数,而数据量很大,但必须调用这个函数,不然文本就不会更新)。

我该怎么做才能加快这个速度,让光标位置可以在图上显示,但又不会让它变得那么慢呢?我觉得可能需要对bbox做点什么?

代码:

import wx
import numpy as np
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.figure import Figure
from matplotlib.widgets import Cursor
from matplotlib.backends.backend_wxagg import \
   FigureCanvasWxAgg as FigCanvas, \
   NavigationToolbar2WxAgg as NavigationToolbar

class wxPlotting(wx.Frame):
   title = 'Test'
   def __init__(self):
      wx.Frame.__init__(self, None, -1, self.title)
      self.time = np.arange(10000)
      self.data = np.random.random(10000)
      self.sb = self.CreateStatusBar()
      self.create_main_panel()
      self.axes.plot(self.time, self.data)
      self.canvas.draw()

   def create_main_panel(self):
      self.panel = wx.Panel(self)
      self.fig = Figure((5.0, 4.0), dpi=100)
      self.canvas = FigCanvas(self.panel, -1, self.fig)
      self.axes = self.fig.add_subplot(111)
      self.text = self.axes.text(0., 1.005, '', transform = self.axes.transAxes)
      self.cursor = Cursor(self.axes, useblit=True, color='red')
      self.canvas.mpl_connect('motion_notify_event', self.cbUpdateCursor)
      self.vbox = wx.BoxSizer(wx.VERTICAL)
      self.vbox.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
      self.panel.SetSizer(self.vbox)
      self.vbox.Fit(self)

   def cbUpdateCursor(self, event):
      if event.inaxes:
         text = 'x = %5.4f, y = %5.4f' % (event.xdata, event.ydata)
         self.sb.SetStatusText(text)
         #self.text.set_text(text)
         #self.canvas.draw()

if __name__ == '__main__':
   app = wx.PySimpleApp()
   app.frame = wxPlotting()
   app.frame.Show()
   app.MainLoop()

基本上,我想要的效果类似于使用pyplot时显示的文本,也就是在运行下面的代码时出现在右下角的文本:

代码:

import matplotlib.pyplot as plt
plt.plot(range(10000), range(10000))
plt.show()

编辑

在我的实际程序中,我希望静态文本是在matplotlib的坐标轴内,而不是在上面。所以我觉得我不能仅仅使用wxpython的静态文本来显示它。

2 个回答

0

你可以在上面加一个静态文本框,然后只需要更新它的标签就可以了:

import wx
import numpy as np
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.figure import Figure
from matplotlib.widgets import Cursor
from matplotlib.backends.backend_wxagg import \
   FigureCanvasWxAgg as FigCanvas, \
   NavigationToolbar2WxAgg as NavigationToolbar

class wxPlotting(wx.Frame):
   title = 'Test'
   def __init__(self):
      wx.Frame.__init__(self, None, -1, self.title)
      self.time = np.arange(10000)
      self.data = np.random.random(10000)
      self.sb = self.CreateStatusBar()
      self.create_main_panel()
      self.axes.plot(self.time, self.data)
      self.canvas.draw()

   def create_main_panel(self):
      self.panel = wx.Panel(self)
      self.fig = Figure((5.0, 4.0), dpi=100)
      self.canvas = FigCanvas(self.panel, -1, self.fig)
      self.axes = self.fig.add_subplot(111)
      self.text = self.axes.text(0., 1.005, '', transform = self.axes.transAxes)
      self.cursor = Cursor(self.axes, useblit=True, color='red')
      self.canvas.mpl_connect('motion_notify_event', self.cbUpdateCursor)
      self.vbox = wx.BoxSizer(wx.VERTICAL)
      self.cursor_pos = wx.StaticText(self.panel,-1, label="")
      self.vbox.Add(self.cursor_pos, 0, wx.LEFT | wx.TOP | wx.GROW)
      self.vbox.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
      self.panel.SetSizer(self.vbox)
      self.vbox.Fit(self)

   def cbUpdateCursor(self, event):
      if event.inaxes:
         text = 'x = %5.4f, y = %5.4f' % (event.xdata, event.ydata)
         self.sb.SetStatusText(text)
         self.cursor_pos.SetLabel(text)
         #self.text.set_text(text)
         #self.canvas.draw()

if __name__ == '__main__':
   app = wx.PySimpleApp()
   app.frame = wxPlotting()
   app.frame.Show()
   app.MainLoop()
5

你可以使用一种叫做“blitting”的技术,这个技术和这里的动画示例类似这里

在这种情况下,这样做会让性能提升非常明显,因为只需要重新绘制窗口的一小部分。

不过,我还没找到办法在文本后面加上灰色背景,这样才能和默认的图形背景匹配……不过性能确实很棒。

这是一个基于你上面代码的独立示例:

import wx
import numpy as np
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.figure import Figure
from matplotlib.widgets import Cursor
from matplotlib.backends.backend_wxagg import \
   FigureCanvasWxAgg as FigCanvas, \
   NavigationToolbar2WxAgg as NavigationToolbar

class wxPlotting(wx.Frame):
   title = 'Test'
   def __init__(self):
      wx.Frame.__init__(self, None, -1, self.title)
      self.time = np.arange(10000)
      self.data = np.random.random(10000)
      self.sb = self.CreateStatusBar()
      self.create_main_panel()
      self.axes.plot(self.time, self.data)
      self.background = self.canvas.copy_from_bbox(self.fig.bbox)
      self.canvas.draw()

   def create_main_panel(self):
      self.panel = wx.Panel(self)
      self.fig = Figure((5.0, 4.0), dpi=100)
      self.canvas = FigCanvas(self.panel, -1, self.fig)
      self.axes = self.fig.add_subplot(111)
      self.text = self.axes.text(0., 1.005, '', transform = self.axes.transAxes, animated=True)
      self.cursor = Cursor(self.axes, useblit=True, color='red')
      self.canvas.mpl_connect('motion_notify_event', self.cbUpdateCursor)
      self.vbox = wx.BoxSizer(wx.VERTICAL)
      self.vbox.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
      self.panel.SetSizer(self.vbox)
      self.vbox.Fit(self)

   def cbUpdateCursor(self, event):
      if event.inaxes:
         text = 'x = %5.4f, y = %5.4f' % (event.xdata, event.ydata)
         self.sb.SetStatusText(text)

         self.canvas.restore_region(self.background)
         self.text.set_text(text)
         self.axes.draw_artist(self.text)
         self.canvas.blit(self.text.get_window_extent())

if __name__ == '__main__':
   app = wx.PySimpleApp()
   app.frame = wxPlotting()
   app.frame.Show()
   app.MainLoop()

撰写回答