From 5ed602f355864f023093b7efe919a8e93fa733e1 Mon Sep 17 00:00:00 2001 From: flatberg Date: Mon, 26 Feb 2007 08:00:36 +0000 Subject: [PATCH] Lots of changes .... (new selector, mode updates, histogram selection, ++) --- fluents/plots.py | 683 ++++++++++++++++++++++++++++++----------------- 1 file changed, 444 insertions(+), 239 deletions(-) diff --git a/fluents/plots.py b/fluents/plots.py index 98aa745..809d53a 100644 --- a/fluents/plots.py +++ b/fluents/plots.py @@ -8,18 +8,19 @@ import logger import matplotlib from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas from matplotlib.backend_bases import NavigationToolbar2,cursors -from matplotlib.backends.backend_gtk import FileChooserDialog,cursord -from matplotlib.widgets import SubplotTool,RectangleSelector,Lasso +from matplotlib.backends.backend_gtk import FileChooserDialog from matplotlib.nxutils import points_inside_poly from matplotlib.axes import Subplot, AxesImage from matplotlib.figure import Figure from matplotlib import cm,cbook from pylab import Polygon, axis, Circle from matplotlib.collections import LineCollection +from matplotlib.patches import Polygon,Rectangle +from matplotlib.lines import Line2D from matplotlib.mlab import prctile import networkx import scipy - + # global active mode. Used by toolbars to communicate correct mode active_mode = 'default' @@ -415,19 +416,21 @@ class EmptyView (View): class Plot (View): def __init__(self, title): View.__init__(self, title) - + logger.log('debug', 'plot %s init' %title) self.selection_listener = None self.fig = Figure() self.canvas = FigureCanvas(self.fig) - self._background = None - self._frozen = False self._toolbar = PlotToolbar(self) self.canvas.add_events(gtk.gdk.ENTER_NOTIFY_MASK) self.current_dim = None self._current_selection = None - + self._background = None + self._frozen = False + self.use_blit = False + def set_frozen(self, frozen): - """A frozen plot will not be updated when the current selection is changed.""" + """A frozen plot will not be updated when the current + selection is changed.""" self._frozen = frozen if not frozen and self._current_selection != None: self.set_current_selection(self._current_selection) @@ -437,7 +440,7 @@ class Plot (View): def get_toolbar(self): return self._toolbar - + def selection_changed(self, dim_name, selection): """ Selection observer handle. @@ -471,14 +474,12 @@ class Plot (View): key map: shift : union control : intersection + """ if key == 'shift': ids = set(ids).union(self._current_selection[self.current_dim]) elif key == 'control': ids = set(ids).intersection(self._current_selection[self.current_dim]) - else: - pass - return ids def set_current_selection(self, selection): @@ -488,7 +489,23 @@ class Plot (View): no implemented selection can ignore selections alltogether. """ pass + + def rectangle_select_callback(self, *args): + """Overrriden in subclass.""" + pass + + def lasso_select_callback(self, *args): + """Overrriden in subclass.""" + pass + def get_index_from_selection(self, dataset, selection): + """Returns the index vector of current selection in given dim.""" + if not selection: return [] + ids = selection.get(self.current_dim, []) # current identifiers + if not ids : return [] + return dataset.get_indices(self.current_dim, ids) + + class LineViewPlot(Plot): """Line view plot with percentiles. @@ -511,28 +528,33 @@ class LineViewPlot(Plot): Plot.__init__(self, name) self.use_blit = False #fixme: blitting should work self.current_dim = self.dataset.get_dim_name(major_axis) + self.line_coll = None # make axes - self.ax = self.fig.add_subplot(111) + self.axes = self.fig.add_subplot(111) #initial draw x_axis = scipy.arange(self._data.shape[minor_axis]) - self.line_segs=[] + self.line_segs = [] for xi in range(self._data.shape[major_axis]): yi = self._data.take([xi], major_axis).ravel() self.line_segs.append([(xx,yy) for xx,yy in izip(x_axis, yi)]) - + # draw background - self._set_background(self.ax) + self._set_background(self.axes) # add canvas self.add(self.canvas) self.canvas.show() # add toolbar - #FIXME: Lineview plot cannot do selections -> disable in toolbar - #self._toolbar = PlotToolbar(self) - self.canvas.mpl_connect('resize_event', self.clear_background) + + self._toolbar = PlotToolbar(self) + # Disable selection modes + self._toolbar.freeze_button.set_sensitive(False) + self._toolbar.set_mode_sensitive('select', False) + self._toolbar.set_mode_sensitive('lassoselect', False) + #self.canvas.mpl_connect('resize_event', self.clear_background) def _set_background(self, ax): """Add three patches representing [min max],[5,95] and [25,75] percentiles, and a line at the median. @@ -583,45 +605,29 @@ class LineViewPlot(Plot): # median line ax.plot(xax, med, median_color, linewidth=median_width) - # Disable selection modes - btn = self._toolbar.get_button('select') - btn.set_sensitive(False) - btn = self._toolbar.get_button('lassoselect') - btn.set_sensitive(False) - self._toolbar.freeze_button.set_sensitive(False) - - def clear_background(self, event): - """Callback on resize event. Clears the background. - """ - self._background = None def set_current_selection(self, selection): """Draws the current selection. """ - ids = selection[self.current_dim] # current identifiers - index = self.dataset.get_indices(self.current_dim, ids) - if len(index)==0: # do we have a selection - return - if self.use_blit: - if self._background is None: - self._bbox = self.ax.bbox.deepcopy() - self._background = self.canvas.copy_from_bbox(self.ax.bbox) - self.canvas.restore_region(self._background) + index = self.get_index_from_selection(self.dataset, selection) - if len(self.ax.collections)>0: # remove old linecollection - self.ax.collections = [] + if self.line_coll: + self.axes.collections.remove(self.line_coll) segs = [self.line_segs[i] for i in index] - 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.line_coll = LineCollection(segs, colors=(1,0,0,1)) + self.axes.add_collection(self.line_coll) + #draw if self.use_blit: - self.ax.draw_artist(line_coll) + if self._background is None: + self._background = self.canvas.copy_from_bbox(self.axes.bbox) + self.canvas.restore_region(self._background) + self.axes.draw_artist(self.lines) self.canvas.blit() else: - self.ax.add_collection(line_coll) self.canvas.draw() + class ScatterMarkerPlot(Plot): """The ScatterMarkerPlot is faster than regular scatterplot, but has no color and size options.""" @@ -629,11 +635,9 @@ class ScatterMarkerPlot(Plot): def __init__(self, dataset_1, dataset_2, id_dim, sel_dim, id_1, id_2, s=6, name="Scatter plot"): Plot.__init__(self, name) - 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.axes = self.fig.add_subplot(111) + self.axes.axhline(0, color='k', lw=1., zorder=1) + self.axes.axvline(0, color='k', lw=1., zorder=1) self.current_dim = id_dim self.dataset_1 = dataset_1 self.ms = s @@ -642,8 +646,7 @@ class ScatterMarkerPlot(Plot): 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.ax.set_title(self.get_title()) + self.line = self.axes.plot(self.xaxis_data, self.yaxis_data, 'o', markeredgewidth=0, markersize=s) self.add(self.canvas) self.canvas.show() @@ -664,34 +667,33 @@ class ScatterMarkerPlot(Plot): self.selection_listener(self.current_dim, ids) def lasso_select_callback(self, verts, key=None): - self.canvas.draw_idle() xys = scipy.c_[self.xaxis_data[:,scipy.newaxis], self.yaxis_data[:,scipy.newaxis]] index = scipy.nonzero(points_inside_poly(xys, verts))[0] ids = self.dataset_1.get_identifiers(self.current_dim, index) ids = self.update_selection(ids, key) self.selection_listener(self.current_dim, ids) - self.canvas.widgetlock.release(self._lasso) def set_current_selection(self, selection): - ids = selection[self.current_dim] # current identifiers - index = self.dataset_1.get_indices(self.current_dim, ids) - if self.use_blit: - if self._background is None: - self._background = self.canvas.copy_from_bbox(self.ax.bbox) - self.canvas.restore_region(self._background) + index = self.get_index_from_selection(self.dataset_1, selection) if not len(index)>0: return xdata_new = self.xaxis_data.take(index) #take data ydata_new = self.yaxis_data.take(index) + #remove old selection if self._selection_line: - self.ax.lines.remove(self._selection_line) + self.axes.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._toolbar.forward() #update data lims before draw + self._selection_line = Line2D(xdata_new, ydata_new + ,marker='o', markersize=self.ms, + linewidth=0, markerfacecolor='r', + markeredgewidth=1.0) + self.axes.add_line(self._selection_line) if self.use_blit: - self.ax.draw_artist(self._selection_line) + if self._background is None: + self._background = self.canvas.copy_from_bbox(self.axes.bbox) + self.canvas.restore_region(self._background) + self.axes.draw_artist(self._selection_line) self.canvas.blit() else: self.canvas.draw() @@ -700,12 +702,13 @@ class ScatterMarkerPlot(Plot): class ScatterPlot(Plot): """The ScatterPlot is slower than scattermarker, but has size option.""" def __init__(self, dataset_1, dataset_2, id_dim, sel_dim, id_1, id_2, c='b', s=30, sel_dim_2=None, name="Scatter plot"): + self.dataset_1 = dataset_1 + self.s = s + self.c = c Plot.__init__(self, name) self.use_blit = False - self.ax = self.fig.add_subplot(111) - self._clean_bck = self.canvas.copy_from_bbox(self.ax.bbox) self.current_dim = id_dim - self.dataset_1 = dataset_1 + x_index = dataset_1[sel_dim][id_1] if sel_dim_2: y_index = dataset_2[sel_dim_2][id_2] @@ -713,29 +716,63 @@ class ScatterPlot(Plot): y_index = dataset_2[sel_dim][id_2] self.xaxis_data = dataset_1._array[:, x_index] self.yaxis_data = dataset_2._array[:, y_index] - lw = scipy.zeros(self.xaxis_data.shape) - self.sc = sc = self.ax.scatter(self.xaxis_data, self.yaxis_data, - s=s, c=c, linewidth=lw) - self.ax.axhline(0, color='k', lw=1., zorder=1) - self.ax.axvline(0, color='k', lw=1., zorder=1) - # labels self._text_labels = None + + self.init_draw() # add canvas to widget self.add(self.canvas) self.canvas.show() + self.connect('zoom-changed', self.onzoom) + self.connect('pan-changed', self.onpan) + self.need_redraw = False + self.canvas.mpl_connect('resize_event', self.onresize) + + def onzoom(self, widget, mode): + logger.log('notice', 'Zoom in widget: %s' %widget) + self.clean_redraw() + + def onpan(self, widget, mode): + logger.log('notice', 'Pan in widget: %s' %widget) + self.clean_redraw() + + def onresize(self, widget): + logger.log('notice', 'resize event') + self.clean_redraw() + + def clean_redraw(self): + if self.use_blit == True: + logger.log('notice', 'blit -> clean redraw ') + self.set_current_selection(None) + self._background = self.canvas.copy_from_bbox(self.axes.bbox) + self.set_current_selection(self._current_selection) + else: + self._background = None + + def init_draw(self): + self.axes = self.fig.add_subplot(111) + lw = scipy.zeros(self.xaxis_data.shape) + self.sc = self.axes.scatter(self.xaxis_data, self.yaxis_data, + s=self.s, c=self.c, linewidth=lw, zorder=3) + self.axes.axhline(0, color='k', lw=1., zorder=1) + self.axes.axvline(0, color='k', lw=1., zorder=1) + self._background = self.canvas.copy_from_bbox(self.axes.bbox) + def is_mappable_with(self, obj): """Returns True if dataset/selection is mappable with this plot. """ if isinstance(obj, fluents.dataset.Dataset): - if self.current_dim in obj.get_dim_name() and obj.asarray().shape[0] == self.xaxis_data.shape[0]: + if self.current_dim in obj.get_dim_name() \ + and obj.asarray().shape[0] == self.xaxis_data.shape[0]: return True + elif isinstance(obj, fluents.dataset.Selection): if self.current_dim in obj.get_dim_name(): print "Selection is mappable" return True + else: return False @@ -782,7 +819,6 @@ class ScatterPlot(Plot): self.selection_listener(self.current_dim, ids) def lasso_select_callback(self, verts, key=None): - self.canvas.draw_idle() xys = scipy.c_[self.xaxis_data[:,scipy.newaxis], self.yaxis_data[:,scipy.newaxis]] index = scipy.nonzero(points_inside_poly(xys, verts))[0] ids = self.dataset_1.get_identifiers(self.current_dim, index) @@ -790,21 +826,17 @@ class ScatterPlot(Plot): self.selection_listener(self.current_dim, ids) def set_current_selection(self, selection): - ids = selection[self.current_dim] # current identifiers - if len(ids)==0: - return - index = self.dataset_1.get_indices(self.current_dim, ids) - if self.use_blit: + linewidth = scipy.zeros(self.xaxis_data.shape, 'f') + index = self.get_index_from_selection(self.dataset_1, selection) + if len(index) > 0: + linewidth.put(2, index) + self.sc.set_linewidth(linewidth) + + if self.use_blit and len(index)>0 : if self._background is None: - self._background = self.canvas.copy_from_bbox(self.ax.bbox) + self._background = self.canvas.copy_from_bbox(self.axes.bbox) self.canvas.restore_region(self._background) - lw = scipy.zeros(self.xaxis_data.shape, 'f') - if len(index)>0: - lw.put(2., index) - self.sc.set_linewidth(lw) - - if self.use_blit: - self.ax.draw_artist(self.sc) + self.axes.draw_artist(self.sc) self.canvas.blit() else: self.canvas.draw() @@ -817,52 +849,115 @@ class ImagePlot(Plot): Plot.__init__(self, kw['name']) - self.ax = self.fig.add_subplot(111) - self.ax.set_xticks([]) - self.ax.set_yticks([]) - self.ax.grid(False) + self.axes = self.fig.add_subplot(111) + self.axes.set_xticks([]) + self.axes.set_yticks([]) + self.axes.grid(False) # Initial draw - self.ax.imshow(dataset.asarray(), interpolation='nearest', aspect='auto') + self.axes.imshow(dataset.asarray(), interpolation='nearest', aspect='auto') # Add canvas and show self.add(self.canvas) self.canvas.show() # Disable selection modes - btn = self._toolbar.get_button('select') - btn.set_sensitive(False) - btn = self._toolbar.get_button('lassoselect') - btn.set_sensitive(False) self._toolbar.freeze_button.set_sensitive(False) + self._toolbar.set_mode_sensitive('select', False) + self._toolbar.set_mode_sensitive('lassoselect', False) def get_toolbar(self): return self._toolbar class HistogramPlot(Plot): + """ Histogram plot. + If dataset is 1-dim the current_dim is set and selections may + be performed. For dataset> 1.dim the histogram is over all values + and selections are not defined,""" + def __init__(self, dataset, **kw): Plot.__init__(self, kw['name']) - - self.ax = self.fig.add_subplot(111) - self.ax.grid(False) - - # Initial draw - self.ax.hist(dataset.asarray(), bins=20) + self.dataset = dataset + self._data = dataset.asarray() + self.axes = self.fig.add_subplot(111) + self.axes.grid(False) + # If dataset is 1-dim we may do selections + if dataset.shape[0]==1: + self.current_dim = dataset.get_dim_name(1) + if dataset.shape[1]==1: + self.current_dim = dataset.get_dim_name(0) + # Initial draw + bins = min(len(self.dataset[self.current_dim]), 20) + count, lims, self.patches = self.axes.hist(self._data, bins=bins) + + # Add identifiers to the individual patches + if self.current_dim != None: + for i, patch in enumerate(self.patches): + if i==len(self.patches)-1: + end_lim = self._data.max() + 1 + else: + end_lim = lims[i+1] + bool_ind = scipy.bitwise_and(self._data>=lims[i], + self._data<=end_lim) + patch.index = scipy.where(bool_ind)[0] # Add canvas and show self.add(self.canvas) self.canvas.show() - # Disable selection modes - btn = self._toolbar.get_button('select') - btn.set_sensitive(False) - btn = self._toolbar.get_button('lassoselect') - btn.set_sensitive(False) - self._toolbar.freeze_button.set_sensitive(False) - - def get_toolbar(self): - return self._toolbar + if self.current_dim==None: + # Disable selection modes + self._toolbar.freeze_button.set_sensitive(False) + self._toolbar.set_mode_sensitive('select', False) + self._toolbar.set_mode_sensitive('lassoselect', False) + + def rectangle_select_callback(self, x1, y1, x2, y2, key): + if self.current_dim == None: return + # make (x1, y1) the lower left corner + if x1>x2: + x1, x2 = x2, x1 + if y1>y2: + y1, y2 = y2, y1 + + self.active_patches = [] + for patch in self.patches: + xmin = patch.xy[0] + xmax = xmin + patch.get_width() + ymin, ymax = 0, patch.get_height() + if xmax>x1 and xmin y2 or ymax>y1): + self.active_patches.append(patch) + if not self.active_patches: return + + ids = set() + for patch in self.active_patches: + ids.update(self.dataset.get_identifiers(self.current_dim, + patch.index)) + ids = self.update_selection(ids, key) + self.selection_listener(self.current_dim, ids) + + def lasso_select_callback(self, verts, key): + if self.current_dim == None: return + self.active_patches = [] + for patch in self.patches: + if scipy.any(points_inside_poly(verts, patch.get_verts())): + self.active_patches.append(patch) + if not self.active_patches: return + ids = set() + for patch in self.active_patches: + ids.update(self.dataset.get_identifiers(self.current_dim, + patch.index)) + ids = self.update_selection(ids, key) + self.selection_listener(self.current_dim, ids) + + def set_current_selection(self, selection): + index = self.get_index_from_selection(self.dataset, selection) + for patch in self.patches: + patch.set_facecolor('b') + for patch in self.patches: + if scipy.intersect1d(patch.index, index).size>1: + patch.set_facecolor('r') + self.canvas.draw() class BarPlot(Plot): @@ -875,8 +970,8 @@ class BarPlot(Plot): self.dataset = dataset n, m = dataset.shape Plot.__init__(self, name) - self.ax = self.fig.add_subplot(111) - self.ax.grid(False) + self.axes = self.fig.add_subplot(111) + self.axes.grid(False) # Initial draw if m>1: @@ -887,22 +982,20 @@ class BarPlot(Plot): height = row color = clrs[i] c = (color[0], color[1], color[2]) - self.ax.bar(left, height,color=c) + self.axes.bar(left, height,color=c) else: height = dataset.asarray().ravel() left = scipy.arange(1, n, 1) - self.ax.bar(left, height) + self.axes.bar(left, height) # Add canvas and show self.add(self.canvas) self.canvas.show() # Disable selection modes - btn = self._toolbar.get_button('select') - btn.set_sensitive(False) - btn = self._toolbar.get_button('lassoselect') - btn.set_sensitive(False) self._toolbar.freeze_button.set_sensitive(False) + self._toolbar.set_mode_sensitive('select', False) + self._toolbar.set_mode_sensitive('lassoselect', False) def get_toolbar(self): return self._toolbar @@ -914,59 +1007,50 @@ class NetworkPlot(Plot): self.graph = dataset.asnetworkx() self.dataset = dataset self.keywords = kw - self.dim_name = self.dataset.get_dim_name(0) - - if not kw.has_key('with_labels'): - kw['with_labels'] = False if not kw.has_key('name'): kw['name'] = self.dataset.get_name() + Plot.__init__(self, kw['name']) + self.current_dim = self.dataset.get_dim_name(0) if not kw.has_key('prog'): kw['prog'] = 'neato' - if not kw.has_key('pos') or kw['pos']: - kw['pos'] = networkx.graphviz_layout(self.graph, kw['prog']) + if not kw.has_key('pos'): + kw['pos'] = networkx.graphviz_layout(self.graph, 'neato') if not kw.has_key('nodelist'): - kw['nodelist'] = self.dataset.get_identifiers(self.dim_name, sorted=True) - Plot.__init__(self, kw['name']) - self.current_dim = self.dim_name + kw['nodelist'] = self.dataset.get_identifiers(self.current_dim, sorted=True) + if not kw.has_key('with_labels'): + kw['with_labels'] = True # Keep node size and color as dicts for fast lookup self.node_size = {} if kw.has_key('node_size') and cbook.iterable(kw['node_size']): kw.remove('node_size') - for id, size in zip(self.dataset[self.dim_name], kw['node_size']): + for id, size in zip(self.dataset[self.current_dim], kw['node_size']): self.node_size[id] = size else: - for id in dataset[self.dim_name]: + for id in dataset[self.current_dim]: self.node_size[id] = 30 self.node_color = {} if kw.has_key('node_color') and cbook.iterable(kw['node_color']): kw.remove('node_color') - for id, color in zip(self.dataset[self.dim_name], kw['node_color']): + for id, color in zip(self.dataset[self.current_dim], kw['node_color']): self.node_color[id] = color else: - self.node_color = None -# for id in self.dataset[self.dim_name]: -# self.node_color[id] = 'red' + for id in self.dataset[self.current_dim]: + self.node_color[id] = 1.0 - if kw.has_key('node_color'): - kw.pop('node_color') - - self.ax = self.fig.add_subplot(111) - self.ax.set_xticks([]) - self.ax.set_yticks([]) - self.ax.grid(False) - self.ax.set_frame_on(False) - # FIXME: ax shouldn't be in kw at all - if kw.has_key('ax'): - kw.pop('ax') + self.axes = self.fig.add_subplot(111) + self.axes.set_xticks([]) + self.axes.set_yticks([]) + self.axes.grid(False) + self.axes.set_frame_on(False) # Add canvas and show self.add(self.canvas) self.canvas.show() # Initial draw - networkx.draw_networkx(self.graph, ax=self.ax, **kw) + networkx.draw_networkx(self.graph, ax=self.axes, node_size=30, node_color='gray', **kw) del kw['nodelist'] def get_toolbar(self): @@ -1009,7 +1093,7 @@ class NetworkPlot(Plot): ids = [node_ids[i] for i in index] ids = self.update_selection(ids, key) self.selection_listener(self.current_dim, ids) - self.canvas.widgetlock.release(self._lasso) + def set_current_selection(self, selection): ids = selection[self.current_dim] # current identifiers @@ -1026,23 +1110,26 @@ class NetworkPlot(Plot): if self.node_size: unselected_sizes = [self.node_size[x] for x in unselected_nodes] selected_sizes = [self.node_size[x] for x in selected_nodes] - - self.ax.collections=[] + else: + unselected_sizes = 30 + selected_sizes = 30 + + self.axes.collections = [] networkx.draw_networkx_edges(self.graph, edge_list=self.graph.edges(), - ax=self.ax, + ax=self.axes, **self.keywords) - networkx.draw_networkx_labels(self.graph,**self.keywords) + networkx.draw_networkx_labels(self.graph, **self.keywords) if unselected_nodes: networkx.draw_networkx_nodes(self.graph, nodelist=unselected_nodes, \ - node_color='gray', node_size=unselected_sizes, ax=self.ax, **self.keywords) + node_color='gray', node_size=unselected_sizes, ax=self.axes, **self.keywords) if selected_nodes: networkx.draw_networkx_nodes(self.graph, nodelist=selected_nodes, \ - node_color='r', node_size=selected_sizes, ax=self.ax, **self.keywords) - self.ax.collections[-1].set_zorder(3) + node_color='r', node_size=selected_sizes, ax=self.axes, **self.keywords) + self.axes.collections[-1].set_zorder(3) self.canvas.draw() @@ -1125,7 +1212,6 @@ class VennPlot(Plot): def lasso_select_callback(self, verts, key=None): if verts==None: - print "ks" verts = (self._event.xdata, self._event.ydata) if key!='shift': for m in self._markers: @@ -1160,8 +1246,6 @@ class VennPlot(Plot): self._last_active = self.active_elements.copy() self._sel_label = 'Sel: ' + str(len(self.active_elements)) self._legend.texts[1].set_text(self._sel_label) - self.canvas.widgetlock.release(self._lasso) - del self._lasso self._ax.figure.canvas.draw() def rectangle_select_callback(self, x1, y1, x2, y2, key): @@ -1254,25 +1338,13 @@ class PlotMode: """ 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): +class DefaultPlotMode(PlotMode): def __init__(self, plot): PlotMode.__init__(self, plot, 'default', 'Default mode', 'cursor') -class PanPlotMode (PlotMode): +class PanPlotMode(PlotMode): def __init__(self, plot): PlotMode.__init__(self, plot, 'pan', 'Pan axes with left mouse, zoom with right', @@ -1288,10 +1360,8 @@ class PanPlotMode (PlotMode): def activate(self): self._button_press = self.canvas.mpl_connect( 'button_press_event', self._on_button_press) - self._button_relese = self.canvas.mpl_connect( + self._button_release = self.canvas.mpl_connect( 'button_release_event', self._on_button_release) - #self._drag = self.canvas.mpl_connect( - # 'mouse_drag_event', self._on_drag) def deactivate(self): if self._button_press: @@ -1427,89 +1497,86 @@ class PanPlotMode (PlotMode): self._xypress = None self._button_pressed = None self.canvas.draw() + self.plot.emit('pan-changed', self) -class ZoomPlotMode (PlotMode): +class ZoomPlotMode(PlotMode): def __init__(self, plot): PlotMode.__init__(self, plot, 'zoom', 'Zoom to rectangle','zoom_to_rect') self._selectors = {} def activate(self): + self.cids = [] for ax in self.canvas.figure.get_axes(): - props = dict(facecolor = 'blue', - edgecolor = 'black', - alpha = 0.3, - fill = True) - - rs = RectangleSelector(ax, self._on_select, drawtype='box', - useblit=True, rectprops = props) - self.canvas.draw() - self._selectors[rs] = ax - + selector = Selector(ax, self._on_select, + select_type='rectangle') + selector.line.set_linestyle('-') + selector.line.set_linewidth(0.5) + self.cids.append(self.canvas.mpl_connect('button_release_event', + selector._onrelease)) + self.cids.append(self.canvas.mpl_connect('motion_notify_event', + selector._onmove)) + self.cids.append(self.canvas.mpl_connect('button_press_event', + selector._onpress)) def deactivate(self): - self._mpl_disconnect_all() - self._selectors = {} - - def _on_select(self, start, end): - 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))) + for cid in self.cids: + self.canvas.mpl_disconnect(cid) + + def _on_select(self, x1, y1, x2, y2, key): + if abs(x1-x2)<0.0001 or abs(y1-y2)<0.0001: + # too sensitive zoom crashes matplotlib + return + ax = self.canvas.figure.get_axes()[0] + ax.set_xlim((min(x1, x2), max(x1, x2))) + ax.set_ylim((min(y1,y2), max(y1, y2))) self.canvas.draw() + self.plot.emit('zoom-changed', self) -class SelectPlotMode2 (PlotMode): +class SelectLassoPlotMode(PlotMode): def __init__(self, plot): PlotMode.__init__(self, plot, 'lassoselect', 'Select within lasso', 'lasso') - def activate(self): - self._button_press = self.canvas.mpl_connect( - 'button_press_event', self._on_select) + self._selectors = {} + def activate(self): + self.cids = [] + for ax in self.canvas.figure.get_axes(): + lasso = Selector(ax, self.plot.lasso_select_callback, + select_type='lasso') + self.cids.append(self.canvas.mpl_connect('button_release_event', + lasso._onrelease)) + self.cids.append(self.canvas.mpl_connect('motion_notify_event', + lasso._onmove)) + self.cids.append(self.canvas.mpl_connect('button_press_event', + lasso._onpress)) def deactivate(self): - self._mpl_disconnect_all() - self.plot._lasso = None - - def _on_select(self, event): - if event.inaxes is None: - logger.log('debug', 'Lasso select not in axes') - return - self.plot._lasso = Lasso(event.inaxes, (event.xdata, event.ydata), self.lasso_callback) - self.plot._lasso.line.set_linewidth(1) - self.plot._lasso.line.set_linestyle('--') - self._event = event - - def lasso_callback(self, verts): - self.plot.lasso_select_callback(verts, self._event.key) + for cid in self.cids: + self.canvas.mpl_disconnect(cid) -class SelectPlotMode (PlotMode): +class SelectRectanglePlotMode(PlotMode): def __init__(self, plot): PlotMode.__init__(self, plot, 'select', 'Select within rectangle', 'select') self._selectors = {} - - def activate(self): - for ax in self.canvas.figure.get_axes(): - props = dict(facecolor = 'blue', - edgecolor = 'black', - alpha = 0.3, - fill = True) - - rs = RectangleSelector(ax, self._on_select, drawtype='box', - useblit=True, rectprops = props) - self.canvas.draw() - self._selectors[rs] = ax + def activate(self): + self.cids = [] + for ax in self.canvas.figure.get_axes(): + lasso = Selector(ax, self.plot.rectangle_select_callback, + select_type='rectangle') + self.cids.append(self.canvas.mpl_connect('button_release_event', + lasso._onrelease)) + self.cids.append(self.canvas.mpl_connect('motion_notify_event', + lasso._onmove)) + self.cids.append(self.canvas.mpl_connect('button_press_event', + lasso._onpress)) def deactivate(self): - self._mpl_disconnect_all() - self._selectors = {} - - def _on_select(self, start, end): - self.plot.rectangle_select_callback(start.xdata, start.ydata, - end.xdata, end.ydata, end.key) + for cid in self.cids: + self.canvas.mpl_disconnect(cid) class PlotToolbar(gtk.Toolbar): @@ -1520,7 +1587,7 @@ class PlotToolbar(gtk.Toolbar): self.canvas = plot.canvas self._current_mode = None self.tooltips = gtk.Tooltips() - + self._mode_sensitive = {} ## Maps toolbar buttons to PlotMode objects. self._mode_buttons = {} self.set_property('show-arrow', False) @@ -1530,8 +1597,8 @@ class PlotToolbar(gtk.Toolbar): self.add_mode(DefaultPlotMode(self.plot)) self.add_mode(PanPlotMode(self.plot)) self.add_mode(ZoomPlotMode(self.plot)) - self.add_mode(SelectPlotMode(self.plot)) - self.add_mode(SelectPlotMode2(self.plot)) + self.add_mode(SelectRectanglePlotMode(self.plot)) + self.add_mode(SelectLassoPlotMode(self.plot)) self.insert(gtk.SeparatorToolItem(), -1) self.set_style(gtk.TOOLBAR_ICONS) @@ -1560,6 +1627,7 @@ class PlotToolbar(gtk.Toolbar): def add_mode(self, mode): """Adds a new mode to the toolbar.""" + self._mode_sensitive[mode] = True if len(self._mode_buttons) > 0: other = self._mode_buttons.keys()[0] else: @@ -1600,7 +1668,7 @@ class PlotToolbar(gtk.Toolbar): """Sets a mode by name. Returns the mode or None""" if mode_name == self._current_mode.name: return None - + if self._current_mode: self._current_mode.deactivate() @@ -1614,12 +1682,17 @@ class PlotToolbar(gtk.Toolbar): if self.get_button(mode_name) and \ not self.get_button(mode_name).get_active(): self.get_button(mode_name).set_active(True) - globals()['active_mode'] = mode_name return self._current_mode - + def set_mode_sensitive(self, mode, bool): + self._mode_sensitive[mode] = bool + self.get_button(mode).set_sensitive(False) + def _on_mode_toggle(self, button): + mode = self._mode_buttons[button].name + if self._mode_sensitive.get(mode) == False: + logger.log('notice', 'Mode: %s is not sensitive in this plot' %mode) if button.get_active(): self.set_mode(self._mode_buttons[button].name) @@ -1648,6 +1721,127 @@ class PlotToolbar(gtk.Toolbar): # need views (plots) to grab key-events widget.grab_focus() + +class Selector: + def __init__(self, ax, callback, select_type='lasso'): + self.drawon = True + self.eventson = True + self.select_type = select_type + self.eventpress = None + self.eventrelease = None + self._background = None + self.callback = callback + self.axes = ax + self.figure = self.axes.figure + self.canvas = self.figure.canvas + self.verts = [] + # to draw + patch_props = dict(facecolor='gray', linewidth = 0, + alpha=0.3, fill=True) + line_props = dict(linestyle='--', lw=1.0, color='black') + + self.line = Line2D([0,0], [0,0], visible=False, **line_props) + self.axes.add_line(self.line) + if self.select_type == 'lasso': + self.polygon = Polygon([(0, 0)], visible=False, **patch_props) + elif self.select_type == 'rectangle': + self.polygon = Rectangle((0,0), 0, 1, visible=False, **patch_props) + self.axes.add_patch(self.polygon) + + def _onrelease(self, event): + if self.eventpress is None or self._ignore(event): + return + + self.eventrelease = event + if self.verts is not None: + self.verts.append((event.xdata, event.ydata)) + self.line.set_visible(False) + self.polygon.set_visible(False) + self.canvas.blit(self.axes.bbox) + if self.select_type == 'lasso': + if len(self.verts)>2: + self.callback(self.verts, event.key) + elif self.select_type == 'rectangle': + x1, y1 = self.eventpress.xdata, self.eventpress.ydata + x2, y2 = self.eventrelease.xdata, self.eventrelease.ydata + self.callback(x1, y1, x2, y2, event.key) + + self.verts = [] + self.eventpress = None + self.eventrelease = None + self._background = None + return False + + def _onmove(self, event): + if self.eventpress is None or self._ignore(event):return + + # add new data + if self.select_type == 'lasso': + self.verts.append((event.xdata, event.ydata)) + self.polygon.xy = self.verts + self.line.set_data(zip(*self.verts)) + + elif self.select_type == 'rectangle': + x,y = event.xdata, event.ydata + minx, maxx = self.eventpress.xdata, x + miny, maxy = self.eventpress.ydata, y + if minx>maxx: minx, maxx = maxx, minx + if miny>maxy: miny, maxy = maxy, miny + self.polygon.xy[0] = minx + self.polygon.xy[1] = miny + self.polygon.set_width(maxx-minx) + self.polygon.set_height(maxy-miny) + xx = [minx, maxx, maxx, minx, minx] + yy = [miny, miny, maxy, maxy, miny] + self.line.set_data(xx,yy) + + # draw + self.canvas.restore_region(self._background) + self.axes.draw_artist(self.line) + self.axes.draw_artist(self.polygon) + self.canvas.blit(self.axes.bbox) + + def _onpress(self, event): + # Is the event to be ignored? + if self._ignore(event): return + if self._background == None: + self._background = self.canvas.copy_from_bbox(self.axes.bbox) + + self.polygon.set_visible(True) + self.line.set_visible(True) + self.eventpress = event + self.verts = [(event.xdata, event.ydata)] + return False + + def _ignore(self, event): + 'return True if event should be ignored' + ignore = False + # If no button was pressed yet ignore the event if it was out + # of the axes + if self.eventpress == None: + return event.inaxes!= self.axes + + # If a button was pressed, check if the release-button is the + # same. + ignore = event.inaxes!=self.axes or\ + event.button != self.eventpress.button + + if ignore: + # remove poly + line if they are visible + if self.polygon._visible or self.line._visible: + self.polygon.set_visible(False) + self.line.set_visible(False) + self.canvas.restore_region(self._background) + self.canvas.blit() + else: + if not self.polygon._visible or not self.line._visible: + # put poly + line back if we event is ok + self.polygon.set_visible(True) + self.line.set_visible(True) + + return ignore + + # 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, @@ -1659,3 +1853,14 @@ gobject.signal_new('focus-changed', ViewFrame, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, gobject.TYPE_BOOLEAN,)) +# Create zoom-changed signal +gobject.signal_new('zoom-changed', Plot, gobject.SIGNAL_RUN_LAST, None, + (gobject.TYPE_PYOBJECT,)) + +# Create pan/zoom-changed signal +gobject.signal_new('pan-changed', Plot, gobject.SIGNAL_RUN_LAST, None, + (gobject.TYPE_PYOBJECT,)) + +# Create plot-resize-changed signal +gobject.signal_new('plot-resize-changed', Plot, gobject.SIGNAL_RUN_LAST, None, + (gobject.TYPE_PYOBJECT,))