diff --git a/system/plots.py b/system/plots.py index 61b8f97..11e0de2 100644 --- a/system/plots.py +++ b/system/plots.py @@ -1,11 +1,15 @@ import os,sys +from itertools import izip import pygtk import gobject import gtk +import fluents +from system import logger import matplotlib -import scipy from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas -from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTK as NavigationToolbar2 +from matplotlib.backend_bases import NavigationToolbar2,cursors +from matplotlib.backends.backend_gtk import FileChooserDialog,cursord +from matplotlib.widgets import SubplotTool,RectangleSelector from matplotlib.axes import Subplot from matplotlib.figure import Figure from matplotlib import cm,cbook @@ -13,9 +17,10 @@ from pylab import Polygon from matplotlib.collections import LineCollection from matplotlib.mlab import prctile import networkx -from system import logger -from itertools import izip - +import scipy + +# global active mode. Used by toolbars to communicate correct mode +active_mode = 'DEFAULT' class ObjectTable: """A 2D table of elements.""" @@ -151,7 +156,7 @@ class ViewFrame (gtk.Frame): selection, info, timestamp): treestore, path = selection.tree_get_row_drag_data() iter = treestore.get_iter(path) - obj = treestore.get_value(iter,2) + obj = treestore.get_value(iter, 2) if isinstance(obj, Plot): self.set_view(obj) @@ -243,16 +248,15 @@ class MainView (gtk.Notebook): class Plot (gtk.Frame): def __init__(self, title): gtk.Frame.__init__(self) - self.sel_obj = None self.title = title + self.sel_obj = None self.selection_listener = None + self.fig = Figure() + self.canvas = FigureCanvas(self.fig) self.set_shadow_type(gtk.SHADOW_NONE) self._background = None - self.canvas = None - self.fig = Figure(figsize=(5,4), dpi=72) - self.canvas = FigureCanvas(self.fig) - self.fig.set_facecolor('white') self._sel_sensitive = True + self.canvas.add_events(gtk.gdk.ENTER_NOTIFY_MASK) def set_selection_sensitive(self,event): if event: @@ -263,6 +267,7 @@ class Plot (gtk.Frame): logger.log('debug','Selections active') self._sel_sensitive = True + def get_title(self): return self.title @@ -282,7 +287,7 @@ class Plot (gtk.Frame): def get_toolbar(self): return None - + class EmptyView (Plot): def __init__(self): @@ -291,124 +296,10 @@ class EmptyView (Plot): ebox = gtk.EventBox() ebox.add(label) self.add(ebox) - label.show() ebox.show() self.show() - -class NavToolbar(NavigationToolbar2): - toolitems = (('Select', 'Select within rectangle', 'zoom_to_rect.png', - 'select'),) + NavigationToolbar2.toolitems - - def __init__(self, *args): - NavigationToolbar2.__init__(self, *args) - self._select_callback = None - self.message.hide() - - def select(self, *args): - """Selection mode selected handler.""" - if self._active == 'SELECT': - self._active = None - else: - self._active = 'SELECT' - - if self._idPress is not None: - self._idPress = self.canvas.mpl_disconnect(self._idPress) - self.mode = '' - - if self._idRelease is not None: - self._idRelease = self.canvas.mpl_disconnect(self._idRelease) - self.mode = '' - - if self._active: - self._idPress = self.canvas.mpl_connect('button_press_event', self.press_select) - self._idRelease = self.canvas.mpl_connect('button_release_event', self.release_select) - self.mode = 'Select rectangle mode' - self.set_message(self.mode) - - def set_message(self, s): - """Set status in toolbar to string s. - - Overrided to make sure message can be updated even when - drawing rubberband. - """ - self.message.set_label(s) - - def press_select(self, event): - """Mouse button pressed handler for selection mode.""" - x, y = event.x, event.y - - for i, a in enumerate(self.canvas.figure.get_axes()): - if event.inaxes==a and event.inaxes.get_navigate(): - xmin, xmax = a.get_xlim() - ymin, ymax = a.get_ylim() - lim = xmin, xmax, ymin, ymax - self._xypress = x, y, a, i, lim, a.transData.deepcopy() - break - - self.press(event) - - def release_select(self, event): - """Mouse button released handler for selection mode.""" - # only release if button was pressed inside first? - if self._xypress: - x, y = event.x, event.y - lastx, lasty, a, ind, lim, trans = self._xypress - lastx, lasty = a.transData.inverse_xy_tup( (lastx, lasty) ) - x, y = a.transData.inverse_xy_tup( (x, y) ) - - if self._select_callback: - self._select_callback(lastx, lasty, x, y) - - self._xypress = None - self.push_current() - self.release(event) - - def mouse_move(self, event): - """Extend NavigationToolbar2.mouse_move to provide selection support.""" - from matplotlib.backend_bases import cursors - - # 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 - - -class PlotToolbar(NavToolbar): - """Extensions to existing toolbar - """ - def __init__(self, *args): - NavToolbar.__init__(self, *args) - 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.message.hide() - - class LineViewPlot(Plot): """Line view of current selection, no interaction Only works on 2d-arrays @@ -417,7 +308,7 @@ class LineViewPlot(Plot): -- minor_axis : needs definition only for higher order arrays ps: slow (cant get linecollection and blit to work) """ - def __init__(self, dataset,major_axis=1,minor_axis=None, name="Line view"): + def __init__(self, dataset, major_axis=1, minor_axis=None, name="Line view"): self.use_blit = False self._last_index = [] self._data = dataset.asarray() @@ -433,7 +324,7 @@ class LineViewPlot(Plot): x_axis = scipy.arange(self._data.shape[minor_axis]) self.line_segs=[] for xi in range(self._data.shape[major_axis]): - yi = self._data.take([xi],major_axis) + yi = self._data.take([xi], major_axis) self.line_segs.append([(xx,yy) for xx,yy in izip(x_axis,yi)]) #background @@ -443,41 +334,40 @@ class LineViewPlot(Plot): verts_2 = [] # 75,25 med = [] for i in xax: - pp = prctile(self._data[i,:],[0.,5.,25,50.,75.,95.,100]) + pp = prctile(self._data[i,:], [0.,5.,25,50.,75.,95.,100]) verts_0.append((i,pp[0])) verts_1.append((i,pp[1])) verts_2.append((i,pp[2])) for i in xax[::-1]: - pp = prctile(self._data[i,:],[0.,5.,25,50.,75.,95.,100]) - verts_0.append((i,pp[-1])) - verts_1.append((i,pp[-2])) - verts_2.append((i,pp[-3])) + pp = prctile(self._data[i,:], [0.,5.,25,50.,75.,95.,100]) + verts_0.append((i, pp[-1])) + verts_1.append((i, pp[-2])) + verts_2.append((i, pp[-3])) med.append(pp[3]) - bck0 = Polygon(verts_0,alpha=.15,lw=0) - bck1 = Polygon(verts_1,alpha=.15,lw=0) - bck2 = Polygon(verts_2,alpha=.15,lw=0) + bck0 = Polygon(verts_0, alpha=.15, lw=0) + bck1 = Polygon(verts_1, alpha=.15, lw=0) + bck2 = Polygon(verts_2, alpha=.15, lw=0) self.ax.add_patch(bck0) self.ax.add_patch(bck1) self.ax.add_patch(bck2) - self.ax.plot(xax,med,'b') + self.ax.plot(xax,med, 'b') self.ax.autoscale_view() self.add(self.canvas) self.canvas.show() #FIXME: Lineview plot cannot do selections -> disable in toolbar - self._toolbar = PlotToolbar(self.canvas, None) - self._toolbar.set_property('show-arrow', False) - self._toolbar.chk.connect ('toggled' ,self.set_selection_sensitive) - self.canvas.mpl_connect('resize_event',self.clear_background) + self._toolbar = PlotToolbar(self.canvas,self) + self._toolbar.chk.connect ('toggled' , self.set_selection_sensitive) + self.canvas.mpl_connect('resize_event', self.clear_background) def get_toolbar(self): return self._toolbar - def clear_background(self,event): + def clear_background(self, event): self._background = None def set_current_selection(self, selection): @@ -493,7 +383,7 @@ class LineViewPlot(Plot): if len(self.ax.collections)>0: self.ax.collections = [] segs = [self.line_segs[i] for i in index] - line_coll = LineCollection(segs,colors=(1,0,0,1)) + line_coll = LineCollection(segs, colors=(1,0,0,1)) line_coll.set_clip_box(self.ax.bbox) self.ax.update_datalim(line_coll.get_verts(self.ax.transData)) self._toolbar.forward() @@ -521,24 +411,24 @@ has no color and size options.""" self.use_blit = False self._background = None self.ax = self.fig.add_subplot(111) - self.ax.axhline(0,color='k',lw=1.,zorder=1) - self.ax.axvline(0,color='k',lw=1.,zorder=1) + self.ax.axhline(0, color='k', lw=1., zorder=1) + self.ax.axvline(0, color='k', lw=1., zorder=1) self.current_dim = id_dim self.dataset_1 = dataset_1 self.ms = s self._selection_line = None x_index = dataset_1[sel_dim][id_1] y_index = dataset_2[sel_dim][id_2] - self.xaxis_data = dataset_1._array[:,x_index] - self.yaxis_data = dataset_2._array[:,y_index] - self.ax.plot(self.xaxis_data,self.yaxis_data,'o',markeredgewidth=0,markersize=s) + self.xaxis_data = dataset_1._array[:, x_index] + self.yaxis_data = dataset_2._array[:, y_index] + self.ax.plot(self.xaxis_data, self.yaxis_data, 'o', markeredgewidth=0, markersize=s) self.ax.set_title(self.get_title()) self.add(self.canvas) self.canvas.show() # add toolbar - self._toolbar = PlotToolbar(self.canvas, None) - self._toolbar.chk.connect ('toggled' ,self.set_selection_sensitive) + self._toolbar = PlotToolbar(self.canvas, self) + self._toolbar.chk.connect ('toggled', self.set_selection_sensitive) self._toolbar.set_property('show-arrow', False) self._toolbar.set_select_callback(self.rectangle_select_callback) @@ -575,7 +465,7 @@ has no color and size options.""" if self._selection_line: self.ax.lines.remove(self._selection_line) - self._selection_line, = self.ax.plot(xdata_new,ydata_new,marker='o',markersize=self.ms,linestyle=None,markerfacecolor='r') + self._selection_line, = self.ax.plot(xdata_new, ydata_new,marker='o', markersize=self.ms, linestyle=None, markerfacecolor='r') self._toolbar.forward() #update data lims before draw if self.use_blit: @@ -598,14 +488,14 @@ class ScatterPlot(Plot): y_index = dataset_2[sel_dim_2][id_2] else: y_index = dataset_2[sel_dim][id_2] - self.xaxis_data = dataset_1._array[:,x_index] - self.yaxis_data = dataset_2._array[:,y_index] + self.xaxis_data = dataset_1._array[:, x_index] + self.yaxis_data = dataset_2._array[:, y_index] lw = scipy.zeros(self.xaxis_data.shape) - sc = self.ax.scatter(self.xaxis_data,self.yaxis_data,s=s,c=c,linewidth=lw,edgecolor='k',alpha=.6,cmap = cm.jet) + sc = self.ax.scatter(self.xaxis_data, self.yaxis_data, s=s, c=c, linewidth=lw, edgecolor='k', alpha=.6, cmap = cm.jet) if len(c)>1: - self.fig.colorbar(sc,ticks=[],fraction=.05) - self.ax.axhline(0,color='k',lw=1.,zorder=1) - self.ax.axvline(0,color='k',lw=1.,zorder=1) + self.fig.colorbar(sc,ticks=[], fraction=.05) + self.ax.axhline(0, color='k', lw=1., zorder=1) + self.ax.axvline(0, color='k', lw=1., zorder=1) self.ax.set_title(self.get_title()) # collection self.coll = self.ax.collections[0] @@ -615,8 +505,8 @@ class ScatterPlot(Plot): self.canvas.show() # create toolbar - self._toolbar = PlotToolbar(self.canvas, None) - self._toolbar.chk.connect ('toggled' ,self.set_selection_sensitive) + self._toolbar = PlotToolbar(self.canvas, self) + self._toolbar.chk.connect ('toggled' , self.set_selection_sensitive) self._toolbar.set_property('show-arrow', False) self._toolbar.set_select_callback(self.rectangle_select_callback) @@ -715,8 +605,8 @@ class NetworkPlot(Plot): networkx.draw_networkx(self.graph, ax=self.ax, **kw) # Setup toolbar - self._toolbar = PlotToolbar(self.canvas, None) - self._toolbar.chk.connect ('toggled' ,self.set_selection_sensitive) + self._toolbar = PlotToolbar(self.canvas, self) + self._toolbar.chk.connect ('toggled' , self.set_selection_sensitive) self._toolbar.set_property('show-arrow', False) self._toolbar.set_select_callback(self.rectangle_select_callback) @@ -726,8 +616,8 @@ class NetworkPlot(Plot): def rectangle_select_callback(self, x1, y1, x2, y2): 'event1 and event2 are the press and release events' pos = self.keywords['pos'] - ydata = scipy.zeros((len(pos),),'l') - xdata = scipy.zeros((len(pos),),'l') + ydata = scipy.zeros((len(pos),), 'l') + xdata = scipy.zeros((len(pos),), 'l') node_ids = [] c = 0 for name,(x,y) in pos.items(): @@ -778,6 +668,294 @@ class NetworkPlot(Plot): self.canvas.draw() +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'), + ) + + radiobuttons = ( + ('DEFAULT', 'Default mode', 'cursor.png', 'default'), + ('PAN', 'Pan axes with left mouse, zoom with right', 'move.png', 'pan'), + ('ZOOM', 'Zoom to rectangle','zoom_to_rect.png', 'zoom'), + ('SELECT', 'Select within rectangle', 'select.png', 'select'), + ) + + def __init__(self, canvas, plot): + self.win = None + 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._states = {} + 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._states[text] = rbutton + 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) + + def set_mode(self, active): + # if state is unkown or not set, set to default + if active == None or active not in self._states.keys(): + active = 'DEFAULT' + + # remove current Selector: + if self._selector: + print "Removing selector" + 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 = '' + + for state, button in self._states.items(): + if state != active: + continue + + if state == 'SELECT': + ax, = self.canvas.figure.get_axes() + props = dict(facecolor='blue', edgecolor = 'black', + alpha=0.3, fill=True) + self._selector = RectangleSelector(ax, self.onselect, + drawtype='box', useblit=True, + rectprops=props) + self.mode = 'Select rectangle mode' + print self.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 onselect(self,eclick, erelease): + 'eclick and erelease are matplotlib events at press and release' + if self._select_callback: + 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 mpl_draw_rubberband(self,event): + """Use RectangleSelector for rubberband drawing""" + + def draw_rubberband(self, event, x0, y0, x1, y1): + 'adapted from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/189744' + drawable = self.canvas.window + if drawable is None: + return + + gc = drawable.new_gc() + + height = self.canvas.figure.bbox.height() + y1 = height - y1 + y0 = height - y0 + + w = abs(x1 - x0) + h = abs(y1 - y0) + rect = [int(val)for val in min(x0,x1), min(y0, y1), w, h] + try: lastrect, imageBack = self._imageBack + except AttributeError: + #snap image back + if event.inaxes is None: + return + + ax = event.inaxes + l,b,w,h = [int(val) for val in ax.bbox.get_bounds()] + b = int(height)-(b+h) + axrect = l,b,w,h + self._imageBack = axrect, drawable.get_image(*axrect) + drawable.draw_rectangle(gc, False, *rect) + self._idleId = 0 + else: + def idle_draw(*args): + drawable.draw_image(gc, imageBack, 0, 0, *lastrect) + drawable.draw_rectangle(gc, False, *rect) + self._idleId = 0 + return False + if self._idleId == 0: + self._idleId = gobject.idle_add(idle_draw) + + 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. gobject.signal_new('view-changed', MainView, gobject.SIGNAL_RUN_LAST,