用Python实现一个可定制风格的绘图系统

这篇具有很好参考价值的文章主要介绍了用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()

最后得到下面的效果。

用Python实现一个可定制风格的绘图系统,# Python+tkinter实战:绘图系统,python,绘图系统,matplotlib,tkinter

代码组织

目前,这个绘图系统已经有了400多行代码,5个类被放在同一个文件中,修改起来已经有些不便了。为了开发工作得以继续,有必要把这几个类分发到不同的文件中。

用Python实现一个可定制风格的绘图系统,# Python+tkinter实战:绘图系统,python,绘图系统,matplotlib,tkinter

下面新建四个文件,分别放入以下内容

  • 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

入口函数就在这个文件中,是主程序的文件。文章来源地址https://www.toymoban.com/news/detail-702701.html

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实现一个可定制风格的绘图系统的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Python绘图系统9:新建绘图类型控件,实现混合类型图表

    Python绘图系统: 📈从0开始的3D绘图系统📉一个3D坐标系,多个函数 图表类型和风格:散点图和条形图📊 混合类型图表 很多时候我们都有在一张图上绘制多种图标的需求,比如数据拟合的时候,用散点图表示原始数据,用曲线图来表示绘图后的结果。从这个角度来说,每一

    2024年02月11日
    浏览(35)
  • 基于Tkinter实现一个垃圾分类答题软件

    🤵‍♂️ 个人主页:@艾派森的个人主页 ✍🏻作者简介:Python学习者 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞👍🏻 收藏 📂加关注+ 目录 一、Tkinter 二、最终效果 三、项目过程 3.1分析布局 3.2创建窗口 3.3构建题库 3.4创建组件

    2024年01月21日
    浏览(62)
  • 图文并茂:Python Tkinter从入门到高级实战全解析

    介绍 什么是Tkinter? 准备工作 第一个Tkinter程序 界面布局 事件处理 补充知识点 文本输入框 复选框和单选框 列表框 弹出对话框 综合案例:待办事项列表 总结 欢迎来到本篇文章,我们将带您深入了解如何在Python中使用Tkinter库来创建图形用户界面(GUI)应用程序。无论您是初

    2024年02月11日
    浏览(33)
  • 数据库实战:基于Tkinter+MySQL的学生成绩管理系统

    用Python和数据库一起实现了一个简单的学生成绩管理系统,一起来看看吧! 本篇博客主要分为两大部分,数据库部分和Python程序设计部分,先将数据导入到数据库中,随后通过python程序设计连接到数据库,实现一系列的操作。 代码下载地址:https://download.csdn.net/download/m0_68

    2024年02月03日
    浏览(56)
  • 基于Android+Django+Python的服饰管理与个性化定制系统的设计与实现

    资源下载地址:https://download.csdn.net/download/sheziqiong/87904742 资源下载地址:https://download.csdn.net/download/sheziqiong/87904742 一、选题的背景和意义 1、课题研究背景 随着移动终端技术和网络技术的飞速发展,人们可以使用移动客户端上网,随时随地从互联网获取信息和服务,解决吃

    2024年03月13日
    浏览(98)
  • Python创建一个GUI(Tkinter)选择文件的界面获取文件路径

    1、点击【选择路径可以选择文件】 2、【确定(开始执行)】 这个按钮没有绑定函数,大家可以根据自己的需求绑定函数

    2024年02月11日
    浏览(67)
  • 100个Python实战项目(十三)使用 Tkinter GUI 库构建闹钟应用程序

    本文主要是使用 Python 的 Tkinter 库创建一个简单的闹钟程序,它可以在指定的时间播放一个声音来提醒你。这个程序界面比较简单,只有一个标签、三个选项菜单和一个按钮,用户可以通过选项菜单设置闹钟的时间,然后点击按钮开始计时,直到闹钟时间到达时,程序会播放

    2023年04月16日
    浏览(43)
  • C++“流”风格日志系统实战-课程简介

    一个能快速提升C++复杂代码设计的学习项目,一个能迅速让C++面试官会心一笑的简历项目,一个能在实际项目中使用的项目……学习什么是流?如何利用抽象层面的流编写适用面更广的代码? 每天在用的cout和cin 它们是什么类型?最后还将完整写一个充满 C++ 风味的日志流。

    2024年04月28日
    浏览(34)
  • python 基于tkinter商品销售系统

    苹果手机商店信息管理系统 在信息时代的今天,计算机以其快速,准确的处理功能渗透在我们周围的方方面面,同时随着网络技术和软件技术的飞速发展,当今社会也正快速向信息化时代前进,信息系统的作用也越来越大,商业经营管理也日渐体现出运用计算机的优越性。商

    2023年04月09日
    浏览(73)
  • 前端Web实战:从零打造一个类Visio的流程图拓扑图绘图工具

    大家好,本系列从Web前端实战的角度,给大家分享介绍如何从零打造一个自己专属的绘图工具,实现流程图、拓扑图、脑图等类Visio的绘图工具。 免费好用、 专属自己的绘图工具 前端项目实战学习 如何从0搭建一个前端项目等基础框架 项目设计思路及优雅的架构技巧 开源项

    2024年02月16日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包