This repository has been archived on 2024-07-04. You can view files and clone it, but cannot push or open issues or pull requests.
laydi/system/plots.py
2006-08-04 09:32:11 +00:00

625 lines
20 KiB
Python

import pygtk
import gobject
import gtk
import matplotlib
import scipy
from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTK as NavigationToolbar2
from matplotlib.axes import Subplot
from matplotlib.figure import Figure
from matplotlib.numerix import arange, sin, pi
from matplotlib.widgets import RectangleSelector
from matplotlib import cm
import networkx
from system import logger
class ObjectTable:
"""A 2D table of elements."""
def __init__(self, xsize=0, ysize=0, creator=None):
self._elements = []
self._creator = creator or (lambda : None)
self.xsize = xsize
self.ysize = ysize
self.resize(xsize, ysize)
def resize(self, xsize, ysize):
"""Resizes the table by removing and creating elements as required."""
# Delete or append new rows
del self._elements[ysize:]
new_rows = ysize - len(self._elements)
self._elements.extend([list() for i in range(new_rows)])
# Delete or append new columns
for row in self._elements:
del row[xsize:]
new_elems = xsize - len(row)
row.extend([self._creator() for i in range(new_elems)])
def __getitem__(self, index):
x, y = index
return self._elements[y][x]
def __setitem__(self, index, value):
x, y = index
self._elements[y][x] = value
class ViewFrame (gtk.Frame):
"""
A ViewFrame is a gtk bin that contains a view.
The ViewFrame is either active or inactive, and belongs to a group of
VeiwFrames of which only one can be active at any time.
"""
def __init__(self, view_frames):
gtk.Frame.__init__(self)
self.focused = False
self.view_frames = view_frames
self.empty_view = EmptyView()
self._button_event = None
view_frames.append(self)
if len(view_frames) == 1:
self.focus()
else:
self.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.add(self._view)
self._view.show()
self.show()
def focus(self):
"""Gets focus and ensures that no other window is in focus."""
if self.focused:
return self
for frame in self.view_frames:
frame.unfocus()
self.set_shadow_type(gtk.SHADOW_IN)
self.focused = True
self.emit('focus-changed', self, True)
return self
def unfocus(self):
"""Removes focus from the ViewFrame. Does nothing if unfocused."""
if not self.focused:
return
self.set_shadow_type(gtk.SHADOW_OUT)
self.focused = False
self.emit('focus-changed', self, False)
def set_view(self, view):
"""Set view to view or to empty view if parameter is None"""
# if None is passed, use empty view
if view == None:
view = self.empty_view
# do nothing if the view is already there
if view == self._view:
return
# detach view from current parent
view_parent = view.get_parent()
if view_parent:
view_parent.set_view(None)
# switch which widget we are listening to
if self._button_event:
self._view.disconnect(self._button_event)
self._button_event = view.connect("button-press-event", self.on_button_press_event)
# remove old view, set new view
self._view.hide()
self.remove(self._view)
self.add(view)
view.show()
self._view = view
def get_view(self):
"""Returns current view, or None if the empty view is set."""
if self._view == self.empty_view:
return None
return self._view
def on_button_press_event(self, widget, event):
if not self.focused:
self.focus()
def on_drag_data_received(self, widget, drag_context, x, y, selection, info, timestamp):
treestore, path = selection.tree_get_row_drag_data()
iter = treestore.get_iter(path)
obj = treestore.get_value(iter,2)
if isinstance(obj, Plot):
self.set_view(obj)
class MainView (gtk.Notebook):
def __init__(self):
gtk.Notebook.__init__(self)
self.set_show_tabs(False)
self.set_show_border(False)
self._view_frames = []
self._views = ObjectTable(2, 2, lambda : ViewFrame(self._view_frames))
self._small_views = gtk.Table(2, 2, True)
self._small_views.set_col_spacings(4)
self._small_views.set_row_spacings(4)
self._large_view = ViewFrame(list())
self.update_small_views()
for vf in self._view_frames:
vf.connect('focus-changed', self.on_view_focus_changed)
self.append_page(self._small_views)
self.append_page(self._large_view)
self.show()
self.set_current_page(0)
def __getitem__(self, x, y):
return self._views[x, y]
def update_small_views(self):
for x in range(self._views.xsize):
for y in range(self._views.ysize):
child = self._views[x,y]
self._small_views.attach(child, x, x+1, y, y+1)
def get_active_small_view(self):
for vf in self._view_frames:
if vf.focused:
return vf
return None
def goto_large(self):
if self.get_current_page() == 1:
return
vf = self.get_active_small_view()
view = vf.get_view()
vf.set_view(None)
self._large_view.set_view(view)
self.set_current_page(1)
def goto_small(self):
if self.get_current_page() == 0:
return
vf = self.get_active_small_view()
view = self._large_view.get_view()
self._large_view.set_view(None)
vf.set_view(view)
self.set_current_page(0)
def insert_view(self, view):
if self.get_current_page() == 0:
vf = self.get_active_small_view()
else:
vf = self._large_view
vf.set_view(view)
def show(self):
for vf in self._view_frames:
vf.show()
self._small_views.show()
gtk.Notebook.show(self)
def on_view_focus_changed(self, widget, vf, focused):
if focused:
self.emit('view-changed', vf)
class Plot (gtk.Frame):
def __init__(self, title):
gtk.Frame.__init__(self)
self.sel_obj = None
self.title = title
self.selection_listener = None
self.set_shadow_type(gtk.SHADOW_NONE)
def get_title(self):
return self.title
def selection_changed(self, selection):
pass
def set_selection_listener(self, listener):
"""Allow project to listen to selections.
The selection will propagate back to all plots through the
selection_changed() method. The listener will be called as
listener(dimension_name, ids).
"""
self.selection_listener = listener
def get_toolbar(self):
return None
class EmptyView (Plot):
def __init__(self):
Plot.__init__(self, 'Empty view')
label = gtk.Label('No view')
ebox = gtk.EventBox()
ebox.add(label)
self.add(ebox)
label.show()
ebox.show()
self.show()
class NavToolbar(NavigationToolbar2):
toolitems = (('Select', 'Select within rectangle', 'zoom_to_rect.png',
'select'),) + NavigationToolbar2.toolitems
def __init__(self, *args):
NavigationToolbar2.__init__(self, *args)
self._select_callback = None
def select(self, *args):
"""Selection mode selected handler."""
if self._active == 'SELECT':
self._active = None
else:
self._active = 'SELECT'
if self._idPress is not None:
self._idPress = self.canvas.mpl_disconnect(self._idPress)
self.mode = ''
if self._idRelease is not None:
self._idRelease = self.canvas.mpl_disconnect(self._idRelease)
self.mode = ''
if self._active:
self._idPress = self.canvas.mpl_connect('button_press_event', self.press_select)
self._idRelease = self.canvas.mpl_connect('button_release_event', self.release_select)
self.mode = 'Select rectangle mode'
self.set_message(self.mode)
def set_message(self, s):
"""Set status in toolbar to string s.
Overrided to make sure message can be updated even when
drawing rubberband.
"""
self.message.set_label(s)
def press_select(self, event):
"""Mouse button pressed handler for selection mode."""
x, y = event.x, event.y
for i, a in enumerate(self.canvas.figure.get_axes()):
if event.inaxes==a and event.inaxes.get_navigate():
xmin, xmax = a.get_xlim()
ymin, ymax = a.get_ylim()
lim = xmin, xmax, ymin, ymax
self._xypress = x, y, a, i, lim, a.transData.deepcopy()
break
self.press(event)
def release_select(self, event):
"""Mouse button released handler for selection mode."""
# only release if button was pressed inside first?
if self._xypress:
x, y = event.x, event.y
lastx, lasty, a, ind, lim, trans = self._xypress
lastx, lasty = a.transData.inverse_xy_tup( (lastx, lasty) )
x, y = a.transData.inverse_xy_tup( (x, y) )
if self._select_callback:
self._select_callback(lastx, lasty, x, y)
self._xypress = None
self.draw()
self.release(event)
def mouse_move(self, event):
"""Extend NavigationToolbar2.mouse_move to provide selection support."""
from matplotlib.backend_bases import cursors
# Only update the rubberband for selection mode when mouse is
# within the plotting area.
if event.inaxes and self._active=='SELECT':
if self._lastCursor != cursors.SELECT_REGION:
self.set_cursor(cursors.SELECT_REGION)
self._lastCursor = cursors.SELECT_REGION
if self._xypress is not None:
x, y = event.x, event.y
lastx, lasty, a, ind, lim, trans= self._xypress
self.draw_rubberband(event, x, y, lastx, lasty)
NavigationToolbar2.mouse_move(self, event)
def set_select_callback(self, listener):
"""Allow plots to register a callback for selection events.
The callback will be called as listener(x1, y1, x2, y2). All
coordinates are in the plot coordinate system, not pixels or
widget coordinates.
"""
self._select_callback = listener
class SinePlot(Plot):
def __init__(self):
Plot.__init__(self, 'Sine plot')
fig = Figure(figsize=(5,4), dpi=72)
ax = fig.add_subplot(111)
t = arange(0.0,3.0,0.01)
s = sin(2*pi*t)
ax.plot(t,s)
self.canvas = FigureCanvas(fig)
self._toolbar = NavToolbar(self.canvas, None)
self._toolbar.set_property('show-arrow', False)
self.add(self.canvas)
self.canvas.show()
def get_toolbar(self):
return self._toolbar
class LinePlot(Plot):
def __init__(self, dataset, name="Line plot"):
Plot.__init__(self, name)
fig = Figure(figsize=(5,4), dpi=72)
self.canvas = FigureCanvas(fig)
self.ax = ax = fig.add_subplot(111)
self._dataset = dataset
import rpy
silent_eval = rpy.with_mode(rpy.NO_CONVERSION, rpy.r)
rpy.with_mode(rpy.NO_CONVERSION, rpy.r.assign)("m", dataset.asarray())
ymin, ymax = rpy.r("range(m)*1.1")
self._ymin, self._ymax = ymin, ymax
silent_eval("res <- apply(m, 2, density, from=%s, to=%s)" % (ymin, ymax))
silent_eval("k <- sapply(1:length(res), function(id) rev(res[[id]]$y))")
self._bg_matrix = bg_matrix = rpy.r("k")
rpy.r.rm(["k", "res", "m"])
# Hack - we draw plot in selection_changed()
self.selection_changed(None)
self.add(self.canvas)
self.canvas.show()
# We use a regular toolbar as we don't need selections
self._toolbar = NavigationToolbar2(self.canvas, None)
self._toolbar.set_property('show-arrow', False)
def get_toolbar(self):
self.canvas.draw()
return self._toolbar
def selection_changed(self, selection):
self.ax.clear()
rows, cols = self._bg_matrix.shape
self.ax.imshow(self._bg_matrix, cmap=cm.Greys, extent=(0.5, cols+0.5, self._ymin, self._ymax))
dim_2, dim_1 = self._dataset.get_dim_names()
if selection:
ids = selection[dim_2] # current identifiers
index = [ind for id,ind in self._dataset[dim_2].items() if id in ids] #conversion to index
for i in index:
line = self._dataset.asarray()[i]
self.ax.plot(range(1, len(line)+1), line)
self.ax.set_xlim((0.5, cols+0.5))
self.ax.set_ylim((self._ymin, self._ymax))
self.canvas.draw()
class ScatterPlot(Plot):
def __init__(self, dataset_1, dataset_2, id_dim, sel_dim, id_1, id_2, name="Scatter plot"):
Plot.__init__(self, name)
fig = Figure(figsize=(5,4), dpi=72)
self.ax = ax = fig.add_subplot(111)
self.current_dim = id_dim
# testing testing
self.dataset_1 = dataset_1
x_index = dataset_1[sel_dim][id_1]
y_index = dataset_2[sel_dim][id_2]
self.xaxis_data = dataset_1._array[:,x_index]
self.yaxis_data = dataset_2._array[:,y_index]
ax.plot(self.xaxis_data,self.yaxis_data,'og')
ax.set_title(self.get_title())
ax.set_xlabel("%s - %s" % (sel_dim, id_1))
ax.set_ylabel("%s - %s" % (sel_dim, id_2))
###
self.canvas = FigureCanvas(fig)
self.add(self.canvas)
self.canvas.show()
self._toolbar = NavToolbar(self.canvas, None)
self._toolbar.set_property('show-arrow', False)
self._toolbar.set_select_callback(self.rectangle_select_callback)
def get_toolbar(self):
return self._toolbar
def rectangle_select_callback(self, x1, y1, x2, y2):
ydata = self.yaxis_data
xdata = self.xaxis_data
# find indices of selected area
if x1>x2:
x1, x2 = x2, x1
if y1>y2:
y1, y2 = y2, y1
assert x1<=x2
assert y1<=y2
index = scipy.nonzero((xdata>x1) & (xdata<x2) & (ydata>y1) & (ydata<y2))
ids = self.dataset_1.get_identifiers(self.current_dim, index)
self.selection_listener(self.current_dim, ids)
def selection_changed(self, selection):
ids = selection[self.current_dim] # current identifiers
index = self.dataset_1.get_indices(self.current_dim, ids)
xdata_new = scipy.take(self.xaxis_data, index) #take data
ydata_new = scipy.take(self.yaxis_data, index)
self.ax.clear()
self.ax.plot(self.xaxis_data,self.yaxis_data,'og')
self.ax.plot(xdata_new,ydata_new,'or')
self.canvas.draw()
class NetworkPlot(Plot):
def __init__(self, dataset, **kw):
# Set member variables and call superclass' constructor
self.graph = dataset.asnetworkx()
self.dataset = dataset
self.keywords = kw
self.dim_name = self.dataset.get_dim_name(0)
if not kw.has_key('name'):
kw['name'] = self.dataset.get_name()
if not kw.has_key('prog'):
kw['prog'] = 'neato'
if not kw.has_key('pos') or kw['pos']:
kw['pos'] = networkx.drawing.nx_pydot.graphviz_layout(self.graph, kw['prog'])
Plot.__init__(self, kw['name'])
# Keep node size and color as dicts for fast lookup
self.node_size = {}
if kw.has_key('node_size') and cb.iterable(kw['node_size']):
kw.remove('node_size')
for id, size in zip(self.dataset[self.dim_name], kw['node_size']):
self.node_size[id] = size
else:
for id in dataset[self.dim_name]:
self.node_size[id] = 40
self.node_color = {}
if kw.has_key('node_color') and cb.iterable(kw['node_color']):
kw.remove('node_color')
for id, color in zip(self.dataset[self.dim_name], 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'
if kw.has_key('node_color'):
kw.pop('node_color')
# FIXME: What is figsize?
self.fig = Figure(figsize=(5, 4), dpi=72)
self.ax = self.fig.add_subplot(111)
# FIXME: ax shouldn't be in kw at all
if kw.has_key('ax'):
kw.pop('ax')
# Add canvas and show
self.canvas = FigureCanvas(self.fig)
self.add(self.canvas)
self.canvas.show()
# Initial draw
networkx.draw_networkx(self.graph, ax=self.ax, **kw)
# Setup toolbar
self._toolbar = NavToolbar(self.canvas, None)
self._toolbar.set_property('show-arrow', False)
self._toolbar.set_select_callback(self.rectangle_select_callback)
def get_toolbar(self):
return self._toolbar
def rectangle_select_callback(self, x1, y1, x2, y2):
'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')
node_ids = []
c = 0
for name,(x,y) in pos.items():
node_ids.append(name)
xdata[c] = x
ydata[c] = y
c+=1
# find indices of selected area
if x1 > x2:
x1, x2 = x2, x1
if y1 > y2:
y1, y2 = y2, y1
index = scipy.nonzero((xdata>x1) & (xdata<x2) & (ydata>y1) & (ydata<y2))
ids = [node_ids[i] for i in index]
self.selection_listener(self.dataset.get_dim_name(0), ids)
def selection_changed(self, selection):
ids = selection[self.dataset.get_dim_name(0)] # current identifiers
node_set = set(self.graph.nodes())
selected_nodes = list(ids.intersection(node_set))
unselected_nodes = list(node_set.difference(ids))
if self.node_color:
unselected_colors = [self.node_color[x] for x in unselected_nodes]
else:
unselected_colors = 'red'
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.clear()
networkx.draw_networkx_edges(self.graph, edge_list=self.graph.edges(), \
ax=self.ax, **self.keywords)
if unselected_nodes:
networkx.draw_networkx_nodes(self.graph, nodelist=unselected_nodes, \
node_color='r', node_size=unselected_sizes, ax=self.ax, **self.keywords)
if selected_nodes:
networkx.draw_networkx_nodes(self.graph, nodelist=selected_nodes, \
node_color='k', node_size=selected_sizes, ax=self.ax, **self.keywords)
self.canvas.draw()
# 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,))