文章目录
Python绘图系统:
- 前置源码: Python打造动态绘图系统
- 📈一 三维绘图系统 📈二 多图绘制系统📈三 坐 标 轴 定 制
- 📈四 定制绘图风格 📈五 数据生成导入📈六 三维动态演示
- 坐标列表进阶:导出数据📌系统菜单📌批量文件导入📌辅助坐标轴
辅助坐标
通过eval实现的源码阅读功能,具有非常强大的定制功能,但落实到具体使用上,坐标输入框中的代码总不能太长。如果想对数据进行复杂的处理,那么就相当于提出了一个新的需求,能否给出一些辅助坐标系用于数据处理。
这个功能并不复杂,而且代码敲到现在,应该可以说是很有经验了,首先在工具栏末尾填上加减号按钮,用于添加删除辅助坐标。
在initFeature结尾加上
python
btn = ttk.Button(frm, text="+", width=3)
btn.pack(side=tk.LEFT)
btn.bind("<Button-1>", self.addLast)
btn = ttk.Button(frm, text="-", width=3)
btn.pack(side=tk.LEFT)
btn.bind("<Button-1>", self.rmLast)
界面变为
增减坐标轴
若想添加辅助坐标轴,那么首先要给坐标轴取一个名字,由于具体计算时都在函数中局部地进行,故而可以用abcdefg这样一直下去,一般不会出太大的问题。另一方面,创建AxisFrame需要空间宽度和控件模式这两个参数,而这个参数是从初始化时由外部传入的,所以在初始化代码中添加两个全局变量
python
self.afWidth = widths
self.afMode = mode
而addLast
代码如下
python
def addLast(self, evt):
ABC = "abcdefghijklmnopqrs"
for a in ABC:
if a in self.afs:
continue
self.afs[a] = AxisFrame(self._a,
a, self.afMode, self.afWidth)
break
self.updateVisible()
这段代码相当于预设了最多20个辅助坐标,大部分情况下应该是够用了。
减少坐标轴的逻辑和添加坐标轴相反,但在pack_forget之后,不要忘了把这个对象删掉。
python
def rmLast(self, evt):
CBA = "abcdefghijklmnopqrs"[::-1]
for a in CBA:
if a not in self.afs:
continue
self.afs[a].pack_forget()
del self.afs[a]
break
self.updateVisible()
效果如下
时间轴**
在具体绘图时,时间轴和其他坐标轴有着完全不同的操作,这种不同也应该体现在AxisList的布局上。所以下面要为事件轴专门做一个开关,就像最开始的txyz的独立开关按钮一样。
同时,考虑到作图需求,即有些图像不是三个轴能够完全搞定的,比如箭头向量图。所以需要新增几个坐标轴。
python
self.vis = {L : True for L in 'txyzuvw'}
for u in uvw: self.vis[u] = False
这里需要改动的地方很多很零碎,最重要的地方是dimChanged函数,要把txyz编程'xyzuvw',
python
def dimChanged(self, evt):
txyz = self.getDim()
for flag in 'xyzuvw':
self.vis[flag] = flag in txyz
self.updateVisible()
代码优化
至此,AxisList就已经有了相对完备的功能,但新增功能之后,还需要做的就是代码的优化,从可读性、复用率的角度对代码进行整理。
首先是文件格式常量,以及字母表,可先将其设为全局变量。
然后,当对话框返回值为空时,需要跳出函数。
另外,控件显示和隐藏功能其实是可以和坐标轴的显隐功能合在一起的,这也可以解决之前一直存在的一个bug。
源代码
最后,附上源代码。关于AxisList的修改暂时可以告一段落了,接下来将要做的是修改DrawStyle和DrawType这两个class。
python
import tkinter as tk
import tkinter.ttk as ttk
from tkinter.filedialog import *
from tkinter.simpledialog import *
from tkinter.messagebox import showwarning
import numpy as np
from aframe import AxisFrame, AskDct
from base import DrawType, DrawStyle
class AxisList(ttk.Frame):
def __init__(self, master,
title, mode, widths,
types, typeDct, # 绘图类型Combobox的参数
**options):
super().__init__(master, **options)
self.pack()
self.initConst()
self.afs = {}
self.data = {}
self.afWidth = widths
self.afMode = mode
self.initWidgets(title, widths)
self.initFeature(types, typeDct)
self.initAxis(mode, widths)
self.initStyleFrame()
def initConst(self):
self.ABC = "abcdefghijklmnopqrs"
self.FILE_MERGE = [('numpy数组', 'npy'), ('文本文件', 'csv')]
self.FILE_ALL = [('文本文件', 'txt'), ('文本文件', 'csv'),
('二进制文件', 'bin')]
def initWidgets(self, title, widths):
self.btn = ttk.Button(self, text=title, width=sum(widths)+5,
command=self.Click)
self.btn.pack(side=tk.TOP, fill=tk.X, expand=tk.YES)
self._c = ttk.Frame(self) # 此为主控件
self._b = ttk.Frame(self._c) # 工具栏控件
self._a = ttk.Frame(self._c) # 此为坐标轴
self._s = ttk.LabelFrame(self._c, text = "绘图风格")
self._b.pack(side=tk.TOP)
self._a.pack(side=tk.TOP)
self._s.pack(side=tk.TOP)
self.collapsed = True
self.Click()
# 初始化工具栏
def initFeature(self, types, typeDct):
frm = self._b
frm.pack(pady=2, side=tk.TOP, fill=tk.X)
btn = ttk.Menubutton(frm, text="功能",width=4)
btn.pack(side=tk.LEFT)
m = self.initFileMenu(btn)
btn.config(menu=m)
self.drawType = DrawType(frm, typeDct,
func=self.dimChanged)
self.drawType.pack(side=tk.LEFT, padx=2)
self.vis = {L : True for L in 'txyzuvw'}
ttk.Button(frm, text='t', width=3,
command=self.showTimeAxis).pack(side=tk.LEFT)
btn = ttk.Button(frm, text="+", width=3)
btn.pack(side=tk.LEFT)
btn.bind("<Button-1>", self.addLast)
btn = ttk.Button(frm, text="-", width=3)
btn.pack(side=tk.LEFT)
btn.bind("<Button-1>", self.rmLast)
def addLast(self, evt):
for a in self.ABC:
if a in self.afs:
continue
self.afs[a] = AxisFrame(self._a,
a, self.afMode, self.afWidth)
break
self.updateVisible()
def rmLast(self, evt):
for a in self.ABC[::-1]:
if a not in self.afs:
continue
self.afs[a].pack_forget()
del self.afs[a]
break
self.updateVisible()
def showTimeAxis(self):
self.vis['t'] = not self.vis['t']
self.updateVisible()
# 设置菜单
def initFileMenu(self, btn):
top = tk.Menu(btn, tearoff=False)
menuDct = {
"导入" : {"按坐标轴" : self.mImportOne,
"合并导入" : self.mImportMerge,
"多文件导入": self.mImportAll},
"导出" : {"合并导出" : self.mExportMerge,
"全部导出" : self.mExportAll,
"单轴导出" : self.mExportOne},
"窗口" : { "风格" : self.btnShowStyle}}
for key in menuDct:
m = tk.Menu(top, tearoff=False)
for L,C in menuDct[key].items():
m.add_command(label=L, command = C)
top.add_cascade(label=key, menu=m)
self.showStyle = False
return top
# 加载数据
def btnLoadData(self):
name = askopenfilename()
data = np.genfromtxt(name)
for i, flag in enumerate('xyzuvw'):
if i >= data.shape[1]:
return
self.setOneMode(flag, "外部导入")
self.data[flag] = self.setData(flag, data[:,i])
def mImportOne(self):
fs = askstring("设置参数", "坐标轴间按照逗号分开")
fs = fs.replace(",", ",").split(',')
for f in fs:
if f not in self.getDim():
showwarning("您输入的坐标轴并不存在")
continue
self.afs[f].setMode("外部导入")
self.afs[f].slctChanged(None)
self.afs[f].btnImportComplex(None)
def mImportAll(self):
fs = askopenfilenames(filetypes=self.FILE_ALL)
for i,f in enumerate(self.getDim()):
if i == len(fs) : return
self.afs[f].setMode("外部导入")
self.afs[f].slctChanged(None)
self.afs[f].btnImportSimple(None)
def mImportMerge(self):
path = askopenfilename(filetypes==self.FILE_MERGE)
if path == "" : return
if path.endswith('.npy'):
data = np.load(path)
else:
data = np.loadtxt(path)
for i,f in enumerate(self.getDim()):
if i == len(data): continue
self.afs[f].setMode("外部导入")
self.afs[f].slctChanged(None)
self.afs[f].data = data[i]
def mExportMerge(self):
path = asksaveasfilename(filetypes=self.FILE_MERGE,
initialfile=True)
if path == "" : return
arr = np.array([self.afs[flag].data for flag in self.getDim()])
if path.endswith('.npy'):
arr.tofile(path)
else:
np.savetxt(path, arr, delimiter=', ')
def mExportAll(self):
path = askdirectory()
if path == "": return
for flag in self.getDim():
#self.afs[flag].save(path)
pass
def mExportOne(self):
flag = askstring("请选择输出的轴")
if flag not in self.getDim():
showwarning("您输入的坐标轴并不存在")
#self.afs[flag].save()
# 初始化坐标轴
def initAxis(self, mode, widths):
for flag in 'txyzuvw':
self.afs[flag] = AxisFrame(self._a, flag, mode, widths)
self.afs[flag].pack(side=tk.TOP, fill=tk.X)
self.vis = {L : L in self.getDim() for L in 'txyzuvw'}
self.updateVisible()
# 初始化风格控件
def initStyleFrame(self):
self.drawStyle = DrawStyle(self._s)
self.drawStyle.pack(side=tk.TOP, fill=tk.X)
# 维度改变时的回调函数
def dimChanged(self, evt):
txyz = self.getDim()
for flag in 'xyzuvw':
self.vis[flag] = flag in txyz
self.updateVisible()
# 更新隐藏和显示
def updateVisible(self):
for key in self.afs:
self.afs[key].pack_forget()
for key in self.ABC:
if key in self.afs:
self.afs[key].pack(side=tk.TOP, fill=tk.X)
for flag in 'txyzuvw':
if self.vis[flag]:
self.afs[flag].pack(side=tk.TOP, fill=tk.X)
if self.showStyle:
self._s.pack(side=tk.TOP, fill=tk.X)
else:
self._s.pack_forget()
def btnShowStyle(self):
self.showStyle = not self.showStyle
self.updateVisible()
def getSub(self):
return self.drawType.getSub()
def getProj(self):
return self.drawType.getProj()
def getType(self):
return self.drawType.getType()
def getDim(self):
xyz = self.getXYZ()
if self.vis['t']:
xyz = 't' + xyz
return xyz
def getXYZ(self):
return self.drawType.getDim()
def hasTimeAxis(self):
return self.vis['t']
def getStyle(self):
return self.drawStyle.getVarDct()
def Click(self):
if self.collapsed:
self._c.pack(side=tk.TOP, fill=tk.BOTH, expand=tk.YES)
else:
self._c.pack_forget()
self.collapsed = not self.collapsed
# 设置数据
def setData(self, flag, data=None, **options):
return self.afs[flag].setData(data, **options)
# 设置模式
def setOneMode(self, flag, mode):
self.afs[flag].setMode(mode)