diff --git a/fluents/plots.py b/fluents/plots.py index 317fac9..0873dac 100644 --- a/fluents/plots.py +++ b/fluents/plots.py @@ -1,15 +1,9 @@ -import os,sys -from itertools import izip - import pygtk import gobject import gtk -import matplotlib -from matplotlib import cm,cbook from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg from matplotlib.nxutils import points_inside_poly -from matplotlib.axes import Subplot from matplotlib.figure import Figure from matplotlib.collections import LineCollection from matplotlib.patches import Polygon,Rectangle, Circle @@ -20,434 +14,12 @@ import scipy import fluents import logger - -# global active mode. Used by toolbars to communicate correct mode -active_mode = 'default' - - -class ObjectTable: - """A 2D table of elements. - An ObjectTable is a resizable two-dimensional array of objects. - When resized, it will keep all the elements that fit in the new shape, - and forget about other elements. When new elements are needed to fill - out the new shape, they will be created. - """ - - def __init__(self, xsize=0, ysize=0, creator=None): - """Creates a two-dimensional table of objects. - @param xsize: Width of the object table. - @param ysize: Height of the object table. - @param creator: A function that will be used to instantiate new items. - """ - 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. - Makes the ObjectTable the specified size by removing elements that - will not fit in the new shape and appending elements to fill the - table. New elements will be created with the creator function passed - as an argument to __init__. - - @param xsize: The new width. - @type xsize: int - @param ysize: The new height. - @type ysize: int - """ - - # 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)]) - - self.xsize = xsize - self.ysize = ysize - - 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 container widget 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): - """Initializes a new ViewFrame. - @param view_frames: A list of view frames to which the new view - frame shouls belong. Only one ViewFrame in this list can be - active at any time. - """ - gtk.Frame.__init__(self) - self.focused = False - self.view_frames = view_frames - self.empty_view = EmptyView() - self._button_event = None - - ## Set up a VBox with a label wrapped in an event box. - label = gtk.Label() - ebox = gtk.EventBox() - ebox.add(label) - vbox = gtk.VBox() - vbox.pack_start(ebox, expand=False) - vbox.pack_start(gtk.HSeparator(), expand=False) - - self._ebox_button_event = ebox.connect("button-press-event", - self._on_button_press_event) - ## Keep the references for later use. - self._vbox = vbox - self._ebox = ebox - self._view_title = label - self.add(vbox) - - view_frames.append(self) - if len(view_frames) == 1: - self.focus() - else: - self.focused = True - 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._vbox.add(self._view) - self._view_title.set_text(self._view.title) - self.show_all() - self._view.show() - - def focus(self): - """Gets focus and ensures that no other ViewFrame in the same set - is in focus. - """ - if self.focused: - self.emit('focus-changed', self, True) - return self - - for frame in self.view_frames: - frame.unfocus() - - self.set_shadow_type(gtk.SHADOW_IN) - self._ebox.set_state(gtk.STATE_ACTIVE) - 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._ebox.set_state(gtk.STATE_NORMAL) - 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 - @param view: An instance of View - """ - - # 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 - if view._view_frame: - view._view_frame.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 - if self._view: - self._view.hide() - self._vbox.remove(self._view) - self._view._view_frame = None - - self._view_title.set_text(view.title) - self._vbox.add(view) - view.show() - - view._view_frame = self - self._view = view - - def get_view(self): - """Returns current view, or None if the empty view is set. - @returns: None if the ViewFrame is currently displaying it's - EmptyView. Otherwise it returns the currently displeyd View. - """ - 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) - self.focus() - - elif isinstance(obj, fluents.dataset.Dataset): - view = self.get_view() - if view.is_mappable_with(obj): - view._update_color_from_dataset(obj) - - elif isinstance(obj, fluents.dataset.Selection): - view = self.get_view() - if view.is_mappable_with(obj): - view.selection_changed(self.current_dim, obj) - - - -class MainView (gtk.Notebook): - """The MainView class displays the Views in Fluents. - - MainView, of which there is normally only one instance, contains a - gtk.Table that holds all the visible Views, and a single ViewFrame - that is used for maximizing plots. - """ - 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._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): - """Creates a new gtk.Table to show the views. Called after changes to - the _views property""" - - self._small_views = gtk.Table(self._views.ysize, self._views.xsize, True) - self._small_views.set_col_spacings(4) - self._small_views.set_row_spacings(4) - 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) - self._small_views.show_all() - - def get_active_small_view(self): - """Returns the active ViewFrame in the small views table. - If a view is maximized, the corresponding ViewFrame in the table - will be returned, not the active maximized ViewFrame""" - for vf in self._view_frames: - if vf.focused: - return vf - return None - - def get_active_view_frame(self): - """Returns the active view frame.""" - if self.get_current_page() == 0: - return self.get_active_small_view() - else: - return self._large_view - - def goto_large(self): - """Maximize the view in the current ViewFrame. - - Maximizes the View in the current ViewFrame. The ViewFrame itself is - not resized or modified, except it's view will be set to None. - This method will do nothing if a view is currently maximized. - """ - 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): - """Goes to the small views page. - - The maximized View will be given to the active ViewFrame in the view - table. - """ - 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): - """Set a view in the currently active ViewFrame. - @param view: An instance of View. - """ - if self.get_current_page() == 0: - vf = self.get_active_small_view() - else: - vf = self._large_view - vf.set_view(view) - - def set_all_plots(self, views): - """Displays all the views in the list, and hides all other views. - - Loops through all ViewFrames from top left to bottom right, and sets - their currently active View to the next view in the list. After the - last view is placed in a ViewFrame, the remaining ViewFrames are - emptied. - - @param views: A list of views to set. - """ - for y in range(self._views.ysize): - for x in range(self._views.xsize): - if views: - self._views[x, y].set_view(views.pop()) - else: - self._views[x, y].set_view(None) - - def get_active_view_index(self): - current = self.get_active_small_view() - for i in range(self._views.xsize): - for j in range(self._views.ysize): - if self._views[i, j] == current: - return (i, j) - return None - - def set_active_view_by_index(self, x, y): - self._views[x, y].focus() - - def move_focus_left(self): - x, y = self.get_active_view_index() - if x > 0: - self.set_active_view_by_index(x-1, y) - - def move_focus_right(self): - x, y = self.get_active_view_index() - if x < self._views.xsize-1: - self.set_active_view_by_index(x+1, y) - - def move_focus_up(self): - x, y = self.get_active_view_index() - if y > 0: - self.set_active_view_by_index(x, y-1) - - def move_focus_down(self): - x, y = self.get_active_view_index() - if y < self._views.ysize-1: - self.set_active_view_by_index(x, y+1) - - def _on_view_focus_changed(self, widget, vf, focused): - if focused: - self.emit('view-changed', vf) - - def resize_table(self, width, height): - """Resizes the list of small views. - """ - ## Hide all plots that will be removed from the screen. - for x in range(self._views.xsize): - for y in range(self._views.ysize): - if x >= width or y >= height: - self._views[x, y].set_view(None) - self._view_frames.remove(self._views[x, y]) - - for x in range(self._views.xsize): - for y in range(self._views.ysize): - self._small_views.remove(self._views[x, y]) - - self._views.resize(width, height) - self.update_small_views() - self.insert_page(self._small_views, gtk.Label("small"), 0) - self.remove_page(1) - #self.set_current_page(0) - self.goto_small() - - -class View (gtk.Frame): - """The base class of everything that is shown in the center view of - fluents. - - Most views should rather subclass Plot, which automatically handles - freezing, creates a toolbar, and sets up matplotlib Figure and Canvas - objects. - """ - - def __init__(self, title): - gtk.Frame.__init__(self) - self.title = title - self.set_shadow_type(gtk.SHADOW_NONE) - self._view_frame = None - - def get_toolbar(self): - return None - - def is_mappable_with(self, dataset): - """Override in individual plots.""" - return False - - -class EmptyView (View): - """EmptyView is shown in ViewFrames that are unused.""" - def __init__(self): - View.__init__(self, 'Empty view') - self.set_label(None) - label = gtk.Label('No view') - ebox = gtk.EventBox() - ebox.add(label) - self.add(ebox) - label.show() - ebox.show() - self.show() +import view -class Plot (View): +class Plot (view.View): def __init__(self, title): - View.__init__(self, title) + view.View.__init__(self, title) logger.log('debug', 'plot %s init' %title) self.selection_listener = None self.current_dim = None @@ -462,7 +34,7 @@ class Plot (View): self.fig = Figure() self.canvas = FigureCanvasGTKAgg(self.fig) self.axes = self.fig.gca() - self._toolbar = PlotToolbar(self) + self._toolbar = view.PlotToolbar(self) self.canvas.add_events(gtk.gdk.ENTER_NOTIFY_MASK) self.add(self.canvas) self.canvas.show() @@ -575,7 +147,7 @@ class LineViewPlot(Plot): x_axis = scipy.arange(self._data.shape[minor_axis]) 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)]) + self.line_segs.append([(xx,yy) for xx,yy in zip(x_axis, yi)]) # draw background self._set_background(self.axes) @@ -991,7 +563,7 @@ class BarPlot(Plot): self.axes.grid(False) n, m = dataset.shape if m>1: - sm = cm.ScalarMappable() + sm = matplotlib.cm.ScalarMappable() clrs = sm.to_rgba(range(n)) for i, row in enumerate(dataset.asarray()): left = scipy.arange(i+1, m*n+1, n) @@ -1104,9 +676,9 @@ class NetworkPlot(Plot): class VennPlot(Plot): def __init__(self, name="Venn diagram"): Plot.__init__(self, name) - - self._init_bck() + # init draw + self._init_bck() for c in self._venn_patches: self.axes.add_patch(c) for mrk in self._markers: @@ -1261,557 +833,6 @@ class VennPlot(Plot): return scipy.sqrt( (x2-x1)**2 + (y2-y1)**2 ) -class PlotMode: - """A PlotMode object corresponds to a mouse mode in a plot. - - When a mode is selected in the toolbar, the PlotMode corresponding - to the toolbar button is activated by calling setup(ax) for the axis - system ax. - """ - def __init__(self, plot, name, tooltip, image_file): - self.name = name - self.tooltip = tooltip - self.image_file = image_file - self.plot = plot - self.canvas = plot.canvas - - def get_icon(self): - """Returns the icon for the PlotMode""" - image = gtk.Image() - image.set_from_pixbuf(fluents.icon_factory.get(self.image_file)) - 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 - - -class DefaultPlotMode(PlotMode): - def __init__(self, plot): - PlotMode.__init__(self, plot, 'default', 'Default mode', 'cursor') - - -class PanPlotMode(PlotMode): - def __init__(self, plot): - PlotMode.__init__(self, plot, 'pan', - 'Pan axes with left mouse, zoom with right', - 'move') - - # Holds handler IDs for callbacks. - self._button_press = None - self._button_release = None - self._motion_notify = None - self._xypress = None - self._button_pressed = None - - def activate(self): - self._button_press = self.canvas.mpl_connect( - 'button_press_event', self._on_button_press) - self._button_release = self.canvas.mpl_connect( - 'button_release_event', self._on_button_release) - - def deactivate(self): - if self._button_press: - self.canvas.mpl_disconnect(self._button_press) - - if self._button_release: - self.canvas.mpl_disconnect(self._button_release) - - def _on_button_press(self, event): - - 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): - '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() - self.plot.emit('pan-changed', self) - - -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(): - 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): - 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 SelectLassoPlotMode(PlotMode): - def __init__(self, plot): - PlotMode.__init__(self, plot, 'lassoselect', - 'Select within lasso', 'lasso') - - 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): - for cid in self.cids: - self.canvas.mpl_disconnect(cid) - - -class SelectRectanglePlotMode(PlotMode): - def __init__(self, plot): - PlotMode.__init__(self, plot, 'select', - 'Select within rectangle', 'select') - self._selectors = {} - - 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): - for cid in self.cids: - self.canvas.mpl_disconnect(cid) - - -class PlotToolbar(gtk.Toolbar): - - def __init__(self, plot): - gtk.Toolbar.__init__(self) - self.plot = plot - 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) - self.canvas.connect('enter-notify-event', self.on_enter_notify) - self.show() - - self.add_mode(DefaultPlotMode(self.plot)) - self.add_mode(PanPlotMode(self.plot)) - self.add_mode(ZoomPlotMode(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) - - # Set up freeze button - btn = gtk.ToolButton(gtk.STOCK_SAVE) - - #btn.set_icon_widget(image) - btn.connect('clicked', self._on_save_clicked) - self.insert(btn, -1) - self.save_button = btn - - # Set up freeze button - btn = gtk.ToggleToolButton() - - image = gtk.Image() - image.set_from_pixbuf(fluents.icon_factory.get('freeze')) - - btn.set_icon_widget(image) - btn.connect('toggled', self._on_freeze_toggle) - self.insert(btn, -1) - self.freeze_button = btn - - self.show_all() - - 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: - other = None - - btn = gtk.RadioToolButton(other) - btn.set_icon_widget(mode.get_icon()) - btn.set_tooltip(self.tooltips, mode.tooltip, 'Private') - btn.connect('toggled', self._on_mode_toggle) - - self._mode_buttons[btn] = mode - self.insert(btn, -1) - - if self._current_mode == None: - self._current_mode = mode - - def get_mode(self): - """Returns the active mode name.""" - if self._current_mode: - return self._current_mode.name - return None - - def get_mode_by_name(self, mode_name): - """Returns the mode with the given name or None.""" - for m in self._mode_buttons.values(): - if m.name == mode_name: - return m - return None - - def get_button(self, mode_name): - """Returns the button that corresponds to a mode name.""" - for b, m in self._mode_buttons.items(): - if m.name == mode_name: - return b - return None - - def set_mode(self, mode_name): - """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() - - new_mode = self.get_mode_by_name(mode_name) - if new_mode: - new_mode.activate() - self._current_mode = self.get_mode_by_name(mode_name) - 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(): - 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) - - def _on_save_clicked(self, button): - """Pops up a file dialog saver, and saves current plot""" - dialog = gtk.FileChooserDialog('Save plot') - dialog.set_action(gtk.FILE_CHOOSER_ACTION_SAVE) - dialog.add_buttons(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK) - default_name = "fluents_image" - dialog.set_current_name("%s.png" % default_name) - retval = dialog.run() - if retval in [gtk.RESPONSE_CANCEL, gtk.RESPONSE_DELETE_EVENT]: - logger.log("debug", "Cancelled save plot") - elif retval == gtk.RESPONSE_OK: - logger.log("debug", "Saving plot as: %s" % dialog.get_filename()) - self.canvas.print_figure(dialog.get_filename()) - else: - print "unknown; ", retval - dialog.destroy() - - def _on_freeze_toggle(self, button): - self.plot.set_frozen(button.get_active()) - - def on_enter_notify(self, widget, event): - self.set_mode(active_mode) - # 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 - self._show_selector(False) - if self.verts is not None: - self.verts.append((event.xdata, event.ydata)) - 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 - self._show_selector(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._show_selector(False) - else: - if not self.polygon._visible or not self.line._visible: - # put poly + line back if we event is ok - self._show_selector(True) - - return ignore - - def _show_selector(self, bool): - if self.polygon and self.line: - self.polygon.set_visible(bool) - self.line.set_visible(bool) - if self._background == None: - self._background = self.canvas.copy_from_bbox(self.axes.bbox) - self.canvas.restore_region(self._background) - self.canvas.blit() - - -# 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,)) - # Create zoom-changed signal gobject.signal_new('zoom-changed', Plot, gobject.SIGNAL_RUN_LAST, None, (gobject.TYPE_PYOBJECT,))