Embedding a Matplotlib Figure in a Traits App

Traits, part of theEnthought Tools Suit , provides a great framework for creating GUI Apps without a lot of the normal boilerplate required to connect the UI the rest of the application logic. A brief introduction to Traits can be found here. Although ETS comes with it's own traits-aware plotting framework (Chaco), if you already know matplotlib it is just as easy to embed this instead. The advantages of Chaco (IMHO) are its interactive "tools", an (in development) OpenGL rendering backend and an easy-to-understand codebase. However, matplotlib has more and better documentation and better defaults; it just works. The key to getting TraitsUI and matplotlib to play nice is to use the mpl object-oriented API, rather than pylab / pyplot. This recipe requires the following packages:

For this example, we will display a function (y, a sine wave) of one variable (x, a numpy ndarray) and one parameter (scale, a float value with bounds). We want to be able to vary the parameter from the UI and see the resulting changes to y in a plot window. Here's what the final result looks like: The TraitsUI "CustomEditor" can be used to display any wxPython window as the editor for the object. You simply pass the CustomEditor a callable which, when called, returns the wxPython window you want to display. In this case, our MakePlot() function returns a wxPanel containing the mpl FigureCanvas and Navigation toolbar. This example exploits a few of Traits' features. We use "dynamic initialisation" to create the Axes and Line2D objects on demand (using the _xxx_default methods). We use Traits "notification", to call update_line(...) whenever the x- or y-data is changed. Further, the y-data is declared as a Property trait which depends on both the 'scale' parameter and the x-data. 'y' is then recalculated on demand, whenever either 'scale' or 'x' change. The 'cached_property' decorator prevents recalculation of y if it's dependancies are not modified.

Finally, there's a bit of wx-magic in the redraw() method to limit the redraw rate by delaying the actual drawing by 50ms. This uses the wx.CallLater class. This prevents excessive redrawing as the slider is dragged, keeping the UI from lagging.Here's the full listing:

   1 """
   2 A simple demonstration of embedding a matplotlib plot window in
   3 a traits-application. The CustomEditor allow any wxPython window
   4 to be used as an editor. The demo also illustrates Property traits,
   5 which provide nice dependency-handling and dynamic initialisation, using
   6 the _xxx_default(...) method.
   7 """
   8 from enthought.traits.api import HasTraits, Instance, Range,\
                                Array, on_trait_change, Property,\
                                cached_property, Bool
   9 from enthought.traits.ui.api import View, Item
  10 from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
  11 from matplotlib.backends.backend_wx import NavigationToolbar2Wx
  12 from matplotlib.figure import Figure
  13 from matplotlib.axes import Axes
  14 from matplotlib.lines import Line2D
  15 from enthought.traits.ui.api import CustomEditor
  16 import wx
  17 import numpy
  18 def MakePlot(parent, editor):
  19     """
  20     Builds the Canvas window for displaying the mpl-figure
  21     """
  22     fig = editor.object.figure
  23     panel = wx.Panel(parent, -1)
  24     canvas = FigureCanvasWxAgg(panel, -1, fig)
  25     toolbar = NavigationToolbar2Wx(canvas)
  26     toolbar.Realize()
  27     sizer = wx.BoxSizer(wx.VERTICAL)
  28     sizer.Add(canvas,1,wx.EXPAND|wx.ALL,1)
  29     sizer.Add(toolbar,0,wx.EXPAND|wx.ALL,1)
  30     panel.SetSizer(sizer)
  31     return panel
  32 class PlotModel(HasTraits):
  33     """A Model for displaying a matplotlib figure"""
  34     #we need instances of a Figure, a Axes and a Line2D
  35     figure = Instance(Figure, ())
  36     axes = Instance(Axes)
  37     line = Instance(Line2D)
  38     _draw_pending = Bool(False) #a flag to throttle the redraw rate
  39     #a variable paremeter
  40     scale = Range(0.1,10.0)
  41     #an independent variable
  42     x = Array(value=numpy.linspace(-5,5,512))
  43     #a dependent variable
  44     y = Property(Array, depends_on=['scale','x'])
  45     traits_view = View(
  46                     Item('figure',
  47                          editor=CustomEditor(MakePlot),
  48                          resizable=True),
  49                     Item('scale'),
  50                     resizable=True
  51                     )
  52     def _axes_default(self):
  53         return self.figure.add_subplot(111)
  54     def _line_default(self):
  55         return self.axes.plot(self.x, self.y)[0]
  56     @cached_property
  57     def _get_y(self):
  58         return numpy.sin(self.scale * self.x)
  59     @on_trait_change("x, y")
  60     def update_line(self, obj, name, val):
  61         attr = {'x': "set_xdata", 'y': "set_ydata"}[name]
  62         getattr(self.line, attr)(val)
  63         self.redraw()
  64     def redraw(self):
  65         if self._draw_pending:
  66             return
  67         canvas = self.figure.canvas
  68         if canvas is None:
  69             return
  70         def _draw():
  71             canvas.draw()
  72             self._draw_pending = False
  73         wx.CallLater(50, _draw).Start()
  74         self._draw_pending = True
  75 if __name__=="__main__":
  76     model = PlotModel(scale=2.0)
  77     model.configure_traits()

Cookbook/EmbeddingInTraitsGUI (last edited 2009-01-29 08:46:38 by GaelVaroquaux)