pyside6学习专栏(八):在PySide6中使用matplotlib库绘制三维图形
- 互联网
- 2025-09-21 04:54:02

本代码原来是PySide6官网的一个示例程序,我对其进行的详细的注释,同时增加了一个功能:加载显示cass的地形图坐标数据示例,示例可显示以下几种三维图形
程序运行界面如下:
代码如下:
# -*- coding: utf-8 -*- from __future__ import annotations import sys,time,copy,os,random import numpy as np from matplotlib.backends.backend_qtagg import FigureCanvas from matplotlib.figure import Figure from mpl_toolkits.mplot3d import axes3d from PySide6.QtCore import Qt, Slot from PySide6.QtGui import QAction, QKeySequence from PySide6.QtWidgets import (QApplication, QComboBox, QHBoxLayout, QHeaderView, QLabel, QMainWindow, QSlider, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget) """这是一个使用3Dmatplotlib plot 在Qt Widgets上显示三维图形的示例 原版为PySide6官网上的一示例代码,对其进行了详细注解,同时扩展增加了以下一组示例代码 1、从外部导入地形图数据(cass地形测绘数据格式)并进行显示地形三角网 """ class ApplicationWindow(QMainWindow): def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.column_names = ["X向", "Y向", "Z向"] #定义中心窗口,并居中显示 self._main = QWidget() self.setCentralWidget(self._main) self.setWindowTitle('PySide6+matplotlib绘制三维图示例') #定义菜单栏 self.menu = self.menuBar() self.menu_file = self.menu.addMenu("文件") exit = QAction("离开", self, triggered=qApp.quit) # noqa: F821 self.menu_file.addAction(exit) self.menu_about = self.menu.addMenu("关于") about = QAction("关于Qt", self, shortcut=QKeySequence(QKeySequence.HelpContents), triggered=QApplication.aboutQt) # noqa: F821 self.menu_about.addAction(about) #窗体左侧轮廓:为一视图和标签及滑条 self.fig = Figure(figsize=(5, 3)) self.canvas = FigureCanvas(self.fig) #左侧滑条控件 min = 0 max = 360 self.slider_azim = QSlider(minimum=min, maximum=max, orientation=Qt.Orientation.Horizontal) self.slider_elev = QSlider(minimum=min, maximum=max, orientation=Qt.Orientation.Horizontal) self.slider_azim_layout = QHBoxLayout() self.slider_azim_layout.addWidget(QLabel(f"{min}")) self.slider_azim_layout.addWidget(self.slider_azim) self.slider_azim_layout.addWidget(QLabel(f"{max}")) self.slider_elev_layout = QHBoxLayout() self.slider_elev_layout.addWidget(QLabel(f"{min}")) self.slider_elev_layout.addWidget(self.slider_elev) self.slider_elev_layout.addWidget(QLabel(f"{max}")) #窗体右侧为一表格控件 self.table = QTableWidget() header = self.table.horizontalHeader() header.setSectionResizeMode(QHeaderView.Stretch) #窗体右侧顶部为一组合框控件 self bo = QComboBox() self bo.addItems(["网格", "表面", "三角网表面", "球面","导入cass地形图","导入散点模型"]) #设置窗体右侧布局 rlayout = QVBoxLayout() rlayout.setContentsMargins(1, 1, 1, 1) rlayout.addWidget(QLabel("Plot类:")) rlayout.addWidget(self bo) rlayout.addWidget(self.table) #设置窗体左侧布局 llayout = QVBoxLayout() rlayout.setContentsMargins(1, 1, 1, 1) llayout.addWidget(self.canvas, 88) llayout.addWidget(QLabel("方位角:"), 1) llayout.addLayout(self.slider_azim_layout, 5) llayout.addWidget(QLabel("高程:"), 1) llayout.addLayout(self.slider_elev_layout, 5) #窗体总布局采用竖向布局 layout = QHBoxLayout(self._main) layout.addLayout(llayout, 70) #左侧布局占70% layout.addLayout(rlayout, 30) #右侧布局点30% #定义信号槽绑定 self bo.currentTextChanged.connect(self bo_option) #组合框选择项发生变化时 self.slider_azim.valueChanged.connect(self.rotate_azim) #方位角(XY平面)滑条控件值发生变化时 self.slider_elev.valueChanged.connect(self.rotate_elev) #方位角(Z向)滑条控件值发生变化时 #初始化视图显示 self.plot_wire() #默认加载 self._ax.view_init(30, 30) self.slider_azim.setValue(30) self.slider_elev.setValue(30) self.fig.canvas.mpl_connect("button_release_event", self.on_click) #左侧画布视图单击后鼠标释放时信号 #槽函数: 单击Matplotlib视时发出的信号 def on_click(self, event): azim, elev = self._ax.azim, self._ax.elev self.slider_azim.setValue(azim + 180) self.slider_elev.setValue(elev + 180) # 设置表格中的数据:传入XYZ数据(数据是一个np一维数组) def set_table_dataS(self, xS, yS, zS): for i in range(len(xS)): self.table.setItem(i, 0, QTableWidgetItem(f"{xS[i]:.2f}")) self.table.setItem(i, 1, QTableWidgetItem(f"{yS[i]:.2f}")) self.table.setItem(i, 2, QTableWidgetItem(f"{zS[i]:.2f}")) #重绘视图和表格时,当前的XYZ数据集 def set_canvas_table_configuration(self, row_count, dataS): self.fig.set_canvas(self.canvas) self._ax = self.canvas.figure.add_subplot(projection="3d") self._ax.set_xlabel(self.column_names[0]) #表格的0例数据全部导入到视图的的x self._ax.set_ylabel(self.column_names[1]) #表格的0例数据全部导入到视图的的y self._ax.set_zlabel(self.column_names[2]) #表格的0例数据全部导入到视图的的z self.table.setRowCount(row_count) self.table.setColumnCount(3) self.table.setHorizontalHeaderLabels(self.column_names) self.set_table_dataS(dataS[0], dataS[1], dataS[2]) # Plot 绘制样式: #视图和表格中数据为四边形网(无表面) def plot_wire(self): #产生示例数据: np二维数组->(200,200) self.X, self.Y, self.Z = axes3d.get_test_data(0.03) #从axes3d类中得到示例数据 self.set_canvas_table_configuration(len(self.X[0]), (self.X[0], self.Y[0], self.Z[0])) self._ax.plot_wireframe(self.X, self.Y, self.Z, rstride=10, cstride=10, cmap="viridis") self.canvas.draw() #视图和表格中数据为漏斗型表面 def plot_surface(self): #产生示例数据: np二维数组->(30,30) self.X, self.Y = np.meshgrid(np.linspace(-6, 6, 30), np.linspace(-6, 6, 30)) self.Z = np.sin(np.sqrt(self.X ** 2 + self.Y ** 2)) self.set_canvas_table_configuration(len(self.X[0]), (self.X[0], self.Y[0], self.Z[0])) self._ax.plot_surface(self.X, self.Y, self.Z, rstride=1, cstride=1, cmap="viridis", edgecolor="none") self.canvas.draw() #视图和表格中数据为三角网表面 def plot_triangular_surface(self): #产生示例数据: np一维数组->(289,) radii = np.linspace(0.125, 1.0, 8) angles = np.linspace(0, 2 * np.pi, 36, endpoint=False)[..., np.newaxis] self.X = np.append(0, (radii * np.cos(angles)).flatten()) self.Y = np.append(0, (radii * np.sin(angles)).flatten()) self.Z = np.sin(-self.X * self.Y) self.set_canvas_table_configuration(len(self.X), (self.X, self.Y, self.Z)) self._ax.plot_trisurf(self.X, self.Y, self.Z, linewidth=0.2, antialiased=True) self.canvas.draw() #视图和表格中数据为球面 def plot_sphere(self): #产生示例数据: np二维数组->(100,100) u = np.linspace(0, 2 * np.pi, 100) v = np.linspace(0, np.pi, 100) self.X = 10 * np.outer(np.cos(u), np.sin(v)) self.Y = 10 * np.outer(np.sin(u), np.sin(v)) self.Z = 9 * np.outer(np.ones(np.size(u)), np.cos(v)) self.set_canvas_table_configuration(len(self.X), (self.X[0], self.Y[0], self.Z[0])) #按当前球面的数据重新填写表格 self._ax.plot_surface(self.X, self.Y, self.Z) self.canvas.draw() #-------------------------------------------------------------------------------------------------------------------------------------------------------------- #自定义数据1:视图和表格中数据为从外部文件导入地形图数据: 本例导入CASS的抄测地形坐标文件 def plot_loadMapDatas(self): #导入的示例数据: np二维数组->(526,5) self.loadMapData('cass格式地形95个点.DAT') self.cassXyz=self.cass_Datas[:,2:5] self.X = self.cass_Datas[:,2:3].reshape(int(self.cass_Datas[:,2:3].size)) self.Y = self.cass_Datas[:,3:4].reshape(int(self.cass_Datas[:,3:4].size)) self.Z = self.cass_Datas[:,4:5].reshape(int(self.cass_Datas[:,4:5].size)) self.set_canvas_table_configuration(len(self.X), (self.X, self.Y, self.Z)) self._ax.plot_trisurf(self.X, self.Y, self.Z, linewidth=0.2, antialiased=True) self.canvas.draw() #自定义数据2:视图和表格中数据为从外部文件导入散点模形 def plot_load3DSapeDatas(self): #产生示例数据: np二维数组->(n,8) pass self.canvas.draw() #从外部地形数据文件导入全部点的坐标数据,示例采用CASS的dat格式数据 二维数组(n,5):如:1,,86240.8790,87568.3282,252.6600 #导入cass软件用的dat测绘数据 def loadMapData(self,mapDataFile,type=0): s='' lstRowData=[] lstData=[] index=0 #以下四个变量用于测试坐标点文件中是否存在差距较大的数,以提醒修改,如不修改,可能偏差点会对计算结果影响较大 minXYZ=[1000000,1000000,1000000] maxXYZ=[0,0,0] self.minXYZid=[0,0,0] self.maxXYZid=[0,0,0] if(os.path.exists(mapDataFile)): rf = open(mapDataFile,'r',encoding='utf-8') lindS='' for lineS in rf.readlines(): lstRowData.clear() s=lineS.strip() s=s.replace("\n","") #将从文件中读出的\n删除,此语句可能会报异常 s=s.replace("\r","") #将从文件中读出的\r删除,此语句可能会报异常 if(s==''):continue #去空行 lstRowData = s.split(',') if(len(lstRowData)!=5):continue #去格式不对的行(要求每行5个数据,4个逗号) lstRowData[0]=index #重新为数据编号 if(len(str(lstRowData[1]))!=0): lstRowData[1]=1 #对第二个参数不为空时,改其值为1,否则为0 else: lstRowData[1]=0 lstRowData[2]=float(lstRowData[2]) #dat文件中的坐标x坐标 lstRowData[3]=float(lstRowData[3]) #dat文件中的坐标y坐标 lstRowData[4]=float(lstRowData[4]) #dat文件中的坐标z坐标 lstData.append(copy.deepcopy(lstRowData)) #以下对导入的点作最大最小值记录 if(minXYZ[0]>lstRowData[2]): minXYZ[0]=lstRowData[2] self.minXYZid[0]=index if(minXYZ[1]>lstRowData[3]): minXYZ[1]=lstRowData[3] self.minXYZid[1]=index if(minXYZ[2]>lstRowData[4]): minXYZ[2]=lstRowData[4] self.minXYZid[2]=index if(maxXYZ[0]<lstRowData[2]): maxXYZ[0]=lstRowData[2] self.maxXYZid[0]=index if(maxXYZ[1]<lstRowData[3]): maxXYZ[1]=lstRowData[3] self.maxXYZid[1]=index if(maxXYZ[2]<lstRowData[4]): maxXYZ[2]=lstRowData[4] self.maxXYZid[2]=index index+=1 rf.close print(f'\n minXYZid={self.minXYZid}\n maxXYZid={self.maxXYZid}\n') self.pointCount=len(lstData) #记录本数据文件的总坐标点数 self.cass_Datas=np.array(lstData) #保存全部的np原始数据 #开始处理原始数据 print(f'导入cass测绘数据文件"{mapDataFile}"成功!') return True return False #------------------------------------------------------------------------------------------------------------------------------------------------------------- #槽函数:组合框选择项发生变化时 @Slot() def combo_option(self, text): if text == "网格": self.plot_wire() elif text == "表面": self.plot_surface() elif text == "三角网表面": self.plot_triangular_surface() elif text == "球面": self.plot_sphere() elif text == "导入cass地形图": self.plot_loadMapDatas() elif text == "导入散点模型": pass #暂未扩展 #方位角(XY平面)滑条控件值发生变化时槽函数 @Slot() def rotate_azim(self, value): self._ax.view_init(self._ax.elev, value) self.fig.set_canvas(self.canvas) self.canvas.draw() #方位角(Z向)滑条控件值发生变化时槽函数 @Slot() def rotate_elev(self, value): self._ax.view_init(value, self._ax.azim) self.fig.set_canvas(self.canvas) self.canvas.draw() if __name__ == "__main__": app = QApplication(sys.argv) w = ApplicationWindow() w.setFixedSize(1280, 720) w.show() app.exec()扩展导入的示例cass地形坐标文件内容如下,copy出来后粘贴到记事本另存文件为“cass格式地形95个点.DAT”,将此文件同代码模块文件放在一个目录里即可
1,,8535.0620,20831.2885,384.9640 2,,8564.7031,20818.7979,386.8800 3,,8566.4578,20824.8254,386.8800 4,,8580.2215,20819.2465,388.0300 5,,8590.5382,20815.9465,388.7660 6,,8525.9910,20830.1208,377.0900 7,,8502.7606,20839.6356,383.2100 8,,8537.2997,20855.6420,385.0000 9,,8537.6544,20857.5221,385.0000 10,,8531.2665,20856.1611,385.3500 11,,8534.0532,20857.8461,385.3500 12,,8533.6219,20853.4993,385.3500 13,,8595.2543,20819.8627,391.3100 14,,8558.8369,20813.6480,387.1910 15,,8576.1126,20813.9809,388.4000 16,,8582.0086,20813.3326,388.7660 17,,8574.6472,20807.6877,388.2800 18,,8578.2663,20799.6209,387.8800 19,,8581.3061,20806.9918,388.7000 20,,8544.7344,20858.6430,384.0600 21,,8546.2683,20821.1758,385.8000 22,,8550.3724,20833.8913,385.5660 23,,8544.0034,20833.0925,385.4460 24,,8597.5370,20808.7561,390.1870 25,,8588.3263,20809.5540,389.2140 26,,8581.5961,20810.1927,388.7600 27,,8575.4039,20810.7972,388.3400 28,,8569.2116,20813.0327,387.9020 29,,8560.1202,20816.6382,387.2510 30,,8548.2657,20828.3883,385.8600 31,,8547.2163,20833.4220,385.5060 32,,8546.3340,20840.2102,385.0240 33,,8545.5363,20848.8394,384.7000 34,,8553.8038,20819.7866,386.7750 35,,8550.7684,20823.7166,386.2300 36,,8544.1572,20863.7326,383.6830 37,,8543.6784,20868.8701,383.3000 38,,8529.1556,20863.1078,383.3000 39,,8533.9251,20864.9365,383.3000 40,,8538.7670,20866.7411,383.3000 41,,8548.0837,20870.6865,383.3000 42,,8540.6612,20862.3642,383.6830 43,,8536.5205,20861.3426,383.6830 44,,8531.3321,20860.6948,383.6830 45,,8541.7397,20859.0886,384.0600 46,,8538.8243,20859.4973,384.0600 47,,8528.7124,20850.9021,384.1650 48,,8521.5594,20849.2209,384.1650 49,,8511.9518,20846.3489,384.1650 50,,8531.5137,20846.2046,384.6400 51,,8537.3263,20847.9737,384.6400 52,,8533.9232,20839.3649,384.9640 53,,8540.2409,20839.6848,384.9640 54,,8540.3997,20832.9546,385.4460 55,,8526.7023,20856.3875,384.0600 56,,8520.6090,20855.1941,384.0600 57,,8512.6042,20852.9863,384.0600 58,,8503.9422,20850.4801,384.0600 59,,8499.1817,20849.5675,384.0600 60,,8527.0635,20859.6211,383.6830 61,,8520.2142,20858.3600,383.6830 62,,8511.2339,20856.1466,383.6830 63,,8503.0271,20854.0863,383.6830 64,,8495.8691,20853.3912,383.6830 65,,8585.2618,20790.1975,391.2700 66,,8583.4519,20792.3716,389.6900 67,,8526.2071,20833.0721,377.0900 68,,8511.7583,20834.1661,377.2200 69,,8501.9803,20833.0331,377.2200 70,,8485.7633,20837.8191,377.2800 71,,8480.4063,20841.6261,377.4100 72,,8483.0683,20846.3021,378.1000 73,,8596.9664,20786.3285,391.8000 74,,8587.9307,20791.0655,390.0370 75,,8586.8727,20802.9864,389.1540 76,,8596.9759,20800.4602,390.0300 77,,8565.5924,20800.8019,386.6600 78,,8521.5125,20838.1220,380.1400 79,,8487.6261,20847.4016,378.7800 80,,8497.7541,20845.0826,378.7100 81,,8494.2471,20850.5446,381.6400 82,,8514.0502,20841.4980,383.2100 83,,8524.2421,20843.4686,384.2000 84,,8541.1481,20826.6746,384.7300 85,,8561.0611,20806.2766,386.6300 86,,8547.6462,20816.0442,386.0600 87,,8557.0848,20809.5790,387.1910 88,,8566.6440,20805.8867,387.8420 89,,8580.9124,20803.9858,388.7000 90,,8558.3059,20823.0354,386.8800 91,,8570.3499,20819.9284,388.0300 92,,8587.7689,20820.1814,389.6800 93,,8571.6399,20826.1884,388.0000 94,,8556.9299,20831.2744,386.4100 95,,8550.9689,20844.2314,385.2700pyside6学习专栏(八):在PySide6中使用matplotlib库绘制三维图形由讯客互联互联网栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“pyside6学习专栏(八):在PySide6中使用matplotlib库绘制三维图形”