A batteries-included starting point for scientific desktop GUIs in Python, built on Traits, TraitsUI, Matplotlib and Mayavi.
Clone it, run it, then replace the example panels with your own. You get a resizable two-pane window with an embedded Matplotlib canvas (including the standard navigation toolbar), an optional Mayavi 3D scene, and tabbed control panels wired to the display — all of which usually take a frustrating afternoon to assemble from scratch.
- Embedded Matplotlib canvas with the full navigation toolbar, in a resizable split pane.
- Runs under Qt or wx. The Matplotlib editor picks the right canvas for whichever ETS toolkit is active, so you are not locked into wxPython.
- Optional 3D. Mayavi powers a 3D scene tab when installed; when it is not, the tab degrades to an install hint and everything else keeps working.
- Live-updating controls. Marker colour, style and size apply to the current plot immediately; panel controls redraw as you drag them.
- Two worked example panels covering the editors you will actually reach
for — ranges, enums, booleans, buttons, directory and file pickers,
enabled_when, read-only readouts and CSV export. - Tested and linted, with a headless pytest suite and CI.
- Python 3.9 or newer
- One GUI toolkit: Qt (recommended) or wx
- TraitsUI 7.2+ — older versions render black backgrounds and other oddities
git clone https://github.com/bgriffen/PythonGUITemplate.git
cd PythonGUITemplate
python -m venv .venv && source .venv/bin/activateThen install with the toolkit you want. Pick one:
pip install -e '.[qt]' # Qt via PySide6 - recommended, installs cleanly everywhere
pip install -e '.[wx]' # wxPython - the original backendQt is the easier of the two to install; wxPython frequently needs to build from
source on Linux. If you install both, set ETS_TOOLKIT=qt (or wx) to say
which one wins.
pip install -e '.[qt,3d]'This pulls in Mayavi and VTK — a large download and the most fragile part of the stack. It is entirely optional: without it the app runs normally and the Mayavi tab shows installation instructions instead.
python main.pyor, equivalently:
python -m pyguitemplate
pyguitemplate # console script, after installingOn macOS with the wx backend you may need pythonw main.py instead, so the
process is treated as a proper windowed application. This is not necessary
under Qt.
pyguitemplate/
├── app.py # ApplicationMain: the window, the split layout, shared plot state
├── mpl_editor.py # Toolkit-agnostic TraitsUI editor embedding a Matplotlib figure
├── scene.py # Mayavi 3D tab, with a fallback when Mayavi is missing
├── colors.py # ColorTrait -> Matplotlib colour conversion
└── panels/
├── first_panel.py # Example: a tour of the common Traits editors
└── second_panel.py # Example: a small signal generator with CSV export
tests/ # Headless pytest suite
main.py # Launcher
Panels are plain HasTraits classes. They receive the ApplicationMain
instance so they can drive the shared display, and they declare a View that
lays out their controls.
# pyguitemplate/panels/my_panel.py
import numpy as np
from traits.api import Any, Button, HasTraits, Range
from traitsui.api import Item, View
class MyPanel(HasTraits):
main = Any()
exponent = Range(1.0, 4.0, 2.0)
plotbutton = Button("Plot")
view = View(
Item("exponent", label="Exponent"),
Item("plotbutton", show_label=False),
)
def replot(self):
x = np.linspace(0.0, 5.0, 200)
self.main.plot(x, x**self.exponent)
# Traits calls this automatically whenever `exponent` changes.
def _exponent_changed(self):
self.replot()
def _plotbutton_fired(self):
self.replot()
def __init__(self, main, **kwargs):
super().__init__(**kwargs)
self.main = mainThen register it in app.py — add an Instance trait, a _my_panel_default
factory, and an Item in right_panel:
my_panel = Instance(MyPanel)
def _my_panel_default(self):
return MyPanel(self)
right_panel = Tabbed(
...,
Item("my_panel", style="custom", label="My Tab", show_label=False),
)Two conventions worth following:
- Plot through
main.plot(x, y)rather than touching the figure yourself. It records the line it draws, which is what lets the marker controls restyle it afterwards. - Run colours through
to_mpl_color()if you read aColorTrait. TraitsUI stores a toolkit-nativeQColor/wx.Colour, and Matplotlib rejects both.
pip install -e '.[qt,dev]'
pytestThe suite never opens a window — it builds the trait objects and inspects the
Matplotlib figure directly — so it runs headless. On a machine with no display,
set QT_QPA_PLATFORM=offscreen.
ruff check . # lint
ruff format . # format| Symptom | Cause and fix |
|---|---|
ImportError naming a toolkit on startup |
No GUI toolkit installed. pip install -e '.[qt]'. |
| Black backgrounds, misdrawn widgets | TraitsUI older than 7.2. Upgrade it. |
| Mayavi tab shows an install hint | Expected without Mayavi. pip install -e '.[qt,3d]' to enable it. |
| Wrong toolkit picked when both installed | Set ETS_TOOLKIT=qt or ETS_TOOLKIT=wx. |
| Window does not focus on macOS (wx) | Launch with pythonw main.py, or use the Qt backend. |
qt.qpa.plugin errors on a headless Linux box |
Set QT_QPA_PLATFORM=offscreen. |
MIT — see LICENSE.