Building a Matplotlib GUI with Qt Designer: Part 3

This series of blog posts details the creation of a custom Qt application containing an interactive matplotlib widget and a plot selection list, which controls the currently displayed figure. In Part 1, we constructed our application framework and layout using Qt Designer. Part 2 of this series explored the custom subclass creation that was necessary to add custom logic to our application. In this installment, we will look at adding multiple figures to our application and including their names in the list widget. In addition, we will write some code to change the current figure by selecting from the names in the list widget. The final versions of the Qt Designer UI file and the “custommpl.py” script can be downloaded here.

Adding Items to the List

Adding text to our list is simple. Our list widget, which we called mplfigs, is an instance of the QListWidget class. This class has an addItem method which adds the given string argument to the list. Remember that if we also want to be able to switch figures by selecting names from the list, so to get us started, let’s modify the Main class’s __init__ method and add a new method calledaddfig.

Now any new instances of our application will contain an empty dictionary called fig_dict, which we will use to store our Figure instances by name. The new addfig method takes a name string argument and a Figure instance. The Figure is added to the dictionary under the name key, which is also added to our list widget. To see how this works, change the condition code block at the end of the program as follows. The resulting application window is shown below.

one_nameTo add a new figure, call rmmpl to remove the old figure then use addmpl and addfig to add the next figure. The plot will update and the new names will be added to the list; however, our application will have no way of knowing how to change the plots when you select names from the list.

Changing Plots Using the List

To connect the names in the list to the figure updating code, we need to take advantage of Qt’s signal and slot architecture.  The main Qt documentation has a very nice explanation of this concept, so I’ll just give a very brief overview here. Essentially, any “events” that occurs in a widget trigger a particular “signal”, which is defined by the widget. In our example, when an item in our list widget is selected, it triggers an itemClicked signal from the list, which emits a QListWidgetItem instance. Initially, the signal is not directed anywhere, so nothing happens. However, we can connect this signal to another widget’s slot, which is a (potentially custom) method that handles the emitted information. In our case, when the itemClicked signal is triggered, we want to remove the old figure (rmmpl) and add the figure (addmpl) defined by the emitted QListWidgetItem name. To make this possible, we “connect” the itemClicked signal to a new method that we’ll call changefig. This is more clearly seen in example code.

A few changes have been made to our initialization method. First of all, we connected the itemClicked signal to a new method called changefig. Remember that when this signal is triggered it emits a QListWidgetItem as well, so changefig must accept one argument, which is an instance of the list item. List widget items define a text method, which simple returns the text of the selected item. Once we know the item text, we can remove the old figure and display a new one based on the name defined by the selected text.

Notice that an empty figure instance was added to our plotting window in the initialization method. This is necessary because our first call to changefig will try to remove a previously displayed figure, which will throw an error if one is not displayed. The empty figure serves as a placeholder so the changefig method functions properly.

To test this, let’s add a couple of Figure instances to our application to see what it does. We’ll do this in the conditional code block at the end of the script.

At first, when you run this code, you will be presented with a blank plotting window; however, select one of the plot names from the list widget and see what happens.

success

SUCCESS!!!

Running Our Application from IPython

IPython is an extremely popular and powerful tool for interactive data analysis. In addition, it is well designed to work with external GUI event loops, which makes it possible to run custom applications as well. To get this to work, we must tell IPython that we will be running an external Qt GUI using the %gui qt cell magic (version >2.4 of IPython). With IPython started, we can start our GUI application in the following manner.

This will display our GUI with an empty plot window and figure list. We can recreate our example above by generating each figure and adding them to our application.

In this way, you can interactively process the data then add new figures to the GUI application as needed. This technique also works with IPython’s qtconsole and locally-hosted notebooks.

Building a Matplotlib GUI with Qt Designer: Part 2

In this second blog post, I’m going to add some of the custom logic to the application GUI that was constructed using Qt Designer in the Part 1. We’ll be using Python (version 3.4), PyQt (version 4.10.4), and matplotlib (version 1.4.3), but this code should work with Python 2.7 as well. I’ve found that these packages are most easily installed using Continuum Analytics’ Anaconda Python Distribution.

To begin, create an empty Python file called “custommpl.py” that will contain all the code in this example. This file should be located in the same directory as the UI file “window.ui” from Part 1. The final version of this Python file is included in the project zip package.

Imports

The necessary imports are the first element of our program and are shown below.

Let’s examine the matplotlib imports first. Notice that the generic pyplot module is not imported — do not use the functions from this module. The pyplot module defines wrapper functions that are meant to automate the creation of the interactive plotting window, and they will most likely cause problems for any custom GUI object. TheFigure class imported above is a very generic plotting container for all aspects of our plot. Instances of this object have the same methods as the figure objects created using pyplot.figure. In addition, we’ve also imported FigureCanvasQTAgg and the NavigationToolbar2QTfrom the PyQt4 backend module. These are both custom Qt widgets. A canvas contains and displays a Figure instance, and a navigation toolbar is the widget containing the buttons for interacting with the plot.

The loadUiType function requires a single argument, the name of a Designer UI file, and returns a tuple of the appropriate application classes. This can be done directly after the imports, as shown below.

In this case, the return tuple contains two class definitions. The first is our custom GUI application class, set to the Ui_MainWindow variable. The second is the base class for our custom GUI application, which in this case is PyQt4.QtGui.QMainWindow. The base class type is defined by the widget we first created with Designer. Note: These are not instances of these classes, but the class definitions themselves. They are meant to serve as superclasses to our new application logic class, which we’ll create next.

This usage differs from many other examples. For tutorials typically convert the UI file to a Python module using the pyuic4 utility (see Part 1 for details), and then import that module into a new script, i.e. from window import Ui_MainWindow. The downside of this approach is that, in addition to generating an extra file, others might be tempted to modify the Python module directly. If the application design is changes, which updates the UI file, then the pyuic4 conversion will destroy any custom modifications to the Python module. By using the UI file directly, as I’ve done above, it will not be possible to inadvertently alter the GUI design code.

The Minimal Subclass

Now we are ready to create a minimal subclass to run our GUI application. To start, we define a new class, called Main, that inherits from QMainWindow and Ui_MainWindow. Initialization of this class should call the __init__ method of QMainWindow as well as the setupUi method, which is defined in all Designer-created GUI applications.

To generate a functional GUI, a conditional code block is added to the end of the script. The leading if statement is telling Python to process this code block only if the program is run directly (e.g. $ python custommpl.py) rather than being imported into a different Python program (e.g. import custommpl). QApplication starts a Qt GUI event loop, which is a necessary prerequisite to running any Qt GUI applications. InstantiatingMain creates our custom application, but on its own, this will not display the GUI. This is accomplished by calling the show method. The sys.exit line simply ensures that the GUI event loop is closed properly when we quit the program. Running this file from the command line should produce the following window.

empty_main_window

Great! We’ve successfully created our first GUI! But at this point, it doesn’t do anything…

Adding a Plot

Next, add a new method, addmpl, to our Main class that inserts a plotting Figure instance into our matplotlib container.

The second line creates a FigureCanvas widget instance to contain our plot. This is stored as a Main instance attributed called canvas. Next, we need to remember a little information about our matplotlib container widget, which we created in Part 1. Recall that we enforced a vertical layout for any encapsulated widgets in mplwindow and named this vertical layout mplvl. This is attribute is a QVBoxLayout instance, which has an addWidget method for adding new vertically-aligned widgets into the container widget. We’ll use this method to upload our Figure widget. Calling the draw method on the canvas to renders the plot.

Side Note: The canvas object could have been added directly to our mplwindow container widget without using the vertical layout. This is done by setting the parent widget of the canvas: self.canvas.setParent(self.mplwindow). However, in this case, the figure will not expand to fit the container widget, and it will not resize with the window. In addition to vertically stacking widgets, the vertical layout instance ensures that the widgets is manages are properly stretched to fit the available area.

As a test, create a simple Figure instance in the conditional code block at the end of the module. Add this plot to the GUI using our new addmpl method (line 12). The code and resulting plot are show below.

plot_only

Adding a Toolbar

Although we’ve successfully added a plot to our application, there is currently no way to interact with the data. This is where the NavigationToolbar comes into play. Adding the toolbar to our plot involves a simple modification to the addmpl method.

The NavigationToolbar construction should be straightforward (lines 5-6). First, you need to connect this toolbar with canvas containing the axes and data. The second argument is the parent widget that will house this toolbar, which in this case is our mplwindow widget. The coordinates keyword argument controls the display of the cursor coordinates when the mouse is hovered over an axes. Finally, the toolbar widget is added into the vertical layout widget mplvl in the same way as the canvas. Because the toolbar was the second widget added, it will be placed under the plot window. The final version of this application is shown below.

plot_w_toolbar

To try something different, we can make the toolbar a detachable widget on the main window by using the following code. Compare the code below with the first example.

Changing plots

In order to change plots, we’re going to do something a little different. In principle, we could use the matplotlibaxes instances to modify the displayed data directly. However, this makes it hard to change the plot type/shape/number of the subplots. Instead, we will define a new method called rmmpl inside our Main class that completely removes the current plot and toolbar widget. New figures can then be added by calling the addmpl method with a new Figure instance. The advantage here is that Figure instances retain all of their plotting and layout information, so we don’t have to recreate these aspect with each plot change. Below is our code.

This should be pretty easy to understand. The removeWidget method removes the canvas and toolbar from our vertical layout instance. Calling theclose method on these widgets ensures that they are no longer displayed in the application window. If we don’t do this, new widgets will be overlaid on the previous plots, which causes problems.

To see how this might work, change the conditional code as shown below. Hit Enter in the terminal to break out of the input and switch plots. The second plot should look something like the one shown below.

dual_plot

Now we’ve created a very simple plotting window, to which we can dynamically change the displayed Figure instances, the final step in this process will be to add the figures to our list widget so users can easily switch plots by simply selecting a new different title in the list. Coding that behavior is straightforward, but will be covered in detail in new next post.

Building a Matplotlib GUI with Qt Designer: Part 1

Designer is a graphical tool for building complex Qt4 GUI applications. In this post, I will use Designer to construct a simple GUI application, and in the following posts, I’ll use Python, matplotlib, and PyQt4 to add the necessary application logic to display an interactive data plot and a plot selection list. The final result is shown below.

final_gui

One thing to keep in mind, Designer’s purpose is to help with GUI construction and widget layout. Application logic must be added in a separate step. That means that ultimately this example will differ from other Qt embedding examples in that we will end up with two files: the first, “window.ui”, defines our overall GUI design, and the second, “custommpl.py”, contains the custom application logic. A zip package of the completed files can be downloaded here.

Designing the Layout

A first step is to analyze the look of the GUI and decide how you will implement this with Designer. Although there are a number of ways to construct this layout, we will define a model that includes five major elements, with a generic graphical display shown below: 1) the main window holds everything together (black), 2) a list of figures (red), 3) a matplotlib container (blue), 4) a data plot (green), and 5) a navigation toolbar (purple).

layout

There are two major layouts to consider in our application. First, the matplotlib container and figure list are horizontally aligned inside of the main window, with the figure list taking up less overall space. Second, the data plot and toolbar are vertically aligned inside of the matplotlib container.

With this list of widgets and layouts in hand, let’s try to create this application framework in Designer.

The Designer Window

Upon starting Designer, you will be presented with a creation dialog. Select “Main Window” and click “Create”. You should now see something very close to the screen shot shown below. The “Main Window” that we’ve just created is going to act as an overall application container widget, in other words the black box in the generic figure above.

blank_window

This Designer window has four major elements that will be important for this tutorial.

  1. The blank, untitled “MainWindow” instance in the center of the screen is our application window. We will add widgets to this window to create our GUI layout. At this point, adjust the size of this window until it is approximately the size that you’d like to see for your final application.
  2. The “Widget Box” (left) contains all of the default Qt widgets that we can add to our MainWindow. To add a widget, click and drag the item into the MainWindow.
  3. The “Object Inspector” (top right) presents a tree of all the widgets that are currently present in our MainWindow. Use this to select and modify individual widgets. The Object indentation level indicates which widgets are contained inside other widgets. For example, the MainWindow widget currently contains centralwidget, menubar, and statusbar widgets.
  4. The “Property Editor”, just below the “Object Inspector”, is an editable list of properties for the currently selected widget.

Building the Framework

To start building our application, let’s add the two widgets that should be contained in the MainWindow: the figure list and the matplotlib container. To do this, drag and drop a “List Widget” (under Item Widgets) to the right side of the MainWindow and a generic Widget (under Containers) to the left hand side. These widgets will have some default size, which will not look correct.

no_h_layout

Manual adjustment of the widgets size and position is unnecessary. This is done automatically by applying the appropriate layout to the MainWindow container widget. Right click on the MainWindow and select “Lay Out”. You can select either “Lay Out Horizontally” or “Layout in a Grid”, both of which will have the same effect.

h_layout_select

This stretches both widgets until they completely fill the window; unfortunately, they will not be the correct shape. Size adjustments are made by changing several widget properties. First of all, select the List Widget (either by clicking on it in the MainWindow or selecting it from the Object Inspector) and set the following properties in the Property Editor:

  • sizePolicy->Horizontal Policy->Maximum
  • maximumSize->Width->200

Next, select the matplotlib container widget and set this property:

  • sizePolicy->Horizontal Policy->Expanding

At this point, our application window should look very much like our final product. However, we still need to setup the contents of our matplotlib container widget.

Adding the data plot and toolbar to the matplotlib container is best done as part of the application logic, but a vertical alignment should be enforced for the widgets contained in the matplotlib container. Unfortunately, a little hack is necessary to set the layout inside our matplotlib container. First of all, drag and drop a temporary widget (it doesn’t matter what you choose) into the matplotlib container. Now right click on the matplotlib container and set the “Lay Out” to “Lay Out Vertically”. With the layout set, you can delete the temporary widget from the plotting widget container.

Naming the Widgets

As a final step, we are going to change the names of some of the application elements. Ultimately, this application framework is going to be compiled into a Python class, and each of the widget and layout elements will be added as attributes of this class. The default attribute names are very generic “widget”, “listWidget”, etc., and renaming these widgets in Designer changes the attribute names as defined inside the main window class. This is not strictly necessary, but it will probably make it easier to keep track of the elements in a much larger GUI application. The widget names are shown in the “Object” column of the “Object Inspector”. To change the names, simply double-click the name and type in the new one. For this application, make the following name changes:

  • listWidget->mplfigs
  • widget->mplwindow

The layouts are also named attributes of our application, and it will be useful to change at least the name of the vertical alignment layout of “mplwindow”. Select the “mplwindow” widget in the Object Inspector, and scroll through the properties until you find the “Layout” section. Change the layoutName property from “verticalLayout” to “mplvl”.

Saving the Application

With the application design complete, save this project as “window.ui”. UI files are language-agnostic XML representations of our GUI. If you need to make changes to your layout in the future, reopen the UI file with Designer and make the necessary changes. In the next post, we’ll go over how to programmatically extract out our MainWindow class from the UI file and create a subclass with the appropriate logic.

PyQt4 ships with a command line utility script called pyuic4, which converts UI files into Python module files. Below is an example of the command line invocation of this script.

This is useful if you want to see how the MainWindow class and its attributes are defined in Python code; however, this step is unnecessary. Be Warned! Designer can not open Python module files, so you should never delete the UI file. Also, do not directly modify the resulting Python module. If you make layout changes to the UI file, the pyuic4 conversion process will overwrite your modified window module. To add additional logic to your MainWindow class, write a separate module file that defines a subclass of the MainWindow object. (See the next post.)