My CurrentCost desktop software is written in Python, and uses the matplotlib library for plotting graphs.
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 Plot
.
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)
The 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 Plot
instead.
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[0] 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[0] 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… 🙂
Tags: matplotlib, NavigationToolbar2Wx, python
Thank you very much for your great post. That was exactly was i was looking for!
ATM i am trying to change the style of the toolbar in wxpython, but i am not sure how to do it for the matplotlib Toolbar. Is there an easy way to build a Matplotlib Toolbar from scratch? Thanks in advance, Maxx
I don’t know – I’ve not tried. The source code for the toolbar is all open, so you could take a look and decide how much of it you think you will need.
That said, I prefer to subclass the existing one because it makes it more likely that I will gain from future improvements to it. Forking it or re-implementing my own copy limits future improvements.
I tried to remove buttons the same way you describe, but it seems it doesn’t work when using the Qt4 backends since DeleteToolByPos is not recognized (‘MyCustomToolbar’ object has no attribute ‘DeleteToolByPos’) :-((
You maybe now a workaround for PyQt4?
Thanks in advance, Jonathan
Thanks for the post. It worked perfectly.
I have a problem now, and I couldn’t find the right answer. I’ve got a FigCanvas with more than 10 plots sharing the X axis. Since they don’t fit in the screen, I want to put the canvas in a scroll panel so the user can scroll up and down the plots. The thing is that when I add the toolbar, it is added to the same scrolling panel where the FigCanvas is. So the toolbar “goes away” from the screen as soon as the user scrolls. My question is, did you find any way to put the canvas and the toolbar in two different wx.Panels?
Thanks in advance,
Juan Pablo