利用Python实现一个可定制风格的绘图系统
作者:微小冷
Python绘图系统:
从0开始的3D绘图系统|一个3D坐标系,多个函数|散点图、极坐标和子图
调用绘图风格控件
如果想在DrawSystem中调用绘图风格,那么就需要DrawStyle对象可以输出绘图参数,由于这里面所有的参数都在字典里面,所以这一步非常容易
def getOneVar(self, key): v = self.varDct[key].get() if v=="": return "" if key in NUM_KEYS: return float(v) elif key in INT_KEYS: return int(v) else: return v def getVarDct(self): dct = {self.VAR_LABS[key] : self.varDct[key].get() for key in self.varDct} return {key : dct[key] for key in dct if dct[key]!=""}
第一个函数用于得到某个绘图参数,第二个则用字典的形式,返回所有已经设置的绘图参数。毕竟,在plot绘图过程中,并不是需要设置所有的绘图参数。
由于DrawStyle是在AxisList中被调用的,所以这个getVarDct函数最好在AxisList中重新封装一下
def getStyle(self): return self.DrawStyle.getVarDct()
最后,绘图风格实现的临门一脚,自然是DrawSystem中的plot函数
def drawPlot(self, ax, data, keys, style): ax.plot(*[data[key] for key in keys], **style)
多了一个style参数,由于绘图函数被重新赋给了func,从而所有绘图函数都要有相同的绘图接口,所以尽管暂时不用,drawScatter和drawBar也要加上style参数。同时更改绘图函数。代码如下,主要添加了一个al.getStyle()的调用。
def btnDrawImg(self): self.fig.clf() keys = self.drawTypeDim.getDim() self.axDct = {} for al in self.als: ax = self.setDrawAxis(al) data = self.readDatas(al) draw = self.drawDct[al.getDrawType()] style = al.getStyle() draw(ax, data, keys, style) self.fig.subplots_adjust(left=0.1, right=0.95, top=0.95, bottom=0.08) self.canvas.draw()
最后得到下面的效果。
代码组织
目前,这个绘图系统已经有了400多行代码,5个类被放在同一个文件中,修改起来已经有些不便了。为了开发工作得以继续,有必要把这几个类分发到不同的文件中。
下面新建四个文件,分别放入以下内容
- base.py:DrawType, DrawStyle
- aframe.py: AxisFrame
- alist.py: AxisList
- ds.py: DrawSystem
源代码
最后附上源代码
base.py
基础的绘图要素,即绘图类型DrawType和绘图风格DrawStyle。
import numpy as np import tkinter as tk import tkinter.ttk as ttk class DrawStyle(ttk.Frame): def __init__(self, master, varDct={}, ws=None, func=None, **options): super().__init__(master, **options) self.pack() self.initConst() self.initVars() self.initWidgets() def initConst(self): self.VAR_LABS = { "线型" : "linestyle", "线宽" : "linewidth", "线色" : "color", "点型" : "marker" , "点径" : "markersize", "点色" : "markeredgecolor", "标签" : "label" , "透明度" : "alpha", "层号" : "zorder" } self.STR_KEYS = ["标签"] self.COM_KEYS = ["线型", "点型"] self.NUM_KEYS = ["线宽", "点径", "透明度"] self.INT_KEYS = ["层号"] self.CLR_KEYS = ["线色", "点色"] def initVars(self): self.varDct = {key:tk.StringVar() for key in self.VAR_LABS} def newFrame(self): frm = ttk.Frame(self) frm.pack(side=tk.TOP, fill=tk.X) return frm def initWidgets(self): frm = self.newFrame() for i,key in enumerate(["标签", "层号", "透明度"]): tk.Label(frm, text=key).grid(row=0, column=i*2, padx=2) tmp = ttk.Entry(frm, width=10, textvariable=self.varDct[key]) tmp.grid(row=0, column=i*2+1, padx=2, pady=2) self.initLineMarker(frm) def initLineMarker(self, frm): enumDct = { "点型" : ['.', ',', '1', '2', '3', '4', '+', 'x', '|', '_', 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 'o', 'v', '^', '<', '>', '8', 's', 'p', '*', 'h', 'H', 'D', 'd', 'P', 'X'], "线型" : ['-', '--', '-.', ':'] } for i in range(2): key = self.COM_KEYS[i] ttk.Label(frm, text=key).grid(row=i+1, column=0, padx=2) tmp = ttk.Combobox(frm, width=10, textvariable=self.varDct[key]) tmp.grid(row=i+1, column=1, padx=2, pady=2) tmp['value'] = enumDct[key] key = self.NUM_KEYS[i] ttk.Label(frm, text=key).grid(row=i+1, column=2, padx=2) tmp = ttk.Entry(frm, width=10, textvariable=self.varDct[key]) tmp.grid(row=i+1, column=3, padx=2, pady=2) key = self.CLR_KEYS[i] ttk.Label(frm, text=key).grid(row=i+1, column=4, padx=2) tmp = ttk.Entry(frm, width=10, textvariable=self.varDct[key]) tmp.grid(row=i+1, column=5, padx=2, pady=2) def getOneVar(self, key): v = self.varDct[key].get() if v=="": return "" if key in NUM_KEYS: return float(v) elif key in INT_KEYS: return int(v) else: return v def getVarDct(self): dct = {self.VAR_LABS[key] : self.varDct[key].get() for key in self.varDct} return {key : dct[key] for key in dct if dct[key]!=""} # 绘图类型和维度 # varDct 的格式是 {"sub":sub, "type":slctType, "dim":dim, "proj": proj} class DrawType(ttk.Frame): # ws为两个combobox的宽 def __init__(self, master, varDct = {"sub":"111", "type":'点线图', "dim":"xyz", "proj": "3d"}, ws=None, func=None, **options): super().__init__(master, **options) self.pack() self.dimChanged = func self.initVar(varDct) self.initWidgets(ws) def initVar(self, varDct): self.drawVars = {key:tk.StringVar() for key in varDct} for key in self.drawVars: self.drawVars[key].set(varDct[key]) def initWidgets(self, ws): if ws==None: ws = [5, 5, 5, 3] slctDct = {'type':("点线图", "散点图", "条形图"), 'proj': ("None", "3d", "polar"), 'dim' : ("x", "xy", "xyz", "tx", "txy", "txyz")} # 绘图维度 keys = ['sub', 'type', 'proj', 'dim'] wDct = {} # 控件字典 # 此为设置子图的Entry控件 wDct['sub'] = ttk.Entry(self, width=ws[0], textvariable=self.drawVars['sub']) for i, key in enumerate(keys[1:], 1): wDct[key] = ttk.Combobox(self, width=ws[i], textvariable=self.drawVars[key]) wDct[key]['value'] = slctDct[key] for key in keys: wDct[key].pack(side=tk.LEFT, padx=2) wDct['dim'].bind('<<ComboboxSelected>>', self.dimChanged) def getSub(self): return self.drawVars['sub'].get() def getType(self): return self.drawVars['type'].get() def getDim(self): return self.drawVars['dim'].get() def getProj(self): return self.drawVars['proj'].get() def getDct(self): return {key:self.drawVars[key].get() for key in self.drawVars}
aframe.py
单个坐标控件,这里面其实有很多功能没做,比如坐标输入方法中,如果选中外部导入的话,那么应该可以出现一个文件对话框,来自动择选想要导入的数据。
import tkinter as tk import tkinter.ttk as ttk import numpy as np class AxisFrame(ttk.Frame): # widths 是每个控件的宽度 def __init__(self, master, label, mode, widths, **options): super().__init__(master, **options) self.pack() self.label = label self.initVar(mode) self.initWidgets(widths) def initVar(self, mode): self.MODES = ("序列化", "源代码", "外部导入", "无数据") self.mode = tk.StringVar() self.setMode(mode) def initWidgets(self, widths): tk.Label(self, text=self.label, width=widths[0]).pack(side=tk.LEFT) self.slct = ttk.Combobox(self, width=widths[1], textvariable=self.mode) self.slct['value'] = self.MODES self.slct.pack(side=tk.LEFT) self.entry = tk.Entry(self, width=widths[2]) self.entry.pack(padx=5, side=tk.LEFT, fill=tk.X) def setText(self, text): self.entry.delete(0, "end") self.entry.insert(0, text) def get(self): return self.entry.get() def setMode(self, mode): if type(mode) != str: mode = self.MODES[mode] self.mode.set(mode) def setData(self, data=None, **txyz): if self.mode.get() == "序列化": return self.getArray() elif self.mode.get() == "外部导入": return self.loadData(data) else: return self.readPython(**txyz) def readPython(self, t=None, x=None, y=None, z=None): self.data = eval(self.get()) return self.data def loadData(self, data): if type(data) != type(None): self.data = data return self.data def getArray(self): val = self.get() self.data = eval(f"np.linspace({val})") return self.data
alist.py
即AxisFrame的列表,这里面已经集齐了绘制一幅图像的全部要素,但并没有图像展示功能。
import tkinter as tk import tkinter.ttk as ttk import numpy as np from aframe import AxisFrame 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.afs = {} self.data = {} self.initWidgets(title, widths) self.initFeature(types, typeDct) self.initAxis(mode, widths) self.initStyleFrame() 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.Frame(self._c) # 此为绘图风格控件 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) ttk.Button(frm, text="导入",width=5, command=self.btnLoadData).pack(side=tk.LEFT) ttk.Button(frm, text="导出",width=5, command=self.btnLoadData).pack(side=tk.LEFT) self.drawTypeDim = DrawType(frm, typeDct, func=self.dimChanged) self.drawTypeDim.pack(side=tk.LEFT, padx=2) self.vis = {L : True for L in 'txyz'} ttk.Button(frm, text="风格",width=5, command=self.btnShowStyle).pack(side=tk.LEFT) self.showStyle = False # 初始化坐标轴 def initAxis(self, mode, widths): for flag in 'txyz': 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.getDrawDim() for L in 'txyz'} self.updateVisible() # 初始化风格控件 def initStyleFrame(self): self.sf = ttk.LabelFrame(self._s, text="绘图风格") self.drawStyle = DrawStyle(self.sf) self.drawStyle.pack(side=tk.TOP, fill=tk.X) # 维度改变时的回调函数 def dimChanged(self, evt): txyz = self.getDrawDim() for flag in 'txyz': self.vis[flag] = flag in txyz self.updateVisible() # 更新隐藏和显示 def updateVisible(self): for flag in 'txyz': self.afs[flag].pack_forget() for flag in 'txyz': if self.vis[flag]: self.afs[flag].pack(side=tk.TOP, fill=tk.X) def btnShowStyle(self): self.showStyle = not self.showStyle if self.showStyle: self.sf.pack(side=tk.TOP, fill=tk.X) else: self.sf.pack_forget() def getSub(self): return self.drawTypeDim.getSub() def getProj(self): return self.drawTypeDim.getProj() def getDrawType(self): return self.drawTypeDim.getType() def getDrawDim(self): return self.drawTypeDim.getDim() def getStyle(self): return self.drawStyle.getVarDct() # 加载数据 def btnLoadData(self): name = askopenfilename() data = np.genfromtxt(name) for i, flag in enumerate('xyz'): if i >= data.shape[1]: return self.setOneMode(flag, "外部导入") self.data[flag] = self.setData(flag, data[:,i]) 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)
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) frm = ttk.Frame(frmCtrl, width=320) frm.pack(side=tk.TOP, fill=tk.X) self.setCtrlButtons(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("<Button-1>", self.addLast) btn = ttk.Button(frm, text="-", width=3) btn.pack(side=tk.LEFT) btn.bind("<Button-1>", self.deleteLast) 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[:,i]) def readDatas(self, al): dct = {} data = {} for flag in self.drawTypeDim.getDim(): data[flag] = al.setData(flag, **dct) 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() keys = self.drawTypeDim.getDim() self.axDct = {} for al in self.als: ax = self.setDrawAxis(al) data = self.readDatas(al) draw = self.drawDct[al.getDrawType()] style = al.getStyle() 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()
到此这篇关于利用Python实现一个可定制风格的绘图系统的文章就介绍到这了,更多相关Python绘图系统内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!