diff --git a/system/plots.py b/system/plots.py index aafa630..6d68960 100644 --- a/system/plots.py +++ b/system/plots.py @@ -309,10 +309,8 @@ class Plot (View): if self._frozen \ or not self.get_property('visible') \ or self.current_dim != dim_name: - print "Ignored a selection changed call in plot: %s" %self.get_title() return - print "Setting current selection in: %s " %self.get_title() self.set_current_selection(selection) def set_selection_listener(self, listener): @@ -443,7 +441,6 @@ class ScatterMarkerPlot(Plot): self.canvas.show() def rectangle_select_callback(self, x1, y1, x2, y2): - print "Rectangle select happened in: %s" %self.get_title() ydata = self.yaxis_data xdata = self.xaxis_data @@ -480,7 +477,6 @@ class ScatterMarkerPlot(Plot): self.ax.draw_artist(self._selection_line) self.canvas.blit() else: - print "A draw happened in: %s" %self.get_title() self.canvas.draw() @@ -514,7 +510,6 @@ class ScatterPlot(Plot): self.canvas.show() def rectangle_select_callback(self, x1, y1, x2, y2): - print "Rectangle select happened in: %s" %self.get_title() ydata = self.yaxis_data xdata = self.xaxis_data @@ -547,11 +542,9 @@ class ScatterPlot(Plot): self.coll.set_linewidth(lw) if self.use_blit: - print "A blit happened in : %s " %self.get_title() self.canvas.blit() self.ax.draw_artist(self.coll) else: - print "A draw happened in : %s " %self.get_title() self.canvas.draw() @@ -688,31 +681,64 @@ class PlotMode: return image def activate(self): + """Subclasses of PlotMode should do their initialization here. + + The activate method is called when a mode is activated, and is + used primarily to set up callback functions on events in the + canvas. + """ pass def deactivate(self): + """Subclasses of PlotMode should do their cleanup here. + + The deactivate method is primarily by subclasses of PlotMode to + remove any callbacks they might have on the matplotlib canvas. + """ pass + def _mpl_disconnect_all(self): + """Disconnects all matplotlib callbacks defined on the canvas. + + This is a hack because the RectangleSelector in matplotlib does + not store its callbacks, so we need a workaround to remove them. + """ + callbacks = self.plot.canvas.callbacks + + for callbackd in callbacks.values(): + for c in callbackd.keys(): + del callbackd[c] + class DefaultPlotMode (PlotMode): def __init__(self, plot): PlotMode.__init__(self, plot, 'default', 'Default mode', 'cursor.png') + def activate(self): + for k, v in self.canvas.callbacks.items(): + print k, v + class PanPlotMode (PlotMode): def __init__(self, plot): PlotMode.__init__(self, plot, 'pan', 'Pan axes with left mouse, zoom with right', 'move.png') + + # Holds handler IDs for callbacks. self._button_press = None self._button_release = None + self._motion_notify = None + + self._button_pressed = None def activate(self): self._button_press = self.canvas.mpl_connect( 'button_press_event', self._on_button_press) self._button_relese = self.canvas.mpl_connect( 'button_release_event', self._on_button_release) - self.mode = 'pan/zoom mode' + #self._drag = self.canvas.mpl_connect( + # 'mouse_drag_event', self._on_drag) def deactivate(self): if self._button_press: @@ -722,10 +748,132 @@ class PanPlotMode (PlotMode): self.canvas.mpl_disconnect(self._button_release) def _on_button_press(self, event): - pass + + if event.button == 1: + self._button_pressed = 1 + elif event.button == 3: + self._button_pressed = 3 + else: + self._button_pressed=None + return + + x, y = event.x, event.y + + # push the current view to define home if stack is empty + # if self._views.empty(): self.push_current() + + self._xypress=[] + for i, a in enumerate(self.canvas.figure.get_axes()): + if x is not None and y is not None and a.in_axes(x, y) \ + and a.get_navigate(): + xmin, xmax = a.get_xlim() + ymin, ymax = a.get_ylim() + lim = xmin, xmax, ymin, ymax + self._xypress.append((x, y, a, i, lim,a.transData.deepcopy())) + self.canvas.mpl_disconnect(self._motion_notify) + + cid = self.canvas.mpl_connect('motion_notify_event', + self._on_motion_notify) + self._motion_notify = cid + + def _on_motion_notify(self, event): + """The drag callback in pan/zoom mode""" + + def format_deltas(event, dx, dy): + """Returns the correct dx and dy based on the modifier keys""" + if event.key=='control': + if(abs(dx)>abs(dy)): + dy = dx + else: + dx = dy + elif event.key=='x': + dy = 0 + elif event.key=='y': + dx = 0 + elif event.key=='shift': + if 2*abs(dx) < abs(dy): + dx=0 + elif 2*abs(dy) < abs(dx): + dy=0 + elif(abs(dx)>abs(dy)): + dy=dy/abs(dy)*abs(dx) + else: + dx=dx/abs(dx)*abs(dy) + return (dx,dy) + + for cur_xypress in self._xypress: + lastx, lasty, a, ind, lim, trans = cur_xypress + xmin, xmax, ymin, ymax = lim + + #safer to use the recorded button at the press than current button: + #multiple button can get pressed during motion... + if self._button_pressed==1: + lastx, lasty = trans.inverse_xy_tup( (lastx, lasty) ) + x, y = trans.inverse_xy_tup( (event.x, event.y) ) + if a.get_xscale()=='log': + dx=1-lastx/x + else: + dx=x-lastx + if a.get_yscale()=='log': + dy=1-lasty/y + else: + dy=y-lasty + + dx,dy=format_deltas(event,dx,dy) + + if a.get_xscale()=='log': + xmin *= 1-dx + xmax *= 1-dx + else: + xmin -= dx + xmax -= dx + if a.get_yscale()=='log': + ymin *= 1-dy + ymax *= 1-dy + else: + ymin -= dy + ymax -= dy + + elif self._button_pressed==3: + try: + dx=(lastx-event.x)/float(a.bbox.width()) + dy=(lasty-event.y)/float(a.bbox.height()) + dx,dy=format_deltas(event,dx,dy) + if a.get_aspect() != 'auto': + dx = 0.5*(dx + dy) + dy = dx + alphax = pow(10.0,dx) + alphay = pow(10.0,dy) + lastx, lasty = trans.inverse_xy_tup( (lastx, lasty) ) + if a.get_xscale()=='log': + xmin = lastx*(xmin/lastx)**alphax + xmax = lastx*(xmax/lastx)**alphax + else: + xmin = lastx+alphax*(xmin-lastx) + xmax = lastx+alphax*(xmax-lastx) + if a.get_yscale()=='log': + ymin = lasty*(ymin/lasty)**alphay + ymax = lasty*(ymax/lasty)**alphay + else: + ymin = lasty+alphay*(ymin-lasty) + ymax = lasty+alphay*(ymax-lasty) + + except OverflowError: + warnings.warn('Overflow while panning') + return + + a.set_xlim(xmin, xmax) + a.set_ylim(ymin, ymax) + + self.canvas.draw() def _on_button_release(self, event): - pass + 'the release mouse button callback in pan/zoom mode' + self.canvas.mpl_disconnect(self._motion_notify) + if not self._xypress: return + self._xypress = None + self._button_pressed=None + self.canvas.draw() class ZoomPlotMode (PlotMode): @@ -747,18 +895,15 @@ class ZoomPlotMode (PlotMode): self._selectors[rs] = ax def deactivate(self): - for sel in self.selectors: - self.canvas.mpl_disconnect(sel.onmove) - self.canvas.mpl_disconnect(sel.press) - self.canvas.mpl_disconnect(sel.release) - self.canvas.mpl_disconnect(sel.update_background) + self._mpl_disconnect_all() self._selectors = {} def _on_select(self, start, end): - min_x = min(start.xdata, end.xdata) - min_y = min(start.ydata, end.ydata) - max_x = max(start.xdata, end.xdata) - max_y = max(start.ydata, end.ydata) + ax = start.inaxes + + ax.set_xlim((min(start.xdata, end.xdata), max(start.xdata, end.xdata))) + ax.set_ylim((min(start.ydata, end.ydata), max(start.ydata, end.ydata))) + self.canvas.draw() class SelectPlotMode (PlotMode): @@ -780,11 +925,7 @@ class SelectPlotMode (PlotMode): self._selectors[rs] = ax def deactivate(self): - for sel in self.selectors: - self.canvas.mpl_disconnect(sel.onmove) - self.canvas.mpl_disconnect(sel.press) - self.canvas.mpl_disconnect(sel.release) - self.canvas.mpl_disconnect(sel.update_background) + self._mpl_disconnect_all() self._selectors = {} def _on_select(self, start, end): @@ -812,6 +953,20 @@ class PlotToolbar(gtk.Toolbar): self.add_mode(PanPlotMode(self.plot)) self.add_mode(ZoomPlotMode(self.plot)) self.add_mode(SelectPlotMode(self.plot)) + + self.insert(gtk.SeparatorToolItem(), -1) + + # Set up freeze button + btn = gtk.ToggleToolButton() + + fname = os.path.join(fluents.ICONDIR, "freeze.png") + image = gtk.Image() + image.set_from_file(fname) + + btn.set_icon_widget(image) + btn.connect('toggled', self._on_freeze_toggle) + self.insert(btn, -1) + self.show_all() def add_mode(self, mode): @@ -850,9 +1005,7 @@ class PlotToolbar(gtk.Toolbar): """Returns the button that corresponds to a mode name.""" for b, m in self._mode_buttons.items(): if m.name == mode_name: - print "Found button for mode: %s" % mode_name return b - print "Couldn't find button for mode: %s" % mode_name return None def set_mode(self, mode_name): @@ -870,7 +1023,8 @@ class PlotToolbar(gtk.Toolbar): else: logger.log('warning', 'No such mode: %s' % mode_name) - if self.get_button(mode_name) and not self.get_button(mode_name).get_active(): + if self.get_button(mode_name) and \ + not self.get_button(mode_name).get_active(): self.get_button(mode_name).set_active(True) return self._current_mode @@ -879,298 +1033,9 @@ class PlotToolbar(gtk.Toolbar): if button.get_active(): self.set_mode(self._mode_buttons[button].name) -# def show(self): -# for b in self._mode_buttons.keys(): -# b.show() -# gtk.Toolbar.show(self) - -# class PlotToolbar(NavigationToolbar2,gtk.Toolbar): -# # list of toolitems to add to the toolbar, format is: -# # text, tooltip_text, image_file, callback(str) -# toolitems = ( -# ('Home', 'Reset original view', 'home.png', 'home'), -# #('Back', 'Back to previous view','back.png', 'back'), -# #('Forward', 'Forward to next view','forward.png', 'forward'), -# #('Subplots', 'Configure subplots','subplots.png', 'configure_subplots'), -# ('Save', 'Save the figure','filesave.png', 'save_figure'), -# ) + def _on_freeze_toggle(self, button): + self.plot.set_frozen(button.get_active()) -# def __init__(self, canvas, plot): -# self.win = None -# self.plot = plot -# gtk.Toolbar.__init__(self) -# NavigationToolbar2.__init__(self, canvas) -# self._idleId = 0 -# self._select_callback = None -# canvas.connect('enter-notify-event', self.on_enter_notify) - -# def _init_toolbar(self): -# self.set_style(gtk.TOOLBAR_ICONS) -# self.tooltips = gtk.Tooltips() -# self._modes = {} -# self._selector = None -# self.set_property('show-arrow', False) -# basedir = fluents.ICONDIR - -# # setup base buttons -# for text, tooltip_text, image_file, callback in self.toolitems: -# if text is None: -# self.insert(gtk.SeparatorToolItem(), -1 ) -# continue -# fname = os.path.join(basedir, image_file) -# image = gtk.Image() -# image.set_from_file(fname) -# tbutton = gtk.ToolButton(image, text) -# self.insert(tbutton, -1) -# tbutton.connect('clicked', getattr(self, callback)) -# tbutton.set_tooltip(self.tooltips, tooltip_text, 'Private') - -# self.insert(gtk.SeparatorToolItem(), -1) - -# # mode/state buttons -# rbutton = None -# for text,tooltip_text,image_file,callback in self.radiobuttons: -# if text is None: -# self.insert(gtk.SeparatorToolItem(), -1 ) -# continue -# fname = os.path.join(basedir, image_file) -# image = gtk.Image() -# image.set_from_file(fname) -# rbutton = gtk.RadioToolButton(rbutton) -# rbutton.set_icon_widget(image) -# rbutton.connect('toggled', getattr(self, callback)) -# rbutton.set_tooltip(self.tooltips, tooltip_text, 'Private') -# self._modes[text] = rbutton -# a self.insert(rbutton, -1) - -# self.insert(gtk.SeparatorToolItem(), -1) - -# toolitem = gtk.ToolItem() -# self.insert(toolitem, -1) -# self.message = gtk.Label() -# toolitem.add(self.message) - -# self.tb_freeze = gtk.ToolItem() -# self.chk = gtk.CheckButton () -# self.chk.set_label ('Freeze') -# self.tb_freeze.add (self.chk) -# self.tb_freeze.set_tooltip(self.tooltips, 'Freeze current selection') -# self.insert(self.tb_freeze,-1) - -# toolitem = gtk.SeparatorToolItem() -# self.insert(toolitem, -1) - -# self.show_all() - -# self.fileselect = FileChooserDialog(title='Save the figure', parent=self.win,) - - -# def on_enter_notify(self, widget, event): -# if self._active != active_mode: -# self.set_mode(active_mode) -# self.set_mode(active_mode) - -# def set_mode(self, mode): -# ## Do nothing if this mode is already active -# if self.mode == mode: -# print "set_mode: already in mode '%s': returning" % mode -# return mode - -# ## Remove current Selector if set. -# if self._selector != None: -# self.canvas.mpl_disconnect(self._selector.onmove) -# self.canvas.mpl_disconnect(self._selector.press) -# self.canvas.mpl_disconnect(self._selector.release) -# self.canvas.mpl_disconnect(self._selector.update_background) -# print "set_mode: Removing selector in: %s" %self.plot.get_title() -# self._selector = None - -# ## Remove current mouse button bindings -# if self._idPress != None: -# self._idPress = self.canvas.mpl_disconnect(self._idPress) -# self.mode = '' - -# if self._idRelease != None: -# self._idRelease = self.canvas.mpl_disconnect(self._idRelease) -# self.mode = '' - -# ## Set state, and do state initialization -# self.mode = mode - - -# def set_mode(self, active): -# print "Set mode called in toolbar from: %s" %self.plot.get_title() -# # if state is unkown or not set, set to default -# if active == None or active not in self._modes.keys(): -# active = 'DEFAULT' - -# # remove current Selector: -# if self._selector != None: -# # problem is ... i have mutliple selectors still connected -# # trying to remove old selectors connections -# # -# # blah -# # Her Einar ... -# # -# self.canvas.mpl_disconnect(self._selector.onmove) -# self.canvas.mpl_disconnect(self._selector.press) -# self.canvas.mpl_disconnect(self._selector.release) -# self.canvas.mpl_disconnect(self._selector.update_background) -# print "Removing selector in: %s" %self.plot.get_title() -# self._selector = None - -# # remove current button bindings -# if self._idPress != None: -# self._idPress = self.canvas.mpl_disconnect(self._idPress) -# self.mode = '' - -# if self._idRelease != None: -# self._idRelease = self.canvas.mpl_disconnect(self._idRelease) -# self.mode = '' - -# self._modes[active] - -# for state, button in self._modes.items(): -# if state != active: -# continue - -# if state == 'SELECT': -# for ax in self.canvas.figure.get_axes(): -# props = dict(facecolor='blue', edgecolor = 'black', -# alpha=0.3, fill=True) -# print "creating a selector" -# self._selector = RectangleSelector(ax, self.on_select, -# drawtype='box', useblit=True, -# rectprops=props) -# self.mode = 'Select rectangle mode' - -# elif state == 'PAN': -# self._idPress = self.canvas.mpl_connect( -# 'button_press_event', self.press_pan) -# self._idRelease = self.canvas.mpl_connect( -# 'button_release_event', self.release_pan) -# self.mode = 'pan/zoom mode' - -# elif state == 'ZOOM': -# self._idPress = self.canvas.mpl_connect('button_press_event', self.press_zoom) -# self._idRelease = self.canvas.mpl_connect('button_release_event', self.release_zoom) -# self.mode = 'Zoom to rect mode' - -# elif state == 'DEFAULT': -# pass -# else: -# pass - -# if not button.get_active(): -# button.set_active(True) - -# for a in self.canvas.figure.get_axes(): -# a.set_navigate_mode(self._active) - -# self.set_message(self.mode) - -# self._active = active -# # update global active_mode (for all toolbar) -# globals()['active_mode'] = active - -# def get_mode(self): -# if self._active == None: -# return 'DEFAULT' -# return self._active - -# def default(self, button): -# """Activates default mode""" -# if not button.get_active(): -# return -# self.set_mode('DEFAULT') - -# def select(self, button): -# """Activate select mode""" -# if not button.get_active(): -# return -# self.set_mode('SELECT') - -# def on_select(self, eclick, erelease, selector): -# 'eclick and erelease are matplotlib events at press and release' -# if self._select_callback: -# print "Onselect called:" -# print " %s" % selector -# self._select_callback(eclick.xdata, eclick.ydata, erelease.xdata, erelease.ydata) - -# def pan(self, button): -# """Activate the pan/zoom tool. pan with left button, zoom with right""" -# if not button.get_active(): -# return -# self.set_mode('PAN') - -# def zoom(self, button): -# """Activate zoom to rect mode""" -# if not button.get_active(): -# return -# self.set_mode('ZOOM') - -# def mouse_move(self, event): -# """Extend NavigationToolbar2.mouse_move to provide selection support.""" -# # Only update the rubberband for selection mode when mouse is -# # within the plotting area. - -# #if event.inaxes and self._active=='SELECT': -# # if self._lastCursor != cursors.SELECT_REGION: -# # self.set_cursor(cursors.SELECT_REGION) -# # self._lastCursor = cursors.SELECT_REGION -# # if self._xypress is not None: -# # x, y = event.x, event.y -# # lastx, lasty, a, ind, lim, trans= self._xypress -# # self.draw_rubberband(event, x, y, lastx, lasty) - -# NavigationToolbar2.mouse_move(self, event) - -# def set_select_callback(self, listener): -# """Allow plots to register a callback for selection events. - -# The callback will be called as listener(x1, y1, x2, y2). All -# coordinates are in the plot coordinate system, not pixels or -# widget coordinates. -# """ -# self._select_callback = listener - -# def set_cursor(self, cursor): -# self.canvas.window.set_cursor(cursord[cursor]) - -# def dynamic_update(self): -# # legacy method; new method is canvas.draw_idle -# self.canvas.draw_idle() - - -# def save_figure(self, button): -# fname = self.fileselect.get_filename_from_user() -# if fname: -# self.canvas.print_figure(fname) - -# def configure_subplots(self, button): -# toolfig = Figure(figsize=(6,3)) -# canvas = self._get_canvas(toolfig) -# toolfig.subplots_adjust(top=0.9) -# tool = SubplotTool(self.canvas.figure, toolfig) - -# w = int (toolfig.bbox.width()) -# h = int (toolfig.bbox.height()) - - -# window = gtk.Window() -# window.set_title("Subplot Configuration Tool") -# window.set_default_size(w, h) -# vbox = gtk.VBox() -# window.add(vbox) -# vbox.show() - -# canvas.show() -# vbox.pack_start(canvas, True, True) -# window.show() - -# def _get_canvas(self, fig): -# return FigureCanvas(fig) - # Create a view-changed signal that should be emitted every time # the active view changes.