A minimal, working starting point for scientific desktop apps built with Traits, TraitsUI, Qt5 and matplotlib.
Python2 is dead. Long live Python3.
This template is an upgrade of my Python2 template, stripped back to the simplest useful combination of Traits/TraitsUI and matplotlib — no Mayavi. The figure embedding leans on Pierre Haessig's gist, ported here to Qt5.
Copy the repository, rename it, and start replacing the example tab with your own.
- An embedded matplotlib canvas with the standard navigation toolbar (pan, zoom, save), wired into TraitsUI as a reusable editor.
- A split layout — the shared figure on the left, a tabbed panel of controls on the right — that scales to as many tabs as you need.
- Live plot styling: marker shape, size and colour controls that act on the plotted data immediately.
- A worked example of the common widgets — sliders, checkboxes, dropdowns, text fields, a directory picker, buttons, read-only outputs, and a group that enables itself conditionally.
- Headless tests and CI, so you can refactor the template without wondering what you broke.
- Python 3.9 or newer
- A Qt binding.
requirements.txtinstalls PyQt5; PySide2 and PySide6 also work, as pyface uses whichever it finds.
On a bare Linux machine you may also need Qt's system libraries — see the CI workflow for the exact package list.
With pip:
git clone https://github.com/bgriffen/Python3GUITemplate.git
cd Python3GUITemplate
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txtOr with conda:
conda env create -f environment.yml
conda activate Python3GUITemplatepython main.pyDrag the gradient and y-intercept sliders to redraw the line, then change the Marker,
Size and Colour controls to restyle it. Button 1 rolls a random number, which disables
the Boolean Option group once it lands above 0.5 — an example of enabled_when.
To check your environment in isolation before debugging the full app:
python standalonematplotlib.pyThat opens a single figure with a rectangle selector attached; drag a box over the curve and the selection coordinates are printed to stdout.
| File | Purpose |
|---|---|
main.py |
ApplicationMain — the window, the shared figure, and the marker controls. Start here. |
firstcalc.py |
FirstCalc — the example tab. Copy this to add your own. |
CommonMPL.py |
MPLFigureEditor, the TraitsUI editor that embeds a matplotlib figure in Qt. |
standalonematplotlib.py |
The editor on its own, for smoke-testing an environment. |
tests/ |
Headless regression tests. |
Write a HasTraits class with a view, taking the main window as its first argument — say in a new
secondcalc.py:
class SecondCalc(HasTraits):
amplitude = Range(0.0, 5.0, 1.0)
view = View(Item("amplitude"))
def __init__(self, main, **kwargs):
super().__init__(**kwargs)
self.main = main
def _amplitude_changed(self):
x = np.linspace(0, 10, 200)
self.main.plot_xy(x, self.amplitude * np.sin(x))Then import it in main.py and register it on ApplicationMain:
secondcalc = Instance(SecondCalc)
def _secondcalc_default(self):
return SecondCalc(self)
right_panel = Tabbed(
Item("firstcalc", style="custom", label="First Tab", show_label=False),
Item("secondcalc", style="custom", label="Second Tab", show_label=False),
)Route drawing through ApplicationMain.plot_xy() rather than touching the figure directly. It
reuses the axes and keeps the marker controls pointing at the current line; clearing the figure
yourself throws away the axis labels and silently breaks them.
pip install pytest
pytestThe suite runs without a display — conftest.py selects Qt's offscreen platform plugin — so it
works over ssh, in containers and in CI.
ruff check . # lint, matching the CI jobTraits calls a method named _<trait>_changed whenever that trait's value changes, and
_<trait>_fired when a Button is pressed. That naming is the whole wiring mechanism — there is no
explicit connect step. Two consequences worth knowing:
- A
Rangewhose default sits outside its own bounds will be clamped by the editor as the UI is built, firing a change handler before the window is ever shown. - The methods are bound when the class is created, so patching one afterwards has no effect.
There is comprehensive documentation for Traits and a set of TraitsUI tutorials.
- Pierre Haessig for the Qt matplotlib editor.
- Gael Varoquaux's TraitsUI scientific application tutorial, and Didrik Pinte for the original Qt version.
