1.思路:
(1)利用FigureCanvasWxAgg嵌入canvas
(2)利用 matplotlib.figure 放入画布中,在figure中画出自己想要的图像
(3)动画利用matplotlib.animation实现,动画停止可以用stop停止
self.ani = animation.FuncAnimation(self.figure,
self.animate,
interval=1000,
blit=False,
init_func=self.init)
self.ani.event_source.stop()
(4)动态数据通过队列和异步线程放入queue中,利用animation取数据,然后重画图像。
(5)可视化显示数据,利用figure.canvas.mpl_connect来相应鼠标事件
self.axes.figure.canvas.mpl_connect('motion_notify_event', self.on_motion)
1.1.鼠标事件
# 当鼠标在子图范围内产生动作时,将触发鼠标事件,鼠标事件分为三种:
botton_press_event: 鼠标按下时触发
botton_release_event: 鼠标释放时触发
motion_notify_event: 时间移动时触发
1.2.鼠标事件的相关信息可以通过event对象的属性获得:
name: 事件名
button: 鼠标按键,1,2,3表示左中右按键,None表示没有按键
x,y: 表示鼠标在图表中的像素坐标
xdata,ydata:鼠标在数据坐标系的坐标
"""
2实现效果:
3.完整代码如下,保证运行:
# -*- coding: UTF-8 -*-
import threading
import queue
import time
import matplotlib.animation as animation
import matplotlib.pyplot as plt
import numpy as np
import wx
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
class MyFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, id=wx.ID_ANY, title=wx.EmptyString, pos=wx.DefaultPosition,
size=wx.Size(600, 200), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL)
self.SetBackgroundColour(wx.Colour('#EBEDEB'))
# 用来正常显示中文标签
plt.rcParams['font.sans-serif'] = ['SimHei']
# 用来正常显示负号
plt.rcParams['axes.unicode_minus'] = False
bSizer_ct_bt = wx.BoxSizer(wx.VERTICAL)
# bSizer
self.bSizer_anim = wx.BoxSizer(wx.VERTICAL)
self.figure = Figure(figsize=(5, 2), facecolor='#0B173B')
self.figure.subplots_adjust(left=0.1, top=0.8, bottom=0.3, right=0.9) # 调整边距和子图的间距
self.axes = self.figure.add_subplot(111, xlim=(0, 101), ylim=(0, 1), facecolor='#0B173B')
self.axes.set_aspect('auto') # 控制长宽比
self.axes.grid(color='#606061') # 是否要格子背景
self.axes.spines['left'].set_color('#9b9c9c')
self.axes.spines['right'].set_color('#9b9c9c')
self.axes.spines['top'].set_color('#9b9c9c')
self.axes.spines['bottom'].set_color('#9b9c9c')
self.axes.tick_params(axis='both', which='both', colors='#ffffff')
# self.axes.axhline(0.5, linestyle='-', color='#1e326d', lw=1)
# self.axes.axvline(20, linestyle='-', color='#239B3F', lw=1)
# 设置图片标题
self.axes.set_title('FigureCanvas横坐标动态更新的animation实现', color='#ffffff')
# 设置横坐标名称
# self.axes.set_xlabel('动态变化的横轴')
# 设置刻度标签
self.axes.set_yticks([0, 0.5, 1])
self.axes.set_yticklabels([0, 0.5, 1], rotation=0)
self.canvas1 = FigureCanvas(self, -1, self.figure)
self.bSizer_anim.Add(self.canvas1, 1, wx.ALL, 0)
bSizer_ct_bt.Add(self.bSizer_anim, 1, wx.ALL, 50)
self.SetSizer(bSizer_ct_bt)
self.Layout()
bSizer_ct_bt.Fit(self)
self.thisAy = []
self.thisx = []
self.thisy = []
self.text0 = self.axes.text(22, 0.8, '22, 0.0000', ha="left", va="bottom", color='#00FF00')
self.line0, = self.axes.plot([20, 20], [0, 1], '-', color='#239B3F', lw=1)
self.line, = self.axes.plot([], [], '-', color='#239B3F', lw=1)
self.ani = animation.FuncAnimation(self.figure,
self.animate,
interval=1000,
blit=False,
init_func=self.init)
# ani.save('jieky_animation.gif',writer='imagemagick')
# anim.event_source.stop()
# 常量
self.initCount = 0
self.destroy_flag = False
self.Show()
# event
self.axes.figure.canvas.mpl_connect('motion_notify_event', self.on_motion)
self.animQueue = queue.Queue()
# 线程加载数据
thread1 = threading.Thread(target=self.put_data_thread,
args=(self.animQueue,)) # 添加线程
thread1.start()
def put_data_thread(self, animQueue):
# 随机生成数组
num_list = np.random.rand(300)
for num in num_list:
time.sleep(0.1)
animQueue.put(num)
def on_motion(self, event):
print(self.thisAy)
x_val = event.xdata
print('9999999999999999999999999999999999999', x_val)
if x_val:
x_val = int(round(x_val))
self.axes.figure.canvas.draw()
self.line0.set_data([x_val, x_val], [0, 1])
showText = x_val
if len(self.thisAy) > x_val:
y_differ = self.thisAy[x_val]
showText = str(showText) + ' ' + str(y_differ)
self.text0.remove()
self.text0 = self.axes.text(x_val + 1, 0.8, showText, ha="left", va="bottom", color='#00FF00')
else:
pass
def stop_anim(self):
self.ani.event_source.stop()
self.destroy_flag = True
def init(self):
self.thisAy = []
self.thisx = [i for i in range(0, 101)]
self.thisy = (np.zeros(101, dtype=int) - 1).tolist()
self.line.set_data([], [])
# 设置x轴的范围
self.axes.set_xlim(min(self.thisx), max(self.thisx))
# 更新刻度,刻度只要早x轴的范围内就可以
self.axes.set_xticks([i for i in range(min(self.thisx), max(self.thisx) + 1, 20)])
# 设置刻度标签
self.axes.set_xticklabels(
[i if i >= 0 else '' for i in range(min(self.thisx), max(self.thisx) + 1, 20)],
rotation=0)
return self.line
def animate(self, *args):
print('animate............')
try:
animQueue = self.animQueue
while not animQueue.empty():
if self.destroy_flag:
break
num_y = animQueue.get()
print('animate get:', num_y)
# 清空重新绘制
if num_y == -1:
self.initCount = 0
self.init()
continue
else:
self.thisAy.append(num_y)
if self.initCount > 99:
del self.thisx[0]
del self.thisy[0]
self.thisx.append(max(self.thisx) + 1)
self.thisy.append(num_y)
else:
self.thisx[self.initCount] = self.initCount
self.thisy[self.initCount] = num_y
self.initCount += 1
except:
self.stop_anim()
return
# 设置x轴的范围
self.axes.set_xlim(min(self.thisx), max(self.thisx))
# 更新刻度,刻度只要早x轴的范围内就可以
self.axes.set_xticks([i for i in range(min(self.thisx), max(self.thisx) + 1, 20)])
# 设置刻度标签
self.axes.set_xticklabels(
[i if i >= 0 else '' for i in range(min(self.thisx),
max(self.thisx) + 1, 20)],
rotation=0)
# 重新渲染子图
try:
self.axes.figure.canvas.draw()
except:
self.stop_anim()
return
self.line.set_data(self.thisx, self.thisy)
return self.line
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(None)
frame.Show(True)
self.SetTopWindow(frame)
return True
if __name__ == '__main__':
app = MyApp()
app.MainLoop()
4.错误:Failed to convert value(s) to axis units
动画中放入的数据需要保证数据类型一致,即放float就保证数组都是float类型。
5.参考文章:
基于matplotlib.animation和python的流数据动态监控原型
对你有帮助就点个赞吧,有疑问的可以留言评论!一起成长!