Merged in the toolbar branch. The toolbar branch is now obsolte.
BIN
icons/cursor.png
Normal file
After Width: | Height: | Size: 406 B |
BIN
icons/filesave.png
Normal file
After Width: | Height: | Size: 873 B |
BIN
icons/freeze.png
Normal file
After Width: | Height: | Size: 724 B |
BIN
icons/home.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
icons/move.png
Normal file
After Width: | Height: | Size: 713 B |
BIN
icons/select.png
Normal file
After Width: | Height: | Size: 659 B |
BIN
icons/zoom_to_rect.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
779
system/plots.py
@ -20,7 +20,7 @@ import networkx
|
||||
import scipy
|
||||
|
||||
# global active mode. Used by toolbars to communicate correct mode
|
||||
active_mode = 'DEFAULT'
|
||||
active_mode = 'default'
|
||||
|
||||
class ObjectTable:
|
||||
"""A 2D table of elements."""
|
||||
@ -245,35 +245,78 @@ class MainView (gtk.Notebook):
|
||||
self.emit('view-changed', vf)
|
||||
|
||||
|
||||
class Plot (gtk.Frame):
|
||||
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 and
|
||||
toolbars, and sets up matplotlib Figure and Canvas objects.
|
||||
"""
|
||||
|
||||
def __init__(self, title):
|
||||
gtk.Frame.__init__(self)
|
||||
self.title = title
|
||||
self.sel_obj = None
|
||||
self.set_shadow_type(gtk.SHADOW_NONE)
|
||||
self.set_label(title)
|
||||
|
||||
def get_toolbar(self):
|
||||
return None
|
||||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
class Plot (View):
|
||||
def __init__(self, title):
|
||||
View.__init__(self, title)
|
||||
|
||||
self.selection_listener = None
|
||||
self.fig = Figure()
|
||||
self.canvas = FigureCanvas(self.fig)
|
||||
self.set_shadow_type(gtk.SHADOW_NONE)
|
||||
self._background = None
|
||||
self._sel_sensitive = True
|
||||
self._frozen = False
|
||||
self._toolbar = PlotToolbar(self)
|
||||
self.canvas.add_events(gtk.gdk.ENTER_NOTIFY_MASK)
|
||||
self.current_dim = None
|
||||
|
||||
def set_selection_sensitive(self,event):
|
||||
if event:
|
||||
if event.get_active():
|
||||
logger.log('debug','Selection freezed')
|
||||
self._sel_sensitive = False
|
||||
else:
|
||||
logger.log('debug','Selections active')
|
||||
self._sel_sensitive = True
|
||||
|
||||
def set_frozen(self, frozen):
|
||||
"""A frozen plot will not be updated when the current selection is changed."""
|
||||
self._frozen = frozen
|
||||
if not frozen:
|
||||
self.set_current_selection(self._current_selection)
|
||||
|
||||
def get_title(self):
|
||||
return self.title
|
||||
|
||||
def selection_changed(self, selection):
|
||||
if not self._sel_sensitive or not self.get_property('visible'):
|
||||
def get_toolbar(self):
|
||||
return self._toolbar
|
||||
|
||||
def selection_changed(self, dim_name, selection):
|
||||
""" Selection observer handle.
|
||||
|
||||
A selection change in a plot is only drawn if:
|
||||
1.) plot is sensitive to selections (not freezed)
|
||||
2.) plot is visible (has a view)
|
||||
3.) the selections dim_name is the plot's dimension.
|
||||
"""
|
||||
|
||||
if self._frozen \
|
||||
or not self.get_property('visible') \
|
||||
or self.current_dim != dim_name:
|
||||
return
|
||||
else:
|
||||
self._current_selection = selection
|
||||
|
||||
self.set_current_selection(selection)
|
||||
|
||||
def set_selection_listener(self, listener):
|
||||
@ -285,21 +328,7 @@ class Plot (gtk.Frame):
|
||||
"""
|
||||
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 LineViewPlot(Plot):
|
||||
"""Line view of current selection, no interaction
|
||||
Only works on 2d-arrays
|
||||
@ -315,7 +344,7 @@ class LineViewPlot(Plot):
|
||||
self.dataset = dataset
|
||||
Plot.__init__(self, name)
|
||||
self.ax = self.fig.add_subplot(111)
|
||||
self.ax.set_title(self.get_title())
|
||||
#self.ax.set_title(self.get_title())
|
||||
self.current_dim = self.dataset.get_dim_name(major_axis)
|
||||
if len(self._data.shape)==2 and not minor_axis:
|
||||
minor_axis = major_axis-1
|
||||
@ -359,14 +388,9 @@ class LineViewPlot(Plot):
|
||||
self.canvas.show()
|
||||
|
||||
#FIXME: Lineview plot cannot do selections -> disable in toolbar
|
||||
self._toolbar = PlotToolbar(self.canvas,self)
|
||||
self._toolbar.chk.connect ('toggled' , self.set_selection_sensitive)
|
||||
self._toolbar = PlotToolbar(self)
|
||||
self.canvas.mpl_connect('resize_event', self.clear_background)
|
||||
|
||||
|
||||
def get_toolbar(self):
|
||||
return self._toolbar
|
||||
|
||||
def clear_background(self, event):
|
||||
self._background = None
|
||||
|
||||
@ -386,27 +410,23 @@ class LineViewPlot(Plot):
|
||||
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()
|
||||
|
||||
if self.use_blit:
|
||||
self.ax.draw_artist(line_coll)
|
||||
#print "\nLine collection clip box:"
|
||||
line_coll.get_clip_box().get_bounds()
|
||||
#print "\nLine collection bbox:"
|
||||
#print self.ax.bbox.get_bounds()
|
||||
#print "Background bbox:"
|
||||
#print self._bbox.get_bounds()
|
||||
#self.canvas.blit(self._bbox)
|
||||
self.canvas.blit()
|
||||
|
||||
#self.ax.draw_artist(line_coll)
|
||||
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."""
|
||||
def __init__(self, dataset_1, dataset_2, id_dim, sel_dim, id_1, id_2,s=6, name="Scatter plot"):
|
||||
has no color and size options."""
|
||||
|
||||
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
|
||||
@ -422,19 +442,10 @@ has no color and size options."""
|
||||
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.ax.set_title(self.get_title())
|
||||
self.add(self.canvas)
|
||||
self.canvas.show()
|
||||
|
||||
# add toolbar
|
||||
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)
|
||||
|
||||
def get_toolbar(self):
|
||||
return self._toolbar
|
||||
|
||||
def rectangle_select_callback(self, x1, y1, x2, y2):
|
||||
ydata = self.yaxis_data
|
||||
xdata = self.xaxis_data
|
||||
@ -467,7 +478,7 @@ has no color and size options."""
|
||||
|
||||
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._toolbar.forward() #update data lims before draw
|
||||
if self.use_blit:
|
||||
self.ax.draw_artist(self._selection_line)
|
||||
self.canvas.blit()
|
||||
@ -496,7 +507,7 @@ class ScatterPlot(Plot):
|
||||
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())
|
||||
#self.ax.set_title(self.get_title())
|
||||
# collection
|
||||
self.coll = self.ax.collections[0]
|
||||
|
||||
@ -504,15 +515,6 @@ class ScatterPlot(Plot):
|
||||
self.add(self.canvas)
|
||||
self.canvas.show()
|
||||
|
||||
# create toolbar
|
||||
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)
|
||||
|
||||
def get_toolbar(self):
|
||||
return self._toolbar
|
||||
|
||||
def rectangle_select_callback(self, x1, y1, x2, y2):
|
||||
ydata = self.yaxis_data
|
||||
xdata = self.xaxis_data
|
||||
@ -532,7 +534,9 @@ class ScatterPlot(Plot):
|
||||
def set_current_selection(self, selection):
|
||||
ids = selection[self.current_dim] # current identifiers
|
||||
if len(ids)==0:
|
||||
print "nothing selected"
|
||||
return
|
||||
#self._toolbar.forward() #update data lims before draw
|
||||
index = self.dataset_1.get_indices(self.current_dim, ids)
|
||||
if self.use_blit:
|
||||
if self._background is None:
|
||||
@ -542,8 +546,7 @@ class ScatterPlot(Plot):
|
||||
if len(index)>0:
|
||||
lw.put(2.,index)
|
||||
self.coll.set_linewidth(lw)
|
||||
self._toolbar.forward() #update data lims before draw
|
||||
|
||||
|
||||
if self.use_blit:
|
||||
self.canvas.blit()
|
||||
self.ax.draw_artist(self.coll)
|
||||
@ -558,6 +561,7 @@ class NetworkPlot(Plot):
|
||||
self.dataset = dataset
|
||||
self.keywords = kw
|
||||
self.dim_name = self.dataset.get_dim_name(0)
|
||||
self.current_dim = self.dim_name
|
||||
if not kw.has_key('name'):
|
||||
kw['name'] = self.dataset.get_name()
|
||||
if not kw.has_key('prog'):
|
||||
@ -604,17 +608,10 @@ class NetworkPlot(Plot):
|
||||
# Initial draw
|
||||
networkx.draw_networkx(self.graph, ax=self.ax, **kw)
|
||||
|
||||
# Setup toolbar
|
||||
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)
|
||||
|
||||
def get_toolbar(self):
|
||||
return self._toolbar
|
||||
|
||||
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')
|
||||
@ -635,10 +632,10 @@ class NetworkPlot(Plot):
|
||||
|
||||
|
||||
ids = [node_ids[i] for i in index]
|
||||
self.selection_listener(self.dataset.get_dim_name(0), ids)
|
||||
self.selection_listener(self.current_dim, ids)
|
||||
|
||||
def set_current_selection(self, selection):
|
||||
ids = selection[self.dataset.get_dim_name(0)] # current identifiers
|
||||
ids = selection[self.current_dim] # current identifiers
|
||||
node_set = set(self.graph.nodes())
|
||||
|
||||
selected_nodes = list(ids.intersection(node_set))
|
||||
@ -668,293 +665,383 @@ 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'),
|
||||
)
|
||||
class PlotMode:
|
||||
"""A PlotMode object corresponds to a mouse mode in a plot.
|
||||
|
||||
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'),
|
||||
)
|
||||
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 __init__(self, canvas, plot):
|
||||
self.win = None
|
||||
def get_icon(self):
|
||||
"""Returns the icon for the PlotMode"""
|
||||
fname = os.path.join(fluents.ICONDIR, self.image_file)
|
||||
image = gtk.Image()
|
||||
image.set_from_file(fname)
|
||||
return image
|
||||
|
||||
def activate(self):
|
||||
"""Subclasses of PlotMode should do their initialization here.
|
||||
|
||||
The activate method is called when a mode is activated, and is
|
||||
used primarily to set up callback functions on events in the
|
||||
canvas.
|
||||
"""
|
||||
pass
|
||||
|
||||
def deactivate(self):
|
||||
"""Subclasses of PlotMode should do their cleanup here.
|
||||
|
||||
The deactivate method is primarily by subclasses of PlotMode to
|
||||
remove any callbacks they might have on the matplotlib canvas.
|
||||
"""
|
||||
pass
|
||||
|
||||
def _mpl_disconnect_all(self):
|
||||
"""Disconnects all matplotlib callbacks defined on the canvas.
|
||||
|
||||
This is a hack because the RectangleSelector in matplotlib does
|
||||
not store its callbacks, so we need a workaround to remove them.
|
||||
"""
|
||||
callbacks = self.plot.canvas.callbacks
|
||||
|
||||
for callbackd in callbacks.values():
|
||||
for c in callbackd.keys():
|
||||
del callbackd[c]
|
||||
|
||||
|
||||
class DefaultPlotMode (PlotMode):
|
||||
def __init__(self, plot):
|
||||
PlotMode.__init__(self, plot, 'default', 'Default mode', 'cursor.png')
|
||||
|
||||
def activate(self):
|
||||
for k, v in self.canvas.callbacks.items():
|
||||
print k, v
|
||||
|
||||
|
||||
class PanPlotMode (PlotMode):
|
||||
def __init__(self, plot):
|
||||
PlotMode.__init__(self, plot, 'pan',
|
||||
'Pan axes with left mouse, zoom with right',
|
||||
'move.png')
|
||||
|
||||
# Holds handler IDs for callbacks.
|
||||
self._button_press = None
|
||||
self._button_release = None
|
||||
self._motion_notify = None
|
||||
|
||||
self._button_pressed = None
|
||||
|
||||
def activate(self):
|
||||
self._button_press = self.canvas.mpl_connect(
|
||||
'button_press_event', self._on_button_press)
|
||||
self._button_relese = self.canvas.mpl_connect(
|
||||
'button_release_event', self._on_button_release)
|
||||
#self._drag = self.canvas.mpl_connect(
|
||||
# 'mouse_drag_event', self._on_drag)
|
||||
|
||||
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()
|
||||
|
||||
|
||||
class ZoomPlotMode (PlotMode):
|
||||
def __init__(self, plot):
|
||||
PlotMode.__init__(self, plot, 'zoom',
|
||||
'Zoom to rectangle','zoom_to_rect.png')
|
||||
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 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)))
|
||||
self.canvas.draw()
|
||||
|
||||
|
||||
class SelectPlotMode (PlotMode):
|
||||
def __init__(self, plot):
|
||||
PlotMode.__init__(self, plot, 'select',
|
||||
'Select within rectangle', 'select.png')
|
||||
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 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)
|
||||
|
||||
|
||||
class PlotToolbar(gtk.Toolbar):
|
||||
|
||||
def __init__(self, plot):
|
||||
gtk.Toolbar.__init__(self)
|
||||
NavigationToolbar2.__init__(self, canvas)
|
||||
self._idleId = 0
|
||||
self._select_callback = None
|
||||
canvas.connect('enter-notify-event', self.on_enter_notify)
|
||||
|
||||
def _init_toolbar(self):
|
||||
self.set_style(gtk.TOOLBAR_ICONS)
|
||||
self.plot = plot
|
||||
self.canvas = plot.canvas
|
||||
self._current_mode = None
|
||||
self.tooltips = gtk.Tooltips()
|
||||
self._states = {}
|
||||
self._selector = None
|
||||
|
||||
## Maps toolbar buttons to PlotMode objects.
|
||||
self._mode_buttons = {}
|
||||
|
||||
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')
|
||||
|
||||
#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(SelectPlotMode(self.plot))
|
||||
|
||||
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)
|
||||
# Set up freeze button
|
||||
btn = gtk.ToggleToolButton()
|
||||
|
||||
self.insert(gtk.SeparatorToolItem(), -1)
|
||||
|
||||
toolitem = gtk.ToolItem()
|
||||
self.insert(toolitem, -1)
|
||||
self.message = gtk.Label()
|
||||
toolitem.add(self.message)
|
||||
fname = os.path.join(fluents.ICONDIR, "freeze.png")
|
||||
image = gtk.Image()
|
||||
image.set_from_file(fname)
|
||||
|
||||
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)
|
||||
btn.set_icon_widget(image)
|
||||
btn.connect('toggled', self._on_freeze_toggle)
|
||||
self.insert(btn, -1)
|
||||
|
||||
self.show_all()
|
||||
|
||||
self.fileselect = FileChooserDialog(title='Save the figure', parent=self.win,)
|
||||
|
||||
def add_mode(self, mode):
|
||||
"""Adds a new mode to the toolbar."""
|
||||
|
||||
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
|
||||
if len(self._mode_buttons) > 0:
|
||||
other = self._mode_buttons.keys()[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)
|
||||
other = None
|
||||
|
||||
def save_figure(self, button):
|
||||
fname = self.fileselect.get_filename_from_user()
|
||||
if fname:
|
||||
self.canvas.print_figure(fname)
|
||||
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)
|
||||
|
||||
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)
|
||||
self._mode_buttons[btn] = mode
|
||||
self.insert(btn, -1)
|
||||
|
||||
w = int (toolfig.bbox.width())
|
||||
h = int (toolfig.bbox.height())
|
||||
if self._current_mode == None:
|
||||
self._current_mode = mode
|
||||
|
||||
|
||||
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)
|
||||
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)
|
||||
return self._current_mode
|
||||
|
||||
|
||||
def _on_mode_toggle(self, button):
|
||||
if button.get_active():
|
||||
self.set_mode(self._mode_buttons[button].name)
|
||||
|
||||
def _on_freeze_toggle(self, button):
|
||||
self.plot.set_frozen(button.get_active())
|
||||
|
||||
|
||||
# Create a view-changed signal that should be emitted every time
|
||||
# the active view changes.
|
||||
|