I am a big fan of matplotlib – you can create some very cool graphs with it. It’s not without it’s issues… for example, I’ve still yet to work out how to do realtime graphing with it that isn’t massively inefficient and resource intensive. But that’s for another post 🙂
One of the nice things that you get with it is a toolbar that lets the user switch between different modes that the mouse supports – zooming, panning, and so on.
Adding the toolbar is straightforward – I create an object of the class
NavigationToolbar2Wx and add it to a sizer which in turn I add to the graph
self.toolbar = Toolbar(self.canvas) self.toolbar.Realize() sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.canvas, 1, wx.EXPAND) sizer.Add(self.toolbar, 0 , wx.LEFT | wx.EXPAND) self.SetSizer(sizer)
NavigationToolbar2 toolbar provides a number of features but it doesn’t exactly meet my needs so last night I had a go at customising it.
I wanted to:
- remove the configure subplots button – as I don’t use subplots in my graphs
- add a couple of new buttons – for different navigation abilities
I couldn’t find this documented clearly anywhere, so thought it was worth sharing the code that I came up with.
Creating a custom toolbar
Step one for customising the toolbar is to create a subclass of NavigationToolbar2Wx, and add this to your
class MyCustomToolbar(NavigationToolbar2Wx): def __init__(self, plotCanvas): NavigationToolbar2Wx.__init__(self, plotCanvas)
You’ve now got something you can start customising.
Removing buttons from the toolbar
The most straightforward approach I’ve settled on is to run the
init method from the
NavigationToolbar2Wx superclass, and then go back and remove the bits I don’t want.
class MyCustomToolbar(NavigationToolbar2Wx): def __init__(self, plotCanvas): # create the default toolbar NavigationToolbar2Wx.__init__(self, plotCanvas) # remove the unwanted button POSITION_OF_CONFIGURE_SUBPLOTS_BTN = 6 self.DeleteToolByPos(POSITION_OF_CONFIGURE_SUBPLOTS_BTN)
Adding new buttons to the toolbar
You can pan matplotlib graphs by dragging the mouse across but a couple of users of my CurrentCost app said that they found this difficult and clunky, and would rather have another way to scroll the graph from the toolbar.
So I added a couple of extra buttons: one which scrolls the graph a screen to the left, and one which scrolls the graph a screen to the right.
I’m using stock images from matplotlib for the button icons, but you could use your own custom images – either way you need to use
_load_bitmap to load the image to use for the icon.
In the action methods that are called when the custom buttons are clicked, I use
axes.get_xlim() to get the min and max values for the x axis, then get the difference between them to work out the horizontal length of the graph. I can then move the x axis min and max by that amount to scroll the graph by one screen.
class MyCustomToolbar(NavigationToolbar2Wx): ON_CUSTOM_LEFT = wx.NewId() ON_CUSTOM_RIGHT = wx.NewId() def __init__(self, plotCanvas): # create the default toolbar NavigationToolbar2Wx.__init__(self, plotCanvas) # add new toolbar buttons self.AddSimpleTool(self.ON_CUSTOM_LEFT, _load_bitmap('stock_left.xpm'), 'Pan to the left', 'Pan graph to the left') wx.EVT_TOOL(self, self.ON_CUSTOM_LEFT, self._on_custom_pan_left) self.AddSimpleTool(self.ON_CUSTOM_RIGHT, _load_bitmap('stock_right.xpm'), 'Pan to the right', 'Pan graph to the right') wx.EVT_TOOL(self, self.ON_CUSTOM_RIGHT, self._on_custom_pan_right) # pan the graph to the left def _on_custom_pan_left(self, evt): ONE_SCREEN = 1 axes = self.canvas.figure.axes x1,x2 = axes.get_xlim() ONE_SCREEN = x2 - x1 axes.set_xlim(x1 - ONE_SCREEN, x2 - ONE_SCREEN) self.canvas.draw() # pan the graph to the right def _on_custom_pan_right(self, evt): ONE_SCREEN = 1 axes = self.canvas.figure.axes x1,x2 = axes.get_xlim() ONE_SCREEN = x2 - x1 axes.set_xlim(x1 + ONE_SCREEN, x2 + ONE_SCREEN) self.canvas.draw()
Now back to thinking about how to do realtime graphing with matplotlib… 🙂