• 绘图系统五:数据产生


    AxisFrame组件

    AxisFrame是存放某一维坐标的组件,目前由一个标签,一个下拉选框和一个输入框构成。下拉选框主要目的是判断输入方式,目前有4个选择,分别是源代码、序列化、外部导入和无数据。

    其中源代码就是通过调用eval执行的代码,这个没什么好说的。但序列化目前的实现方式,用的仍旧是类似Pyton的语法,然后在外面套一层壳,这还是得程序员才能懂。而外部导入目前只起到一个说明的作用,即外部数据导入了,然后这个地方一变,换言之,AxisFrame自身并没有导入数据的功能。

    所以,这两个功能还是需要优化的,当ComboBox的选中项发生变化时,需要调整一下布局,故而initWidgets函数改成下面的形式,其中initRes则根据传入的数据获取模式来初始化其他控件。

    def initWidgets(self, widths):
        tk.Label(self, text=self.label, width=widths[0]).pack(side=tk.LEFT)
        slct = ttk.Combobox(self, width=widths[1], 
            textvariable=self.mode)
        slct['value'] = self.MODES
        slct.bind('<>', self.slctChanged)
        slct.pack(side=tk.LEFT)
        self.initRes(widths[3])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    源码模式

    源码模式最简单,只要有一个Entry就可以,从外观上来看,不需要做任何修改,但代码需要写到另一个函数中。

    代码如下,其逻辑顺序是,先实现一个srcEntry,然后调用showSrcEntry将其展示出来。

    def initRes(self, width):
        self.errWidth = width
        mode = self.mode.get()
        self.srcEntry = tk.Entry(self, width=width, textvariable=self.srcText)
        if mode=="源代码":
            self.showSrcEntry()
        
    def showSrcEntry(self):
        self.srcEntry.pack(padx=5, pady=2, side=tk.LEFT, fill=tk.X)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    其中self.srcText是一个StringVar,是在initVar中定义的

    self.srcText = tk.StringVar()
    
    • 1

    然后把readPython函数改为

    def readPython(self, t=None, x=None, y=None, z=None):
        self.data = eval(self.srcText.get())
        return self.data
    
    • 1
    • 2
    • 3

    序列化

    序列化的含义是生成一个等差数列,要有起点,有终点,还得有步长。所以最直接的创建方法,就是三个Label和三个Entry,而且这些部件需要放进一个Frame中。结合已有的源代码输入控件,initRes函数改为下列形式。

    def initRes(self, width):
        self.errWidth = width
        self.srcEntry = tk.Entry(self, width=width, textvariable=self.srcText)
        
        self.arrFrame = ttk.Frame(self, width=width-5)
        for i, key in enumerate(["起点", "终点", "步长"]):
            tk.Label(self.arrFrame, text=key).grid(row=0, column=i*3)
            tk.Entry(self.arrFrame, width=int(width/6), 
                textvariable=self.arrText[i]).grid(row=0, column=i*3+1)                
    
        self.showRes(self.mode.get())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    其中self.arrText是一个StringVar列表,定义在initVar中

    self.arrText = [tk.StringVar() for _ in range(3)]
    
    • 1

    self.showRes则是显示某组部件的方法

    def showRes(self, mode):
        resDct = {"源代码":self.srcEntry, "序列化":self.arrFrame}
        resDct[mode].pack(padx=5, pady=2, side=tk.LEFT, fill=tk.X)
    
    • 1
    • 2
    • 3

    最后效果如下

    在这里插入图片描述

    另一方面,需要更改getArray函数

    def getArray(self):
        vs = [float(t.get()) for t in self.arrText]
        self.data = np.arange(*vs)
        return self.data
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    绘图结果如下

    在这里插入图片描述

    导入数据

    由于绘图系统中全局的导入按钮已经有了导入数据的功能,所以AxisFrame的导入数据其实有两套工作逻辑:如果事先已经导入数据了,那么就应该什么也不做;如果并没有导入数据,就应该打开数据并导入。

    所以,导入数据选项,至少要有两个部件,一个按钮用于打开文件,外加一个Entry或者Label,用于描述导入的内容。

    其更改过程与序列化的更改如出一辙,先在initVar中添加self.imText

    self.imText = tk.StringVar()
    
    • 1

    然后在initRes中添加self.imFrame,

    # 导入数据设置
    self.imFrame = ttk.Frame(self, width=width-5)
    tk.Entry(self.imFrame, width=width-6,
        textvariable=self.imText).pack(side=tk.LEFT)
    ttk.Button(self.imFrame, text="📂", width=3,
        command=self.btnImport).pack(side=tk.LEFT, padx=3)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    最后修改showRes中的resDct字典

    resDct = {"源代码":self.srcEntry, "序列化":self.arrFrame,
              "外部导入": self.imFrame}
    
    • 1
    • 2

    以及slctChanged中添加

    self.imFrame.pack_forget()
    
    • 1

    效果如下

    在这里插入图片描述

    获取文件信息

    📂按钮指明了命令函数self.btnImport,其第一步一定是打开文件,打开文件需要用到文件对话框,需要导入

    from tkinter.filedialog import askopenfilename
    
    • 1

    虽然对于不同文件,其后续处理方式并不相同,但是将获取到的文件名传递给输入框却十分合理,

    def btnImport(self):
        f = askopenfilename(
            filetypes=[('文本文件', 'txt'), ('文本文件', 'csv'),
                ('二进制文件', 'bin')])
        self.imText.set(f)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    其运行结果为

    在这里插入图片描述

    如果想对得到的文件做进一步的导入,那么可能需要额外的参数,对于文本文件来说,比如首行空格,分隔符等都是需要考虑的;而二进制数据,最起码要对读取数据的数据类型以及行列数做一个判断。

    但有的时候可能又没那么多麻烦,为了应付两种需求,那么可以为按钮绑定左键和右键两种模式。按钮初始化代码更改如下

    btn = ttk.Button(self.imFrame, text="📂", width=3)
    btn.pack(side=tk.LEFT, padx=3)
    btn.bind("", self.btnImportSimple)
    btn.bind("", self.btnImportComplex)
    
    • 1
    • 2
    • 3
    • 4

    这里绑定的两个函数分别响应左键单击和右键单击,具体的实现细节,将在后面的博客给出。

    导入文本

    由于我们设置了两种导入数据的方式,一种简单的,一种复杂的。如果用复杂的,那么就需要设计一个参数对话框,并获取相关参数;如果用简单的,那么只需沿用之前设置的参数就可以了。

    这隐含着一个必选的方案,即设计几个类成员,用于存放这些参数。最好的地方当然是放在initVar函数中

    def initVar(self, mode):
        self.txtPara = {}
        self.binPara = {}
        self.FILES = [('文本文件', 'txt'), ('文本文件', 'csv'),
                    ('二进制文件', 'bin')]
        # ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    由于目前只对文本文件和二进制有兴趣,所以参数列表也只用了两个。

    接下来先设计简单的数据导入函数

    def btnImportSimple(self, evt):
        f = askopenfilename(filetypes=self.FILES)
        self.data = np.genfromtxt(f, **self.txtPara)
        self.showImData()
    
    def showImData(self):
        tmp = self.data.reshape(-1)[:10].astype(str)
        self.imText.set(", ".join(tmp.tolist()))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    其中showImData可以展示前10个数值,效果如下

    在这里插入图片描述

    复杂文本数据的导入,需要一个参数对话框,我们使用的genfromtxt的常用参数大致有下面这些

    genfromtxt(fname,dtype,comments,delimiter,skipd_header,skip_fonter,converters,missing_values,filling_values,usecols,names, autostrip,**kwarg)
    
    • 1

    其中,大部分参数与loadtxt中含义相同,其他参数的含义如下。

    参数类型含义
    fname字符串文件名
    dtype字符串读取后的数据类型
    comments字符串注释标识符,加载时会自动忽略注释标识符后的字符串
    delimiter字符串分割符,为整数时表示元素最大宽度
    skip_header数值跳过文件头部的字符行数
    skip_footer字数值跳过文件尾部字符串行数
    missing_values字符串指定数组中忽略的值
    filling_values字符串指定某个值用于替代忽略值
    autostrip布尔为True时可自动去除变量首尾的空格
    converters字典或函数用以转化数据格式
    usecols数值使用的列号
    encoding字符串编码方式

    据此先设计一个翻译字典,以便于文件对话框使用

    self.TXTLABEL = {
        "comments" : "注释标识",
        "delimiter" : "分割符号",
        "skip_header" : "文首跳过行数",
        "skip_footer" : "文尾舍弃行数",
        "missing_values" : "指定忽略值",
        "filling_values" : "忽略值替代为",
        "autostrip" : "去除首尾空格",
        "usecols" : " 使用的列号",
        "encoding" : "编码方式",
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    为了实现这些功能的顺利输入,可以设计一个字典参数对话框。然后就可以更改高级文本的导入代码了,考虑到这里面有一些参数是整型,所以要在得到数据之后对数据类型进行转换

    def btnImportComplex(self, evt):
        f = askopenfilename(filetypes=self.FILES)
        if f.endswith('.txt'):
            self.txtImport(f)
    
    def txtImport(self, fileName):
        dct = {v:"" for v in self.TXTLABEL.values()}
        AskDct(dct)
        for key in dct:
            if dct[key] == "":
                continue
            if key in ["skip_header", "skip_footer", "usecols"]:
                dct[key] = int(dct[key])
            self.txtPara[key] = dct[key]
        self.data = np.genfromtxt(fileName, **self.txtPara)
        self.showImData()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    导入二进制数据

    二进制数据和文本数据相比,没有那么多的格式要求,但最起码的数据类型和数组形状还是要声明一下的。

    self.BINLABEL = ["数据类型", "行数", "列数", "选择行", "选择列"]
    
    • 1

    这里面之所以没像self.TXTLABEL那样做一个字典,是因为二进制读取函数np.fromfile只有一个数据类型参数,而行数、列数等其他数据说明参数,都需要我们自定义函数来解决。

    def binImport(self, fileName):
        dct = {key:"" for key in self.BINLABEL}
        AskDct(dct)
        binPara = {} if dct["数据类型"]=="" else {"dtype":dct["数据类型"]}
        b = np.fromfile(fileName, **binPara)        
        shape = [-1,-1]
        for i,key in enumerate(["行数", "列数"]):
            if dct[key] != "":
                shape[i] = int(dct[key])
        b = b.reshape(*shape)
    
        if dct["选择列"] != "":
            b = b[int(dct["选择列"]), :]
        elif dct["选择行"] != "":
            b = b[:, int(dct["选择行"])]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  • 相关阅读:
    【泛微ecology】控制文本框输入汉字数量
    Oh my zsh
    力扣2401.最长优雅子数组
    单调栈的本质与应用
    python淘宝商品数据抓取之后数据解析出错怎么办
    csrf总结
    【单片机入门】(四)应用层软件开发的单片机学习之路-----ESP32开发板PWM控制电机以及中断的使用
    MyBatis框架简介
    .NET 托管vs非托管
    [复现 | 论文】Seg4Reg Networks for Automated Spinal Curvature Estimation
  • 原文地址:https://blog.csdn.net/m0_37816922/article/details/132895701