import pygtk import gobject import gtk import matplotlib import scipy from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTK as NavigationToolbar2 from matplotlib.axes import Subplot from matplotlib.figure import Figure from matplotlib.numerix import arange, sin, pi from matplotlib.widgets import RectangleSelector from matplotlib import cm from system import logger class ObjectTable: """A 2D table of elements.""" def __init__(self, xsize=0, ysize=0, creator=None): self._elements = [] self._creator = creator or (lambda : None) self.xsize = xsize self.ysize = ysize self.resize(xsize, ysize) def resize(self, xsize, ysize): """Resizes the table by removing and creating elements as required.""" # Delete or append new rows del self._elements[ysize:] new_rows = ysize - len(self._elements) self._elements.extend([list() for i in range(new_rows)]) # Delete or append new columns for row in self._elements: del row[xsize:] new_elems = xsize - len(row) row.extend([self._creator() for i in range(new_elems)]) def __getitem__(self, index): x, y = index return self._elements[y][x] def __setitem__(self, index, value): x, y = index self._elements[y][x] = value class ViewFrame (gtk.Frame): """ A ViewFrame is a gtk bin that contains a view. The ViewFrame is either active or inactive, and belongs to a group of VeiwFrames of which only one can be active at any time. """ def __init__(self, view_frames): gtk.Frame.__init__(self) self.focused = False self.view_frames = view_frames self.empty_view = EmptyView() self._button_event = None view_frames.append(self) if len(view_frames) == 1: self.focus() else: self.unfocus() # Get dropped views self.drag_dest_set(gtk.DEST_DEFAULT_ALL, [("GTK_TREE_MODEL_ROW", gtk.TARGET_SAME_APP, 7)], gtk.gdk.ACTION_LINK) self.connect("drag-data-received", self.on_drag_data_received) # Set view self._view = self.empty_view self._view.connect("button-press-event", self.on_button_press_event) self.add(self._view) self._view.show() self.show() def focus(self): """Gets focus and ensures that no other window is in focus.""" if self.focused: return self for frame in self.view_frames: frame.unfocus() self.set_shadow_type(gtk.SHADOW_IN) self.focused = True self.emit('focus-changed', self, True) return self def unfocus(self): """Removes focus from the ViewFrame. Does nothing if unfocused.""" if not self.focused: return self.set_shadow_type(gtk.SHADOW_OUT) self.focused = False self.emit('focus-changed', self, False) def set_view(self, view): """Set view to view or to empty view if parameter is None""" # if None is passed, use empty view if view == None: view = self.empty_view # do nothing if the view is already there if view == self._view: return # detach view from current parent view_parent = view.get_parent() if view_parent: view_parent.set_view(None) # switch which widget we are listening to if self._button_event: self._view.disconnect(self._button_event) self._button_event = view.connect("button-press-event", self.on_button_press_event) # remove old view, set new view self._view.hide() self.remove(self._view) self.add(view) view.show() self._view = view def get_view(self): """Returns current view, or None if the empty view is set.""" if self._view == self.empty_view: return None return self._view def on_button_press_event(self, widget, event): if not self.focused: self.focus() def on_drag_data_received(self, widget, drag_context, x, y, selection, info, timestamp): treestore, path = selection.tree_get_row_drag_data() iter = treestore.get_iter(path) obj = treestore.get_value(iter,2) if isinstance(obj, Plot): self.set_view(obj) class MainView (gtk.Notebook): def __init__(self): gtk.Notebook.__init__(self) self.set_show_tabs(False) self.set_show_border(False) self._view_frames = [] self._views = ObjectTable(2, 2, lambda : ViewFrame(self._view_frames)) self._small_views = gtk.Table(2, 2, True) self._small_views.set_col_spacings(4) self._small_views.set_row_spacings(4) self._large_view = ViewFrame(list()) self.update_small_views() for vf in self._view_frames: vf.connect('focus-changed', self.on_view_focus_changed) self.append_page(self._small_views) self.append_page(self._large_view) self.show() self.set_current_page(0) def __getitem__(self, x, y): return self._views[x, y] def update_small_views(self): for x in range(self._views.xsize): for y in range(self._views.ysize): child = self._views[x,y] self._small_views.attach(child, x, x+1, y, y+1) def get_active_small_view(self): for vf in self._view_frames: if vf.focused: return vf return None def goto_large(self): if self.get_current_page() == 1: return vf = self.get_active_small_view() view = vf.get_view() vf.set_view(None) self._large_view.set_view(view) self.set_current_page(1) def goto_small(self): if self.get_current_page() == 0: return vf = self.get_active_small_view() view = self._large_view.get_view() self._large_view.set_view(None) vf.set_view(view) self.set_current_page(0) def insert_view(self, view): if self.get_current_page() == 0: vf = self.get_active_small_view() else: vf = self._large_view vf.set_view(view) def show(self): for vf in self._view_frames: vf.show() self._small_views.show() gtk.Notebook.show(self) def on_view_focus_changed(self, widget, vf, focused): if focused: self.emit('view-changed', vf) class Plot (gtk.Frame): def __init__(self, title): gtk.Frame.__init__(self) self.sel_obj = None self.title = title self.selection_listener = None self.set_shadow_type(gtk.SHADOW_NONE) def get_title(self): return self.title def selection_changed(self, selection): pass def set_selection_listener(self, listener): """Allow project to listen to selections. The selection will propagate back to all plots through the selection_changed() method. The listener will be called as listener(dimension_name, ids). """ self.selection_listener = listener def get_toolbar(self): return None class EmptyView (Plot): def __init__(self): Plot.__init__(self, 'Empty view') label = gtk.Label('No view') 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 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.draw() 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 SinePlot(Plot): def __init__(self): Plot.__init__(self, 'Sine plot') fig = Figure(figsize=(5,4), dpi=72) ax = fig.add_subplot(111) t = arange(0.0,3.0,0.01) s = sin(2*pi*t) ax.plot(t,s) self.canvas = FigureCanvas(fig) self._toolbar = NavToolbar(self.canvas, None) self._toolbar.set_property('show-arrow', False) self.add(self.canvas) self.canvas.show() def get_toolbar(self): return self._toolbar class LinePlot(Plot): def __init__(self, dataset, name="Line plot"): Plot.__init__(self, name) fig = Figure(figsize=(5,4), dpi=72) self.canvas = FigureCanvas(fig) self.ax = ax = fig.add_subplot(111) self._dataset = dataset import rpy silent_eval = rpy.with_mode(rpy.NO_CONVERSION, rpy.r) rpy.with_mode(rpy.NO_CONVERSION, rpy.r.assign)("m", dataset.asarray()) ymin, ymax = rpy.r("range(m)*1.1") self._ymin, self._ymax = ymin, ymax silent_eval("res <- apply(m, 2, density, from=%s, to=%s)" % (ymin, ymax)) silent_eval("k <- sapply(1:length(res), function(id) rev(res[[id]]$y))") self._bg_matrix = bg_matrix = rpy.r("k") rpy.r.rm(["k", "res", "m"]) # Hack - we draw plot in selection_changed() self.selection_changed(None) self.add(self.canvas) self.canvas.show() # We use a regular toolbar as we don't need selections self._toolbar = NavigationToolbar2(self.canvas, None) self._toolbar.set_property('show-arrow', False) def get_toolbar(self): self.canvas.draw() return self._toolbar def selection_changed(self, selection): self.ax.clear() rows, cols = self._bg_matrix.shape self.ax.imshow(self._bg_matrix, cmap=cm.Greys, extent=(0.5, cols+0.5, self._ymin, self._ymax)) dim_2, dim_1 = self._dataset.get_dim_names() if selection: ids = selection[dim_2] # current identifiers index = [ind for id,ind in self._dataset[dim_2].items() if id in ids] #conversion to index for i in index: line = self._dataset.asarray()[i] self.ax.plot(range(1, len(line)+1), line) self.ax.set_xlim((0.5, cols+0.5)) self.ax.set_ylim((self._ymin, self._ymax)) self.canvas.draw() class ScatterPlot(Plot): def __init__(self, dataset_1, dataset_2, id_dim, sel_dim, id_1, id_2, name="Scatter plot"): Plot.__init__(self, name) fig = Figure(figsize=(5,4), dpi=72) self.ax = ax = fig.add_subplot(111) self.current_dim = id_dim # testing testing self.dataset_1 = dataset_1 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] ax.plot(self.xaxis_data,self.yaxis_data,'og') ax.set_title(self.get_title()) ax.set_xlabel("%s - %s" % (sel_dim, id_1)) ax.set_ylabel("%s - %s" % (sel_dim, id_2)) ### self.canvas = FigureCanvas(fig) self.add(self.canvas) self.canvas.show() self._toolbar = NavToolbar(self.canvas, None) self._toolbar.set_property('show-arrow', False) self._toolbar.set_select_callback(self.rectangle_select_callback) def get_toolbar(self): return self._toolbar def rectangle_select_callback(self, x1, y1, x2, y2): ydata = self.yaxis_data xdata = self.xaxis_data # find indices of selected area if x1>x2: if y1x2) & (ydata>y1) & (ydatax2) & (ydatay2)) else: #logger.log('debug','Selection x_start less than x_end') if y1x1) & (xdatay1) & (ydatax1) & (xdatay2)) ids = self.dataset_1.get_identifiers(self.current_dim, index) self.selection_listener(self.current_dim, ids) def selection_changed(self, selection): ids = selection[self.current_dim] # current identifiers index = self.dataset_1.get_indices(self.current_dim, ids) xdata_new = scipy.take(self.xaxis_data,index) #take data ydata_new = scipy.take(self.yaxis_data,index) self.ax.clear() self.ax.plot(self.xaxis_data,self.yaxis_data,'og') self.ax.plot(xdata_new,ydata_new,'or') self.canvas.draw() class NetworkPlot(Plot): def __init__(self, dataset, name='Network Plot'): Plot.__init__(self) fig = Figure(figsize=(5,4), dpi=72) self.ax = ax = fig.add_subplot(111) self.canvas = FigureCanvas(fig) self.add(self.canvas) self.canvas.show() self._toolbar = NavToolbar(self.canvas, None) self._toolbar.set_property('show-arrow', False) self._toolbar.set_select_callback(self.rectangle_select_callback) def get_toolbar(self): return self._toolbar def selection_changed(self, selection): return None # 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, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)) # Create focus-changed signal gobject.signal_new('focus-changed', ViewFrame, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, gobject.TYPE_BOOLEAN,))