简体   繁体   English

在 pyqt5 gui 中嵌入 matplotlib 图

[英]Embed a matplotlib plot in a pyqt5 gui

I'm working with PyQt5 trying to generate a GUI for my data analysis tool.我正在使用 PyQt5 尝试为我的数据分析工具生成 GUI。 My problem is that I don't understand how to embed a matplotlib plot with full functionality.我的问题是我不明白如何嵌入具有完整功能的 matplotlib 图。

All of the tutorials about PyQt5 and how to embed matplotlib show a very simple way where they create all of the graphical object directly in the code.所有关于 PyQt5 以及如何嵌入 matplotlib 的教程都展示了一种非常简单的方法,它们直接在代码中创建所有图形对象。 I don't want to do that because my GUI was generated with Qt Designer.我不想这样做,因为我的 GUI 是用 Qt Designer 生成的。 I created a QWidget to plot data in it.我创建了一个 QWidget 来在其中绘制数据。 Therefore, I import the UI file in the code:因此,我在代码中导入UI文件:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
import numpy as np

from PyQt5.QtGui import QPixmap
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QMainWindow, QLabel, QGridLayout, QWidget, QTableWidget, QTableWidgetItem
from PyQt5.QtWidgets import QApplication, QWidget, QInputDialog, QLineEdit, QFileDialog
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtCore import QSize
from PyQt5 import QtCore, QtGui, uic

import matplotlib
matplotlib.use('QT5Agg')

import matplotlib.pylab as plt

from matplotlib.backends.qt_compat import QtCore, QtWidgets, is_pyqt5
from matplotlib.backends.backend_qt5agg import FigureCanvas,     NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure

class MyWindow(QMainWindow):
    def __init__(self):

        super(MyWindow, self).__init__()

        self.ui = uic.loadUi('test.ui', self) 

        # test data
        data = np.array([0.7,0.7,0.7,0.8,0.9,0.9,1.5,1.5,1.5,1.5])        
        fig, ax1 = plt.subplots()
        bins = np.arange(0.6, 1.62, 0.02)
        n1, bins1, patches1 = ax1.hist(data, bins, alpha=0.6, density=False, cumulative=False)

        # plot
        self.plotWidget = FigureCanvas(fig)        

        # add toolbar
        self.addToolBar(QtCore.Qt.BottomToolBarArea, NavigationToolbar(self.plotWidget, self))        

    #########################################

    # show window
    self.show() 

    #########################################

if __name__ == '__main__':

    app = QtWidgets.QApplication(sys.argv)
    window = MyWindow()
    sys.exit(app.exec_())

This is test.ui file created by Qt Designer:这是 Qt Designer 创建的 test.ui 文件:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Form</class>
 <widget class="QWidget" name="Form">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>772</width>
    <height>650</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Form</string>
  </property>
  <widget class="QWidget" name="plotWidge" native="true">
   <property name="geometry">
    <rect>
     <x>20</x>
     <y>20</y>
     <width>721</width>
     <height>571</height>
    </rect>
   </property>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>

What happened is that I can see a toolbar in my window but now data.发生的事情是我可以在我的窗口中看到一个工具栏,但现在是数据。 I can use the "save" icon to save the plot as an image.我可以使用“保存”图标将绘图保存为图像。 I think that I'm creating a second instance of a widget object that is not correlated to the one I created in Qt Designer.我认为我正在创建与我在 Qt Designer 中创建的对象无关的小部件对象的第二个实例。

How can I solve this problem?我怎么解决这个问题? Thanks in advance!提前致谢!

I think you are confusing that you have created a widget called plotWidge in Qt Designer and you doing self.plotWidget = FigureCanvas(fig) are replacing it (even if the names coincide that action is not the one done), so that it does not cause confusion change plotWidge to content_plot in Qt Designer so the plotWidget must be placed in content_plot with the help of a layout.我认为您很困惑,您在 Qt Designer 中创建了一个名为plotWidge的小部件,并且您正在执行self.plotWidget = FigureCanvas(fig)正在替换它(即使名称一致,该操作不是已完成的操作),因此它不会造成混乱变化plotWidgecontent_plot在Qt Designer中,因此plotWidget必须放置在content_plot与布局的帮助。

test.ui测试界面

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Form</class>
 <widget class="QWidget" name="Form">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>772</width>
    <height>650</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Form</string>
  </property>
  <widget class="QWidget" name="content_plot" native="true">
   <property name="geometry">
    <rect>
     <x>20</x>
     <y>20</y>
     <width>721</width>
     <height>571</height>
    </rect>
   </property>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>

*.py *.py

import sys
import numpy as np

from PyQt5 import QtCore, QtWidgets, uic

import matplotlib
matplotlib.use('QT5Agg')

import matplotlib.pylab as plt
from matplotlib.backends.backend_qt5agg import FigureCanvas 
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar

class MyWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super(MyWindow, self).__init__()
        uic.loadUi('test.ui', self) 
        # test data
        data = np.array([0.7,0.7,0.7,0.8,0.9,0.9,1.5,1.5,1.5,1.5])        
        fig, ax1 = plt.subplots()
        bins = np.arange(0.6, 1.62, 0.02)
        n1, bins1, patches1 = ax1.hist(data, bins, alpha=0.6, density=False, cumulative=False)
        # plot
        self.plotWidget = FigureCanvas(fig)
        lay = QtWidgets.QVBoxLayout(self.content_plot)  
        lay.setContentsMargins(0, 0, 0, 0)      
        lay.addWidget(self.plotWidget)
        # add toolbar
        self.addToolBar(QtCore.Qt.BottomToolBarArea, NavigationToolbar(self.plotWidget, self))

if __name__ == '__main__':

    app = QtWidgets.QApplication(sys.argv)
    window = MyWindow()
    window.show()
    sys.exit(app.exec_())

在此处输入图片说明

The GUI design becomes more flexible with the use of super() in Python.在 Python 中使用 super() 后,GUI 设计变得更加灵活。 After creating the GUI (.ui) using Qt designer we can further extend its functionality by converting it to python (.py) file: pyuic5 -x test.ui -o test.py使用 Qt 设计器创建 GUI (.ui) 后,我们可以通过将其转换为 python (.py) 文件来进一步扩展其功能: pyuic5 -x test.ui -o test.py

For those looking for a solution that starts from at Qt designer and ends at something like this tutorial code :对于那些寻找从 Qt 设计器开始并以本教程代码结束的解决方案的人:

在此处输入图片说明

The test.ui测试.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>800</width>
    <height>600</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QGridLayout" name="gridLayout">
    <item row="0" column="0">
     <layout class="QHBoxLayout" name="horizontalLayout">
      <item>
       <widget class="QLabel" name="label">
        <property name="text">
         <string>Select Theme</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QComboBox" name="comboBox"/>
      </item>
      <item>
       <widget class="QPushButton" name="pushButton">
        <property name="text">
         <string>Open</string>
        </property>
       </widget>
      </item>
      <item>
       <spacer name="horizontalSpacer">
        <property name="orientation">
         <enum>Qt::Horizontal</enum>
        </property>
        <property name="sizeHint" stdset="0">
         <size>
          <width>40</width>
          <height>20</height>
         </size>
        </property>
       </spacer>
      </item>
     </layout>
    </item>
    <item row="1" column="0">
     <layout class="QVBoxLayout" name="verticalLayout">
      <item>
       <spacer name="verticalSpacer">
        <property name="orientation">
         <enum>Qt::Vertical</enum>
        </property>
        <property name="sizeHint" stdset="0">
         <size>
          <width>20</width>
          <height>40</height>
         </size>
        </property>
       </spacer>
      </item>
     </layout>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>800</width>
     <height>22</height>
    </rect>
   </property>
   <widget class="QMenu" name="menuFile">
    <property name="title">
     <string>File</string>
    </property>
    <addaction name="actionOpen_csv_file"/>
    <addaction name="actionExit"/>
   </widget>
   <addaction name="menuFile"/>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
  <action name="actionOpen_csv_file">
   <property name="text">
    <string>Open csv file</string>
   </property>
  </action>
  <action name="actionExit">
   <property name="text">
    <string>Exit</string>
   </property>
  </action>
 </widget>
 <resources/>
 <connections/>
</ui>

and the complete edited test.py和完整编辑过的 test.py

from PyQt5 import QtCore, QtGui, QtWidgets
import matplotlib
import matplotlib.pyplot as plt
matplotlib.use('Qt5Agg')
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QFileDialog
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT as Navi
from matplotlib.figure import Figure
import seaborn as sns
import pandas as pd
import sip # can be installed : pip install sip

# We require a canvas class
import platform

# Use NSURL as a workaround to pyside/Qt4 behaviour for dragging and dropping on OSx
op_sys = platform.system()
if op_sys == 'Darwin':
    from Foundation import NSURL

class MatplotlibCanvas(FigureCanvasQTAgg):
    def __init__(self,parent=None, dpi = 120):
        fig = Figure(dpi = dpi)
        self.axes = fig.add_subplot(111)
        super(MatplotlibCanvas,self).__init__(fig)
        fig.tight_layout()
        
        

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(1440, 1000)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
        self.gridLayout.setObjectName("gridLayout")
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setObjectName("label")
        self.horizontalLayout.addWidget(self.label)
        self.comboBox = QtWidgets.QComboBox(self.centralwidget)
        self.comboBox.setObjectName("comboBox")
        self.horizontalLayout.addWidget(self.comboBox)
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setObjectName("pushButton")
        self.horizontalLayout.addWidget(self.pushButton)
        spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout.addItem(spacerItem)
        self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 1)
        self.verticalLayout = QtWidgets.QVBoxLayout()
        self.verticalLayout.setObjectName("verticalLayout")
        self.spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
        self.verticalLayout.addItem(self.spacerItem1)
        self.gridLayout.addLayout(self.verticalLayout, 1, 0, 1, 1)
        
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 22))
        self.menubar.setObjectName("menubar")
        self.menuFile = QtWidgets.QMenu(self.menubar)
        self.menuFile.setObjectName("menuFile")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)
        self.actionOpen_csv_file = QtWidgets.QAction(MainWindow)
        self.actionOpen_csv_file.setObjectName("actionOpen_csv_file")
        self.actionExit = QtWidgets.QAction(MainWindow)
        self.actionExit.setObjectName("actionExit")
        self.menuFile.addAction(self.actionOpen_csv_file)
        self.menuFile.addAction(self.actionExit)
        self.menubar.addAction(self.menuFile.menuAction())

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)
        
        self.filename = ''
        self.canv = MatplotlibCanvas(self)
        self.df = []
        
        self.toolbar = Navi(self.canv,self.centralwidget)
        self.horizontalLayout.addWidget(self.toolbar)
        
        self.themes = ['bmh', 'classic', 'dark_background', 'fast', 
        'fivethirtyeight', 'ggplot', 'grayscale', 'seaborn-bright',
         'seaborn-colorblind', 'seaborn-dark-palette', 'seaborn-dark', 
         'seaborn-darkgrid', 'seaborn-deep', 'seaborn-muted', 'seaborn-notebook',
         'seaborn-paper', 'seaborn-pastel', 'seaborn-poster', 'seaborn-talk',
         'seaborn-ticks', 'seaborn-white', 'seaborn-whitegrid', 'seaborn',
         'Solarize_Light2', 'tableau-colorblind10']
         
        self.comboBox.addItems(self.themes)
        
        self.pushButton.clicked.connect(self.getFile)
        self.comboBox.currentIndexChanged['QString'].connect(self.Update)
        self.actionExit.triggered.connect(MainWindow.close)
        self.actionOpen_csv_file.triggered.connect(self.getFile)
        
    def Update(self,value):
        print("Value from Combo Box:",value)
        plt.clf()
        plt.style.use(value)
        try:
            self.horizontalLayout.removeWidget(self.toolbar)
            self.verticalLayout.removeWidget(self.canv)
            
            sip.delete(self.toolbar)
            sip.delete(self.canv)
            self.toolbar = None
            self.canv = None
            self.verticalLayout.removeItem(self.spacerItem1)
        except Exception as e:
            print(e)
            pass
        self.canv = MatplotlibCanvas(self)
        self.toolbar = Navi(self.canv,self.centralwidget)
        
        self.horizontalLayout.addWidget(self.toolbar)
        self.verticalLayout.addWidget(self.canv)
        
        self.canv.axes.cla()
        ax = self.canv.axes
        self.df.plot(ax = self.canv.axes)
        legend = ax.legend()
        legend.set_draggable(True)
        
        ax.set_xlabel('X axis')
        ax.set_ylabel('Y axis')
        ax.set_title(self.Title)
        
        self.canv.draw()
        
        
        
        
    
    def getFile(self):
        """ This function will get the address of the csv file location
            also calls a readData function 
        """
        self.filename = QFileDialog.getOpenFileName(filter = "csv (*.csv)")[0]
        print("File :", self.filename)
        self.readData()
    
    def readData(self):
        """ This function will read the data using pandas and call the update
            function to plot
        """
        import os
        base_name = os.path.basename(self.filename)
        self.Title = os.path.splitext(base_name)[0]
        print('FILE',self.Title )
        self.df = pd.read_csv(self.filename,encoding = 'utf-8').fillna(0)
        self.Update(self.themes[0]) # lets 0th theme be the default : bmh
    

    
    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.label.setText(_translate("MainWindow", "Select Theme"))
        self.pushButton.setText(_translate("MainWindow", "Open"))
        self.menuFile.setTitle(_translate("MainWindow", "File"))
        self.actionOpen_csv_file.setText(_translate("MainWindow", "Open csv file"))
        self.actionExit.setText(_translate("MainWindow", "Exit"))


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    
    MainWindow.show()
    sys.exit(app.exec_())

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM