Python绘图系统:
三维并不是人类理解的极限,毕竟我们生活在思维时空中。所以接下来要做的,就是四维图形——加一个时间轴。
实际上,这个项目创建之初就已经考虑到动图绘制的问题,毕竟默认的绘图坐标是txyz。但是,如果想实现动图绘制,就必须要先设置一个额外的线程,用于动态更新图像,否则绘图循环压迫主进程,会导致界面变得奇卡无比。
接下来,就开始实现绘制动图的需求,第一步,绘制UI。先在setFrmCtrl中添加
# 动画控制
frm = ttk.Frame(frmCtrl, width=320)
frm.pack(side=tk.TOP, fill=tk.X)
self.setAnimateFrame(frm)
然后实现具体的self.setAnimateFrame
def setAnimateFrame(self, frm):
pDct = dict(side=tk.LEFT, fill=tk.X, padx=2)
self.aniDelay = tk.StringVar()
self.aniDelay.set(100)
ttk.Label(frm, text="延时/毫秒").pack(**pDct)
ttk.Entry(frm, width=5, textvariable=self.aniDelay).pack(**pDct)
self.aniFrameNum = tk.StringVar()
self.aniFrameNum.set(100)
ttk.Label(frm, text="帧数").pack(**pDct)
ttk.Entry(frm, width=5, textvariable=self.aniFrameNum).pack(**pDct)
self.tIndex = 0 # 当前帧数
ttk.Button(frm, width=3, text= "⇦",
command=self.btnPreFrame).pack(**pDct)
ttk.Button(frm, width=3, text="▶",
command=self.btnAniStart).pack(**pDct)
ttk.Button(frm, width=3, text="⇨",
command=self.btnNextFrame).pack(**pDct)
def btnAniStart(self): pass
def btnPreFrame(self): pass
def btnNextFrame(self): pass
延时表示当自动绘制动图时,两帧间隔时间;帧数表示总共绘制的时间个数。三个按钮,分别用于向前一帧、向后一帧以及动态播放。
坐标t的工作原理和xyz并不相同,毕竟每次调用时间参数的时候,都只需要调用某一点的时间。所以,现有的设置坐标数据的方法就不适用了,需要更改readDatas函数
# 读取坐标轴al的数据
def readDatas(self, al):
dct = {}
data = {}
for flag in self.drawTypeDim.getDim():
data[flag] = al.setData(flag, **dct)
if flag=='t':
dct['t'] = data['t'][self.tIndex]
else:
dct[flag] = data[flag]
return data
然后再更新一下绘图函数:其实只是取消t作为绘图坐标轴的地位
def btnDrawImg(self):
self.fig.clf()
self.axDct = {}
for al in self.als:
ax = self.setDrawAxis(al)
data = self.readDatas(al)
draw = self.drawDct[al.getDrawType()]
style = al.getStyle()
keys = al.getDrawDim().replace('t',"")
draw(ax, data, keys, style)
self.fig.subplots_adjust(left=0.1, right=0.95, top=0.95, bottom=0.08)
self.canvas.draw()
最后,实现一下btnNextFrame
def btnNextFrame(self):
num = int(self.aniFrameNum.get())
self.tIndex = (self.tIndex + 1) % num
self.btnDrawImg()
效果如下,至此,我们离自动化动态绘图,只剩下一个多线程了。
本文只更改了ds.py中的源代码,其他代码可查看这篇博客:定制风格的绘图系统
import tkinter as tk
import tkinter.ttk as ttk
from tkinter.filedialog import askopenfilename
import matplotlib
import matplotlib as mpl
mpl.use('TkAgg')
import matplotlib.backends.backend_tkagg as mbb
from matplotlib.figure import Figure
import numpy as np
from alist import AxisList
from base import DrawType
class DarwSystem():
def __init__(self):
self.root = tk.Tk()
self.root.title("数据展示工具")
self.data = {}
self.als = []
self.initConst()
self.setFrmCtrl()
frmFig = ttk.Frame(self.root)
frmFig.pack(side=tk.LEFT,fill=tk.BOTH,expand=tk.YES)
self.setFrmFig(frmFig)
self.root.mainloop()
def initConst(self):
self.TYPES = ("点线图", "散点图", "条形图")
self.drawDct = {
"点线图" : self.drawPlot,
"散点图" : self.drawScatter,
"条形图" : self.drawBar
}
# ! 设置
def setFrmCtrl(self):
frmCtrl = ttk.Frame(self.root,width=320)
frmCtrl.pack(side=tk.RIGHT, fill=tk.Y)
# 主控Frame
frm = ttk.Frame(frmCtrl, width=320)
frm.pack(side=tk.TOP, fill=tk.X)
self.setCtrlButtons(frm)
# 动画控制
frm = ttk.Frame(frmCtrl, width=320)
frm.pack(side=tk.TOP, fill=tk.X)
self.setAnimateFrame(frm)
self.frmAxis = ttk.Frame(frmCtrl)
self.frmAxis.pack(side=tk.TOP, fill=tk.X)
self.addLast(None)
# ! 工具栏
def setCtrlButtons(self, frm):
self.drawTypeDim = DrawType(frm)
self.drawTypeDim.pack(side=tk.LEFT)
ttk.Button(frm, text="📈",width=3,
command=self.btnDrawImg).pack(side=tk.LEFT)
ttk.Button(frm, text="📂",width=3,
command=self.btnLoadData).pack(side=tk.LEFT)
btn = ttk.Button(frm, text="+", width=3)
btn.pack(side=tk.LEFT)
btn.bind("" , self.addLast)
btn = ttk.Button(frm, text="-", width=3)
btn.pack(side=tk.LEFT)
btn.bind("" , self.deleteLast)
# ! 动画控制
def setAnimateFrame(self, frm):
pDct = dict(side=tk.LEFT, fill=tk.X, padx=2)
self.aniDelay = tk.IntVar()
self.aniDelay.set(100)
ttk.Label(frm, text="延时/毫秒").pack(**pDct)
ttk.Entry(frm, width=5, textvariable=self.aniDelay).pack(**pDct)
self.aniFrameNum = tk.IntVar()
self.aniFrameNum.set(100)
ttk.Label(frm, text="帧数").pack(**pDct)
ttk.Entry(frm, width=5, textvariable=self.aniFrameNum).pack(**pDct)
self.tIndex = 0 # 当前帧数
ttk.Button(frm, width=3, text= "⇦",
command=self.btnPreFrame).pack(**pDct)
ttk.Button(frm, width=3, text="▶",
command=self.btnAniStart).pack(**pDct)
ttk.Button(frm, width=3, text="⇨",
command=self.btnNextFrame).pack(**pDct)
def btnAniStart(self): pass
def btnPreFrame(self): pass
def btnNextFrame(self):
num = self.aniFrameNum.get()
self.tIndex = (self.tIndex + 1) % num
self.btnDrawImg()
# 添加一组坐标系
def addLast(self, evt):
title = f"坐标{len(self.als)}"
al = AxisList(self.frmAxis, title, 1, [5,10,30],
self.TYPES, self.drawTypeDim.getDct())
al.pack(side=tk.TOP, fill=tk.X)
self.als.append(al)
# 删除一组坐标系
def deleteLast(self, evt):
self.als[-1].pack_forget()
del self.als[-1]
# 加载数据 // 暂时处于弃用状态
def btnLoadData(self):
name = askopenfilename()
data = np.genfromtxt(name)
for i, flag in enumerate('xyz'):
if i >= data.shape[1]:
return
self.AL.setOneMode(flag, "外部导入")
self.data[flag] = self.AL.setData(flag, data = data[:,i])
# 读取坐标轴al的数据
def readDatas(self, al):
dct = {}
data = {}
for flag in al.getDrawDim():
data[flag] = al.setData(flag, **dct)
if flag=='t':
dct['t'] = data['t'][self.tIndex]
else:
dct[flag] = data[flag]
return data
# 设置绘图坐标
def setDrawAxis(self, al):
sub = int(al.getSub())
print(sub)
if sub in self.axDct:
return self.axDct[sub]
p = al.getProj()
if p == "None":
self.axDct[sub] = self.fig.add_subplot(sub)
else:
self.axDct[sub] = self.fig.add_subplot(sub, projection=p)
return self.axDct[sub]
# 单帧绘图函数
def btnDrawImg(self):
self.fig.clf()
self.axDct = {}
for al in self.als:
ax = self.setDrawAxis(al)
data = self.readDatas(al)
draw = self.drawDct[al.getDrawType()]
style = al.getStyle()
keys = al.getDrawDim().replace('t',"")
draw(ax, data, keys, style)
self.fig.subplots_adjust(left=0.1, right=0.95, top=0.95, bottom=0.08)
self.canvas.draw()
def drawBar(self, ax, data, keys, style):
ax.bar(data['x'], data['y'])
def drawPlot(self, ax, data, keys, style):
ax.plot(*[data[key] for key in keys], **style)
def drawScatter(self, ax, data, keys, style):
ax.scatter(*[data[key] for key in keys])
def setFrmFig(self, frmFig):
self.fig = Figure()
self.canvas = mbb.FigureCanvasTkAgg(self.fig,frmFig)
self.canvas.get_tk_widget().pack(
side=tk.TOP,fill=tk.BOTH,expand=tk.YES)
self.toolbar = mbb.NavigationToolbar2Tk(self.canvas,frmFig,
pack_toolbar=False)
self.toolbar.update()
self.toolbar.pack(side=tk.RIGHT)
if __name__ == "__main__":
test = DarwSystem()