├── .gitignore ├── LICENSE ├── README.rst ├── application.py ├── build_ui.py ├── screenshot.png ├── take_screenshot.py ├── template.py └── template.ui /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.pyc 3 | __pycache__ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Kenneth Lyons 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | embed-pyqtgraph-tutorial 3 | ======================== 4 | 5 | This is a walkthrough of embedding pyqtgraph_ content in a PyQt application 6 | you're designing with Qt Designer. Here's an overview of the steps involved: 7 | 8 | * Create the layout of the UI in Qt Designer (generates ``template.ui``) 9 | * Compile the XML template to Python source (uses ``build_ui.py`` to generate 10 | ``template.py``) 11 | * Write a custom QWidget class which takes on the UI and populates the embedded 12 | pyqtgraph widget (``application.py``) 13 | 14 | The example provided displays a `PlotWidget`_ with some data and a QCheckBox to 15 | enable/disable mouse control of the plot. It's a simple example, but once 16 | you've seen the process of adding pyqtgraph widgets to your UI, you can create 17 | arbitrarily complex applications without having to write a ton of boilerplate 18 | layout code. 19 | 20 | .. image:: screenshot.png 21 | 22 | 23 | Prerequisites 24 | ============= 25 | 26 | I'm going to assume you have some way of creating Qt applications with Python, 27 | and the instructions here should not be much (if at all) different between the 28 | ways to accomplish that (i.e. PyQt4, PyQt5, PySide, PySide2). For reference, 29 | I'm using PyQt5 and Python 3, so the following works for setting up an 30 | environment for running the example:: 31 | 32 | $ python -m venv .venv 33 | $ source .venv/bin/activate 34 | (.venv) $ pip install pyqt5 pyqtgraph 35 | (.venv) $ python application.py 36 | 37 | If you're using a different setup (e.g. PyQt4, PySide), you'll need to fix some 38 | imports before being able to run the example application. 39 | 40 | I'm also going to assume you have have some knowledge of how to use pyqtgraph. 41 | The `pyqtgraph examples`_ cover its capabilities pretty well. 42 | 43 | 44 | Tutorial 45 | ======== 46 | 47 | Creating the UI Layout 48 | ---------------------- 49 | 50 | :: 51 | 52 | ┌────────────┐ ┌─────────────┐ ┌─────────────┐ 53 | │ creativity │ → │ Qt Designer │ → │ template.ui │ 54 | └────────────┘ └─────────────┘ └─────────────┘ 55 | 56 | Generally, you can use custom widgets in Qt Designer through the `promote`_ 57 | mechanism. This means you add an item of the base class of your custom item to 58 | your UI layout, right click on it, then go to "Promote to ...". Since we're 59 | using Python instead of C++, the "Header file" in this case is going to be the 60 | Python namespace at which you would access the class as though you were 61 | importing it. For example, if you had a custom widget class, where an import 62 | statement might look like: 63 | 64 | .. code:: python 65 | 66 | from myproject.widgets import CustomDialog 67 | 68 | you would put ``myproject.widgets`` in the "Header file" field and 69 | ``CustomDialog`` in the "Promoted class name" field. The basic Qt class you 70 | inherit from (e.g. QDialog, QWidget, etc.) is then the "Base class name." 71 | 72 | pyqtgraph actually provides some documentation for how to embed plotting 73 | objects into a Qt UI using Qt Designer (see `embed pyqtgraph`_), but it's a bit 74 | lacking. In our case, add a QGraphicsView to the layout where you want the plot 75 | to be, then promote it to a PlotWidget. Most pyqtgraph classes are available 76 | under the pyqtgraph namespace, so just put "pyqtgraph" in the header file 77 | field. 78 | 79 | If you inspect the ``template.ui`` file, you can see towards the bottom there 80 | is a ``customwidget`` section specifying everything we just covered: 81 | 82 | ========== ============== 83 | class PlotWidget 84 | base class QGraphicsView 85 | namespace pyqtgraph 86 | ========== ============== 87 | 88 | Compiling the Layout 89 | -------------------- 90 | 91 | :: 92 | 93 | ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ 94 | │ template.ui │ → │ build_ui.py │ → │ template.py │ 95 | └─────────────┘ └─────────────┘ └─────────────┘ 96 | 97 | There are a few ways to turn your UI template into Python code. The first (my 98 | preferred) way of doing it is to write a little script to grab all of your 99 | ``*.ui`` files and use the `pyuic`_ module to write the corresponding Python 100 | code to the desired destination. An example is given here (``build_ui.py``). 101 | You can modify the ``SRC`` and ``DEST`` variables if you want to arrange your 102 | project structure differently. My recommendation is to put your UI files in 103 | something like ``res/`` and the compiled Python files in your project source to 104 | make importing simple (see the next step). 105 | 106 | You could instead use ``pyuic`` directly from the command line. For single UI 107 | templates, this is fine, but you'll want a scripted approach if you end up with 108 | more. For reference, here's how you'd convert the template given here:: 109 | 110 | (.venv) $ pyuic5 template.ui -o template.py 111 | 112 | The third option would be to use the UI template directly from your 113 | hand-written Python code (i.e. using ``uic.loadUI()``). I tend to avoid this to 114 | make packaging the project easier, but it is probably not all that difficult to 115 | package the UI templates. If you're already dealing with packaging custom icon 116 | files and such, this would be a non-issue. 117 | 118 | Now, if you take a look at the ``template.py`` file, you'll see there's a class 119 | called ``Ui_CustomWidget`` that has, primarily, a ``setupUi`` method to create 120 | and lay out all of the content we designed in Qt Designer. This is a pretty 121 | simple example, but with complex layouts with many items, the use of Qt 122 | Designer and automatic generation of this code really shines. 123 | 124 | Also notice the import line at the bottom of the file: 125 | 126 | .. code-block:: python 127 | 128 | from pyqtgraph import PlotWidget 129 | 130 | This all comes straight from that bottom section of the ``template.ui`` file, 131 | where we specified in Qt Designer that we have a custom item of a specific 132 | class and where it can be found. 133 | 134 | Wrapping the UI in a Custom QWidget 135 | ----------------------------------- 136 | 137 | :: 138 | 139 | ┌─────────────┐ ┌────────────────┐ ┌─────────┐ 140 | │ template.py │ → │ application.py │ → │ success │ 141 | └─────────────┘ └────────────────┘ └─────────┘ 142 | 143 | Now for the difficult part. PyQt provides some documentation for `using the 144 | generated code`_, though it is a little sparse if you don't have the high-level 145 | overview of the whole process. Essentially, you write a custom class (usually 146 | QWidget, but really it can be any QWidget-like class such as QMainWindow, 147 | QDialog, etc.), then use the compiled Python code to access and interact with 148 | the layout you specified. The key here is that you *are not modifying the 149 | generated Python code*. That means in our case that we don't want to mess with 150 | ``template.py`` by hand. If you make changes to it, you can no longer make 151 | changes to your UI from Qt Designer because re-generating the template will 152 | overwrite your changes! 153 | 154 | The nice thing about this workflow overall is that it cleans up your custom 155 | class code a lot, since you're not creating container layouts all over the 156 | place, specifying sizes, size policies, etc. 157 | 158 | Our implementation is in ``application.py``. It starts out with importing Qt 159 | stuff so we can set up a QApplication and run it. We also *import the template 160 | module* so we have access to the template class (PyQt refers to it as the *form 161 | class*). We then write a custom QWidget implementation which instantiates the 162 | form class and calls its ``setupUi`` method. I typically assign the form class 163 | to an attribute called ``ui``. Once you call ``setupUi``, you now can access 164 | the items in your UI through the names they were given in Qt Designer. 165 | 166 | So in our example, we access the ``plotWidget`` attribute of the form class 167 | object, which is a pyqtgraph PlotWidget object. Now the pyqtgraph API applies, 168 | so we can plot some content and whatever else we need. In this case, we connect 169 | up the checkbox to a method that toggles mouse functionality on the PlotWidget. 170 | 171 | *Note: It is possible to make CustomWidget inherit from UI_CustomWidget (so 172 | you'd call ``self.setupUi()``), but I prefer to explicitly set up the form 173 | class object as an attribute of the custom widget -- mostly because it's very 174 | clear when you're accessing UI elements specified by Designer rather than 175 | widgets you might add programmatically.* 176 | 177 | 178 | Other Notes 179 | =========== 180 | 181 | Feel free to open an issue if anything here isn't clear. 182 | 183 | I originally wrote this in response to seeing questions on the `pyqtgraph 184 | mailing list`_ about using pyqtgraph functionality in an application designed 185 | with Qt Designer. It's not obvious at all that you can use the promote 186 | mechanism with PyQt (as opposed to C++ Qt), so I wrote this to help people out. 187 | Feel free to use these materials in a pull request to improve pyqtgraph 188 | documentation. 189 | 190 | 191 | .. _pyqtgraph: http://pyqtgraph.org/ 192 | .. _PlotWidget: http://pyqtgraph.org/documentation/widgets/plotwidget.html 193 | .. _pyqtgraph examples: https://github.com/pyqtgraph/pyqtgraph/tree/develop/examples 194 | .. _embed pyqtgraph: http://pyqtgraph.org/documentation/how_to_use.html#embedding-widgets-inside-pyqt-applications 195 | .. _promote: http://doc.qt.io/qt-5/designer-using-custom-widgets.html 196 | .. _pyuic: http://pyqt.sourceforge.net/Docs/PyQt5/designer.html#the-uic-module 197 | .. _using the generated code: http://pyqt.sourceforge.net/Docs/PyQt5/designer.html 198 | .. _pyqtgraph mailing list: https://groups.google.com/forum/#!forum/pyqtgraph 199 | -------------------------------------------------------------------------------- /application.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtGui, QtCore 2 | 3 | # import the "form class" from your compiled UI 4 | from template import Ui_CustomWidget 5 | 6 | 7 | class CustomWidget(QtGui.QWidget): 8 | 9 | def __init__(self, parent=None): 10 | super(CustomWidget, self).__init__(parent=parent) 11 | 12 | # set up the form class as a `ui` attribute 13 | self.ui = Ui_CustomWidget() 14 | self.ui.setupUi(self) 15 | 16 | # access your UI elements through the `ui` attribute 17 | self.ui.plotWidget.plot(x=[0.0, 1.0, 2.0, 3.0], 18 | y=[4.4, 2.5, 2.1, 2.2]) 19 | 20 | # simple demonstration of pure Qt widgets interacting with pyqtgraph 21 | self.ui.checkBox.stateChanged.connect(self.toggleMouse) 22 | 23 | def toggleMouse(self, state): 24 | if state == QtCore.Qt.Checked: 25 | enabled = True 26 | else: 27 | enabled = False 28 | 29 | self.ui.plotWidget.setMouseEnabled(x=enabled, y=enabled) 30 | 31 | 32 | if __name__ == '__main__': 33 | app = QtGui.QApplication([]) 34 | widget = CustomWidget() 35 | widget.show() 36 | app.exec_() 37 | -------------------------------------------------------------------------------- /build_ui.py: -------------------------------------------------------------------------------- 1 | """ 2 | Compiles PyQt UI templates found in SRC and places the corresponding compiled 3 | Python modules in DEST. Output files have the same name, but have a `.py` 4 | extension instead of `.ui`. 5 | """ 6 | 7 | import os 8 | from PyQt5 import uic 9 | 10 | SRC = '.' 11 | DEST = '.' 12 | 13 | if __name__ == '__main__': 14 | uic.compileUiDir(SRC, map=lambda src, name: (DEST, name)) 15 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ixjlyons/embed-pyqtgraph-tutorial/8d13b235729366f025f2ab66f6c18c1dd222f962/screenshot.png -------------------------------------------------------------------------------- /take_screenshot.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtGui 2 | import application 3 | 4 | fname = 'screenshot.png' 5 | 6 | if __name__ == '__main__': 7 | app = QtGui.QApplication([]) 8 | widget = application.CustomWidget() 9 | pixmap = QtGui.QPixmap(widget.size()) 10 | widget.render(pixmap) 11 | pixmap.save(fname) 12 | -------------------------------------------------------------------------------- /template.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'template.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.11.3 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | from PyQt5 import QtCore, QtGui, QtWidgets 10 | 11 | class Ui_CustomWidget(object): 12 | def setupUi(self, CustomWidget): 13 | CustomWidget.setObjectName("CustomWidget") 14 | CustomWidget.resize(400, 300) 15 | self.gridLayout = QtWidgets.QGridLayout(CustomWidget) 16 | self.gridLayout.setObjectName("gridLayout") 17 | self.plotWidget = PlotWidget(CustomWidget) 18 | self.plotWidget.setObjectName("plotWidget") 19 | self.gridLayout.addWidget(self.plotWidget, 0, 0, 1, 1) 20 | self.checkBox = QtWidgets.QCheckBox(CustomWidget) 21 | self.checkBox.setChecked(True) 22 | self.checkBox.setObjectName("checkBox") 23 | self.gridLayout.addWidget(self.checkBox, 1, 0, 1, 1) 24 | 25 | self.retranslateUi(CustomWidget) 26 | QtCore.QMetaObject.connectSlotsByName(CustomWidget) 27 | 28 | def retranslateUi(self, CustomWidget): 29 | _translate = QtCore.QCoreApplication.translate 30 | CustomWidget.setWindowTitle(_translate("CustomWidget", "Form")) 31 | self.checkBox.setText(_translate("CustomWidget", "Mouse Enabled")) 32 | 33 | from pyqtgraph import PlotWidget 34 | -------------------------------------------------------------------------------- /template.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | CustomWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Mouse Enabled 24 | 25 | 26 | true 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | PlotWidget 35 | QGraphicsView 36 |
pyqtgraph
37 |
38 |
39 | 40 | 41 |
42 | --------------------------------------------------------------------------------