我正在编写一个wx / matplotlib应用程序,我在向matplotlib NavigationToolbar添加新工具时遇到了相当大的困难。
基本上我想添加选择工具(选框,套索等)来切换受控子图鼠标模式。到目前为止,我一直无法找到任何可以让我轻松完成此任务的功能。
然而,我确实发现了这个看似有用的功能:http://matplotlib.sourceforge.net/api/axes_api.html?highlight=set_navigate_mode#matplotlib.axes.Axes.set_navigate_mode
不幸的是,正如警告所暗示的那样,它并没有真正帮助我。
有人知道如何做到这一点吗?下面是一个精简的例子,展示了我已经走了多远。书签图标用于代替我的套索图标,为了简洁起见,我删除了套索功能。
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 as NavigationToolbar
class ScatterPanel(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)
def lasso_callback(self, verts):
pass
def on_press(self, evt):
if evt.button == 1:
if self.canvas.widgetlock.locked():
return
if evt.inaxes is None:
return
if self.navtoolbar.mode == 'lasso':
self.lasso = Lasso(evt.inaxes, (evt.xdata, evt.ydata), self.lasso_callback)
self.canvas.widgetlock(self.lasso)
def on_release(self, evt):
# 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 = None
else:
self.show_popup_menu((evt.x, self.canvas.GetSize()[1]-evt.y), None)
def redraw(self):
self.subplot.clear()
self.subplot.scatter([1,2,3],[3,1,2])
def toggle_lasso_tool(self, evt):
if evt.Checked():
self.navtoolbar.mode = 'lasso'
#self.subplot.set_navigate_mode('lasso')
# Cheat: untoggle the zoom and pan tools
self.navtoolbar.ToggleTool(self.navtoolbar._NTB2_ZOOM, False)
self.navtoolbar.ToggleTool(self.navtoolbar._NTB2_PAN, False)
else:
self.navtoolbar.mode = ''
self.lasso = None
#self.subplot.set_navigate_mode('')
def get_toolbar(self):
if not self.navtoolbar:
self.navtoolbar = NavigationToolbar(self.canvas)
self.navtoolbar.DeleteToolByPos(6)
ID_LASSO_TOOL = wx.NewId()
lasso = self.navtoolbar.InsertSimpleTool(5, ID_LASSO_TOOL,
wx.ArtProvider.GetBitmap(wx.ART_ADD_BOOKMARK),
isToggle=True)
self.navtoolbar.Realize()
self.navtoolbar.Bind(wx.EVT_TOOL, self.toggle_lasso_tool, id=ID_LASSO_TOOL)
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()
谢谢, 亚当
答案 0 :(得分:7)
以下是MyNavToolbar
的改进版本。需要注意的主要是添加add_user_tool
方法。我在__init__
内调用它,但你可能想从MyNavToolbar
类之外调用它。通过这种方式,您可以使用不同的绘图类型工具。
class MyNavToolbar(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')
def add_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])
def get_mode(self):
"""Use this rather than navtoolbar.mode
"""
for mode, tool in self.user_tools.items():
if tool.IsToggled():
return mode
return self.mode
def untoggle_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)
def on_toggle_user_tool(self, evt):
"""user tool click handler.
"""
if evt.Checked():
self.untoggle_mpl_tools()
#untoggle other user tools
for tool in self.user_tools.values():
if tool.Id != evt.Id:
self.ToggleTool(tool.Id, False)
def on_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()
def reset_history(self):
"""More hacky junk to clear/reset the toolbar history.
"""
self._views.clear()
self._positions.clear()
self.push_current()
答案 1 :(得分:2)
嗯,这是丑陋但功能性的。我会让docstrings进行谈话,这浪费了我足够的时间。
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
class MyNavToolbar(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)
def get_mode(self):
"""Use this rather than navtoolbar.mode
"""
if self.lasso_tool.IsToggled():
return 'lasso'
else:
return self.mode
def untoggle_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)
def on_toggle_lasso_tool(self, evt):
"""Lasso tool handler.
"""
if evt.Checked():
self.untoggle_mpl_tools()
def on_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()
class ScatterPanel(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)
def lasso_callback(self, verts):
pass
def on_press(self, evt):
"""canvas mousedown handler
"""
if evt.button == 1:
if self.canvas.widgetlock.locked():
return
if evt.inaxes is None:
return
if 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)
def on_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 = None
else:
self.show_popup_menu((evt.x, self.canvas.GetSize()[1]-evt.y), None)
def redraw(self):
self.subplot.clear()
self.subplot.scatter([1,2,3],[3,1,2])
def get_toolbar(self):
if not 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()