Add New Navigate Modes In Matplotlib
I'm writing a wx/matplotlib application, and I'm having considerable difficulty adding a new tool to the matplotlib NavigationToolbar. Basically I want to add tools for selection (
Solution 1:
Here's an improved version of MyNavToolbar
. The main thing to note is the addition of the add_user_tool
method. I call it from within __init__
, but you'd probably want to call it from outside the MyNavToolbar
class. This way you could have different tools for plot types.
classMyNavToolbar(NavigationToolbar2WxAgg):
"""wx/mpl NavToolbar hack with an additional tools user interaction.
This class is necessary because simply adding a new togglable tool to the
toolbar won't (1) radio-toggle between the new tool and the pan/zoom tools.
(2) disable the pan/zoom tool modes in the associated subplot(s).
"""def__init__(self, canvas):
super(NavigationToolbar2WxAgg, self).__init__(canvas)
self.pan_tool = self.FindById(self._NTB2_PAN)
self.zoom_tool = self.FindById(self._NTB2_ZOOM)
self.Bind(wx.EVT_TOOL, self.on_toggle_pan_zoom, self.zoom_tool)
self.Bind(wx.EVT_TOOL, self.on_toggle_pan_zoom, self.pan_tool)
self.user_tools = {} # user_tools['tool_mode'] : wx.ToolBarToolBase
self.InsertSeparator(5)
self.add_user_tool('lasso', 6, icons.lasso_tool.ConvertToBitmap(), True, 'Lasso')
self.add_user_tool('gate', 7, icons.gate_tool.ConvertToBitmap(), True, 'Gate')
defadd_user_tool(self, mode, pos, bmp, istoggle=True, shortHelp=''):
"""Adds a new user-defined tool to the toolbar.
mode -- the value that MyNavToolbar.get_mode() will return if this tool
is toggled on
pos -- the position in the toolbar to add the icon
bmp -- a wx.Bitmap of the icon to use in the toolbar
isToggle -- whether or not the new tool toggles on/off with the other
togglable tools
shortHelp -- the tooltip shown to the user for the new tool
"""
tool_id = wx.NewId()
self.user_tools[mode] = self.InsertSimpleTool(pos, tool_id, bmp,
isToggle=istoggle, shortHelpString=shortHelp)
self.Bind(wx.EVT_TOOL, self.on_toggle_user_tool, self.user_tools[mode])
defget_mode(self):
"""Use this rather than navtoolbar.mode
"""for mode, tool in self.user_tools.items():
if tool.IsToggled():
return mode
return self.mode
defuntoggle_mpl_tools(self):
"""Hack city: Since I can't figure out how to change the way the
associated subplot(s) handles mouse events: I generate events to turn
off whichever tool mode is enabled (if any).
This function needs to be called whenever any user-defined tool
(eg: lasso) is clicked.
"""if self.pan_tool.IsToggled():
wx.PostEvent(
self.GetEventHandler(),
wx.CommandEvent(wx.EVT_TOOL.typeId, self._NTB2_PAN)
)
self.ToggleTool(self._NTB2_PAN, False)
elif self.zoom_tool.IsToggled():
wx.PostEvent(
self.GetEventHandler(),
wx.CommandEvent(wx.EVT_TOOL.typeId, self._NTB2_ZOOM)
)
self.ToggleTool(self._NTB2_ZOOM, False)
defon_toggle_user_tool(self, evt):
"""user tool click handler.
"""if evt.Checked():
self.untoggle_mpl_tools()
#untoggle other user toolsfor tool in self.user_tools.values():
if tool.Id != evt.Id:
self.ToggleTool(tool.Id, False)
defon_toggle_pan_zoom(self, evt):
"""Called when pan or zoom is toggled.
We need to manually untoggle user-defined tools.
"""if evt.Checked():
for tool in self.user_tools.values():
self.ToggleTool(tool.Id, False)
# Make sure the regular pan/zoom handlers get the event
evt.Skip()
defreset_history(self):
"""More hacky junk to clear/reset the toolbar history.
"""
self._views.clear()
self._positions.clear()
self.push_current()
Solution 2:
Well here it is, ugly but functional. I'll let the docstrings do the talking, this has wasted enough of my time.
import wx
from matplotlib.patches import Rectangle
from matplotlib.widgets import Lasso
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
from matplotlib.backends.backend_wxagg import NavigationToolbar2WxAgg
classMyNavToolbar(NavigationToolbar2WxAgg):
"""wx/mpl NavToolbar hack with an additional tools user interaction.
This class is necessary because simply adding a new togglable tool to the
toolbar won't (1) radio-toggle between the new tool and the pan/zoom tools.
(2) disable the pan/zoom tool modes in the associated subplot(s).
"""
ID_LASSO_TOOL = wx.NewId()
def__init__(self, canvas):
super(NavigationToolbar2WxAgg, self).__init__(canvas)
self.pan_tool = self.FindById(self._NTB2_PAN)
self.zoom_tool = self.FindById(self._NTB2_ZOOM)
self.lasso_tool = self.InsertSimpleTool(5, self.ID_LASSO_TOOL,
wx.ArtProvider.GetBitmap(wx.ART_ADD_BOOKMARK),
isToggle=True)
self.Bind(wx.EVT_TOOL, self.on_toggle_lasso_tool, self.lasso_tool)
self.Bind(wx.EVT_TOOL, self.on_toggle_pan_zoom, self.zoom_tool)
self.Bind(wx.EVT_TOOL, self.on_toggle_pan_zoom, self.pan_tool)
defget_mode(self):
"""Use this rather than navtoolbar.mode
"""if self.lasso_tool.IsToggled():
return'lasso'else:
return self.mode
defuntoggle_mpl_tools(self):
"""Hack city: Since I can't figure out how to change the way the
associated subplot(s) handles mouse events: I generate events to turn
off whichever tool mode is enabled (if any).
This function needs to be called whenever any user-defined tool
(eg: lasso) is clicked.
"""if self.pan_tool.IsToggled():
wx.PostEvent(
self.GetEventHandler(),
wx.CommandEvent(wx.EVT_TOOL.typeId, self._NTB2_PAN)
)
self.ToggleTool(self._NTB2_PAN, False)
elif self.zoom_tool.IsToggled():
wx.PostEvent(
self.GetEventHandler(),
wx.CommandEvent(wx.EVT_TOOL.typeId, self._NTB2_ZOOM)
)
self.ToggleTool(self._NTB2_ZOOM, False)
defon_toggle_lasso_tool(self, evt):
"""Lasso tool handler.
"""if evt.Checked():
self.untoggle_mpl_tools()
defon_toggle_pan_zoom(self, evt):
"""Called when pan or zoom is toggled.
We need to manually untoggle user-defined tools.
"""if evt.Checked():
self.ToggleTool(self.ID_LASSO_TOOL, False)
# Make sure the regular pan/zoom handlers get the event
evt.Skip()
classScatterPanel(FigureCanvasWxAgg):
"""Contains the guts for drawing scatter plots.
"""def__init__(self, parent, **kwargs):
self.figure = Figure()
FigureCanvasWxAgg.__init__(self, parent, -1, self.figure, **kwargs)
self.canvas = self.figure.canvas
self.SetMinSize((100,100))
self.figure.set_facecolor((1,1,1))
self.figure.set_edgecolor((1,1,1))
self.canvas.SetBackgroundColour('white')
self.subplot = self.figure.add_subplot(111)
self.navtoolbar = None
self.lasso = None
self.redraw()
self.canvas.mpl_connect('button_press_event', self.on_press)
self.canvas.mpl_connect('button_release_event', self.on_release)
deflasso_callback(self, verts):
passdefon_press(self, evt):
"""canvas mousedown handler
"""if evt.button == 1:
if self.canvas.widgetlock.locked():
returnif evt.inaxes isNone:
returnif self.navtoolbar and self.navtoolbar.get_mode() == 'lasso':
self.lasso = Lasso(evt.inaxes, (evt.xdata, evt.ydata), self.lasso_callback)
self.canvas.widgetlock(self.lasso)
defon_release(self, evt):
"""canvas mouseup handler
"""# Note: lasso_callback is not called on click without drag so we release# the lock here to handle this case as well.if evt.button == 1:
if self.lasso:
self.canvas.draw_idle()
self.canvas.widgetlock.release(self.lasso)
self.lasso = Noneelse:
self.show_popup_menu((evt.x, self.canvas.GetSize()[1]-evt.y), None)
defredraw(self):
self.subplot.clear()
self.subplot.scatter([1,2,3],[3,1,2])
defget_toolbar(self):
ifnot self.navtoolbar:
self.navtoolbar = MyNavToolbar(self.canvas)
self.navtoolbar.Realize()
return self.navtoolbar
if __name__ == "__main__":
app = wx.PySimpleApp()
f = wx.Frame(None, size=(600,600))
p = ScatterPanel(f)
f.SetToolBar(p.get_toolbar())
f.Show()
app.MainLoop()
Post a Comment for "Add New Navigate Modes In Matplotlib"