Bootstrap

基于wxPthon所做的GUI桥梁数据监测管理项目(四)数据可视化界面(绘制折线图)

3.可视化:

在项目树底层新建一个Dialog,命名为VisualFrame。在该窗口中添加一个wxBoxSizer后添加两行,把第一行改为横行排列,依次添加wxStaticText和wxComboBox(在Common下)、wxButton,并分别把两个wxStaticText的标签改为测点类型、测点位置:

注意这里是连续三个wxComboBox,依次改名为m_comboBoxyb,m_comboBoxrd,m_comboBoxwd(截图有误)。

在第二行中添加wxPanel(中央组件Containers-wxPanel),并将姓名改为m_zxt:

在wxPanel中再新加一行,后在该行中新建一个Customcontrol(Additional-Customcontrol),并将其名字修改为m_figurecanvas:

在该组件属性的construction中书写语句:

# 创建了一个Matplotlib的Figure实例,表示一个图形
# figsize=(11.3, 7.9) 指定了图形的大小为11.3英寸宽,7.9英寸高。
# dpi=72 指定了图形的分辨率为72点每英寸。
# facecolor='#FFFFFF' 设置了图形的背景色为白色(使用十六进制颜色表示法,#FFFFFF表示白色)。
self.m_figure = matplotlib.figure.Figure(figsize=(11.3, 7.9),dpi=72,facecolor='#FFFFFF') 

# 创建了一个Matplotlib的FigureCanvas实例,用于将Figure实例渲染到界面上。具体来说:
# self.m_zxt 是画布的父窗口,表示画布被嵌入到哪个窗口中。这里可能是一个窗口对象的引用。
# -1 是画布的标识符,通常用于在窗口中识别画布。
# self.m_figure 是之前创建的Figure实例,表示该画布与哪个图形相关联。
self.m_figurecanvas = FigureCanvas(self.m_zxt, -1, self.m_figure) 

在include中书写语句:

import matplotlib 
'''
指定了Matplotlib的渲染后端为"WXAgg"。Matplotlib支持多种后端,
用于在不同的图形用户界面(GUI)工具包中显示图形。
"WXAgg"表示使用wxPython工具包的Agg渲染后端,
其中Agg是Anti-Grain Geometry的缩写,是一个高质量的图形渲染引擎。
'''
matplotlib.use("WXAgg") 

from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas 

点击测点类型后面那个下拉选项框,在其属性框中点击choices最后的三个点,添加选项:

点击New item,然后在里面输入以下内容,

点击ok关闭窗口,就可以看到下拉选项框变成了这样

同理在后面三个选项框中分别添加以下内容:

在第二个选项框的属性中找到hidden并勾选,就可以隐藏掉这个选项框,第三个选项框同理进行隐藏,最后隐藏的只剩第一个:

给确认键添加一个事件:evt_ok,记得F8生成代码

至此我们前端的绘图面板就设置好了,接下来我们到后端进行数据读取及绘图。

现在就是重复每次新设计一个界面就要做的工作,继承这个界面并写入自己的方法,正常的,先重写__init__方法:

class VisualFrame(CSDNtext01.VisualFrame):
    def __init__(self,parent):
        CSDNtext01.VisualFrame.__init__(self,parent)
        # 绘制图形在panel中所占的位置,其中四个参数表示图像距离左右上下的距离
        self.axes = self.m_figure.add_axes([0.05, 0.15, 0.92, 0.8])
        self.parent = parent
        # 窗口最大化
        self.Maximize()

这个__init__方法重写略有不同,因为需要调整画板的位置,所以多了几行。

现在就开始画图吧。因为我们的窗口支持多次点击。根据选择不同多次生成图像,所以我们得需要一个清除上一个图像的函数吧:

    def __clear(self):
        try:
            '''# 再次画图前必须调用该命令清空原来的图形'''
            self.axes.clear()
            self.m_figure.set_canvas(self.m_figurecanvas)
            self.__updateplot()
            self.m_figurecanvas.draw()
        except:
            pass

清除了之后,得有个更新图像的函数吧:

    def __updateplot(self):
        '''修改图形的任何属性后都必须使用self.__updateplot()更新GUI界面'''
        try:
            self.m_figurecanvas.draw()
        except:
            pass

最重要的,图怎么画出来的(里面备注都写的蛮清楚的,不懂得自己搜一下吧):

    def __draw(self):
        x_datas = []
        y_datas = []
        conn = Connection(
            host="localhost",
            port=3306,
            user="root",
            password="123456",
            db="Csdn_text01"
        )
        cursor = conn.cursor()
        yb = cursor.execute("select *from yb_db")
        yb_data = cursor.fetchall()
        # print(yb_data)
        time_data = []
        for i in yb_data:
            time_data.append(i[0])
        # print(time_data)
        x = time_data
        # self.axes.plot(x,y1)
        # self.m_panel1.Layout()
        if self.m_comboBox1.GetValue() == "应变":
            type1 = "yb_db"
            type2 = self.m_comboBoxyb.GetValue()
        if self.m_comboBox1.GetValue() == "扰度":
            type1 = "rd_db"
            type2 = self.m_comboBoxrd.GetValue()
        if self.m_comboBox1.GetValue() == "温度":
            type1 = "wd_db"
            type2 = self.m_comboBoxwd.GetValue()
        # 这里在写的时候用%s占位符搞半天不行,用字符串格式化(.format)就好了:
        cursor.execute("select {} from {}".format(type2, type1))
        y = cursor.fetchall()
        # 清除上一次生成的折线图
        self.__clear()
        # 用来显示坐标轴网格的函数
        self.axes.grid()
        # 这里是设置了中文字体,没有这一步可能会导致中文无法显示
        plt.rcParams['font.sans-serif'] = ['Simhei']
        # 设置标题及字号,其中使用了GetValue获取到第一个选项框的内容,比如获取到应变,这里生成的标题就是“应变 数据曲线”
        self.axes.set_title('%s 数据曲线' % self.m_comboBox1.GetValue(), size=32)
        # 这个madtes是import matplotlib.dates as mdates
        # 定义横坐标轴显示的格式
        self.axes.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d '
                                                                 '%H:%M:%S'))
        # 设置x轴的主要刻度定位器为自动日期定位器。根据x轴数据的范围自动选择合适的日期刻度间隔,使得x轴上的日期刻度更加合理和易读
        self.axes.xaxis.set_major_locator(mdates.AutoDateLocator())
        # x轴标签
        self.axes.set_xlabel('时间', size=18)
        # y轴标签,使用到了GetValue,同理,可变化
        self.axes.set_ylabel('测点:%s' % self.m_comboBox1.GetValue(), size=18)
        self.m_figure.autofmt_xdate()  # x轴日期时间自动旋转
        # 让 Matplotlib 自动调整x和y轴的范围,以适应数据的范围
        self.axes.set_xlim(auto=True)
        self.axes.set_ylim(auto=True)
        '''x 和 y 是日期和相应的数值。
            '-' 表示使用实线连接数据点。
            c='#00FF00' 设置线的颜色为绿色。
            linewidth=1 设置线的宽度为1。'''
        p, = self.axes.plot_date(x, y, '-', c='#00FF00',
                                 linewidth=1)
        '''
        [p] 表示传递包含绘制对象的列表。
        loc='best' 表示自动选择最佳的位置放置图例。
        shadow=True 表示在图例中添加阴影效果。
        fontsize=12 设置图例字体的大小为12。
        '''
        self.axes.legend([p], [self.m_comboBox1.GetValue()], loc='best', shadow=True, fontsize=12)
        # 更新图像
        self.__updateplot()

编写evt_ok方法把这些函数连接起来:

    def evt_ok(self, event):
        if self.m_comboBox1.CurrentSelection == -1:
            return
        self.__draw()
        event.Skip()

在实际测试中我们会发现,在选择应变之后,后面的选项框是n1-n9,但是选择了扰度和温度时,后面的选项框仍然是n1-n9,我们多做的两个选项框未显示出来,因为缺少了这么一个东西:

    def checkChoice(self, event):
        # print(self.m_comboBox1.GetValue())
        if self.m_comboBox1.GetValue() == '应变':
            self.m_comboBoxyb.Show()
            self.m_comboBoxrd.Hide()
            self.m_comboBoxwd.Hide()
            self.Layout()
        if self.m_comboBox1.GetValue() == '扰度':
            self.m_comboBoxyb.Hide()
            self.m_comboBoxrd.Show()
            self.m_comboBoxwd.Hide()
            self.Layout()
        if self.m_comboBox1.GetValue() == '温度':
            self.m_comboBoxyb.Hide()
            self.m_comboBoxrd.Hide()
            self.m_comboBoxwd.Show()
            self.Layout()
        self.Layout()

这个方法实现了当类型选择框结果不同时,显示不同的下属选择框。

注意:修改后需要self.layout()重新排列窗口,否则布局会乱。

测试发现绘制出的图总是首尾相连,进到数据库看了一眼发现是多次载入同一份excel数据。重复存储,导致一份数据的结尾和另一份数据的开头连接起来了。而我们在每次运行程序都要载入一次表格,所以我们需要编写一个函数用于检查数据库中是否有数据,如果有就把数据库清空,并在程序正常退出时清空数据库。

进入到db.py,在MyConnection下新建一个方法:

    def del_db(self):
        conn=Connection(
            host='localhost',
            port=3306,
            user='root',
            password='123456',
            db='Csdn_text01'
        )
        cursor=conn.cursor()
        cursor.execute("delete from yb_db")
        cursor.execute("delete from rd_db")
        cursor.execute("delete from wd_db")
        conn.commit()
        cursor.close()
        conn.close()

一定记得commit,以及连接数据库用完数据后,执行close。

我们给实现检测数据库是否有数据这个功能起个名字,叫系统自检,是不是一下就高大上了,在Login类中编写一个函数:

    def evt_sysselftest(self, event):
        conn = Connection(
            host='localhost',
            port=3306,
            user='root',
            password='123456',
            db='Csdn_text01'
        )
        cursor = conn.cursor()
        cursor.execute('select count(*) from yb_db ')
        result_yb1 = cursor.fetchone()
        result_yb = result_yb1[0]
        # print(result_yb)
        cursor.execute('select count(*) from rd_db ')
        result_rd1 = cursor.fetchone()
        result_rd = result_rd1[0]
        cursor.execute('select count(*) from wd_db ')
        result_wd1 = cursor.fetchone()
        result_wd = result_wd1[0]
        if result_yb != 0 or result_rd != 0 or result_wd != 0:
            wx.MessageBox("上次程序未正常退出,请点击OK等待程序修复", "错误信息")
            # 程序退出时自动清空数据库
            db.MyConnection.del_db(self)
            wx.MessageBox("程序已修复", "提示信息")
        else:
            wx.MessageBox("系统正常,请继续使用", "提示信息")
        self.Close()

然后在登录成功后(调用数据库之前)调用这个方法:

self.evt_sysselftest()

再次跑一下代码,可以发现没有问题了。

;