From e0ca48d4b318d13bd4615c1e93679f1ac35f5408 Mon Sep 17 00:00:00 2001 From: einarr Date: Fri, 8 Sep 2006 18:25:03 +0000 Subject: [PATCH] Changed how the selection list works. CategoryDatasets can be dragged down to the selection list, and will then be converted to Selections. --- system/dataset.py | 44 +++-- system/fluents.glade | 90 ++++++--- system/fluents.py | 15 +- system/selections.py | 387 +++++++++++++++++++++++++++++++++++-- workflows/test_workflow.py | 8 +- 5 files changed, 480 insertions(+), 64 deletions(-) diff --git a/system/dataset.py b/system/dataset.py index 9d39665..35013bd 100644 --- a/system/dataset.py +++ b/system/dataset.py @@ -136,20 +136,23 @@ class Dataset: return self._all_dims def get_dim_name(self,axis=None): - """Returns dim name for an axis, if no axis is provided it returns a list of dims""" + """Returns dim name for an axis, if no axis is provided it + returns a list of dims""" if type(axis)==int: return self._dims[axis] else: return [dim for dim in self] def get_identifiers(self, dim, indices=None,sorted=False): - """Returns identifiers along dim, sorted by position (index) is optional. + """Returns identifiers along dim, sorted by position (index) + is optional. - You can optionally provide a list/ndarray of indices to get only the - identifiers of a given position. + You can optionally provide a list/ndarray of indices to get + only the identifiers of a given position. - Identifiers are the unique names (strings) for a variable in a given dim. - Index (Indices) are the Identifiers position in a matrix in a given dim. + Identifiers are the unique names (strings) for a variable in a + given dim. Index (Indices) are the Identifiers position in a + matrix in a given dim. """ try: if len(indices)==0:# if empty list or empty array @@ -170,18 +173,22 @@ class Dataset: def get_indices(self, dim, idents=None): """Returns indices for identifiers along dimension. - You can optionally provide a list of identifiers to retrieve a index subset. + You can optionally provide a list of identifiers to retrieve a + index subset. - Identifiers are the unique names (strings) for a variable in a given dim. - Index (Indices) are the Identifiers position in a matrix in a given dim. - If none of the input identifiers are found an empty index is returned + Identifiers are the unique names (strings) for a variable in a + given dim. Index (Indices) are the Identifiers position in a + matrix in a given dim. If none of the input identifiers are + found an empty index is returned """ if idents==None: index = array_sort(self._map[dim].values()) else: - index = [self._map[dim][key] for key in idents if self._map[dim].has_key(key)] + index = [self._map[dim][key] + for key in idents if self._map[dim].has_key(key)] return asarray(index) + class CategoryDataset(Dataset): """The category dataset class. @@ -216,7 +223,8 @@ class CategoryDataset(Dataset): """ data={} for name,ind in self._map[self.get_dim_name(0)].items(): - data[name] = self.get_identifiers(self.get_dim_name(1),list(self._array[ind,:].nonzero())) + data[name] = self.get_identifiers(self.get_dim_name(1), + list(self._array[ind,:].nonzero())) self._dictlists = data self.has_dictlists = True return data @@ -226,9 +234,10 @@ class CategoryDataset(Dataset): """ ret_list = [] for cat_name,ind in self._map[self.get_dim_name(1)].items(): - ids = self.get_identifiers(self.get_dim_name(0),self._array[:,ind].nonzero()) + ids = self.get_identifiers(self.get_dim_name(0), + self._array[:,ind].nonzero()[0]) selection = Selection(cat_name) - selection.select(cat_name,ids) + selection.select(self.get_dim_name(0), ids) ret_list.append(selection) return ret_list @@ -242,6 +251,7 @@ class GraphDataset(Dataset): If the library NetworkX is installed, there is support for representing the graph as a NetworkX.Graph, or NetworkX.XGraph structure. """ + def __init__(self,array=None,identifiers=None,shape=None,all_dims=[],**kwds): Dataset.__init__(self,array=array,identifiers=identifiers,name='A') self.has_graph = False @@ -256,9 +266,9 @@ class GraphDataset(Dataset): return G def _graph_from_adj_matrix(self,A,labels=None,nx_type='graph'): - """Creates a networkx graph class from adjacency matrix and ordered labels. - nx_type = ['graph',['xgraph']] - labels = None, results in string-numbered labels + """Creates a networkx graph class from adjacency matrix and + ordered labels. nx_type = ['graph',['xgraph']] labels = None, + results in string-numbered labels """ import networkx as nx diff --git a/system/fluents.glade b/system/fluents.glade index a62b5d8..8b6b851 100644 --- a/system/fluents.glade +++ b/system/fluents.glade @@ -643,35 +643,73 @@ - + True True - 400 + 554 - + True True - GTK_POLICY_NEVER - GTK_POLICY_ALWAYS - GTK_SHADOW_NONE - GTK_CORNER_TOP_LEFT + 230 - + True + True + GTK_POLICY_NEVER + GTK_POLICY_AUTOMATIC GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT - + True - create_selection_tree - 0 - 0 - Fri, 04 Aug 2006 12:42:10 GMT + True + True + False + False + True + False + False + False + + True + False + + + + + + True + True + GTK_POLICY_NEVER + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + True + False + False + True + False + False + False + + + + + True + True + @@ -681,28 +719,25 @@ - + True True GTK_POLICY_NEVER GTK_POLICY_ALWAYS - GTK_SHADOW_NONE + GTK_SHADOW_IN GTK_CORNER_TOP_LEFT - + True - GTK_SHADOW_IN - - - - True - create_identifier_list - 0 - 0 - Fri, 04 Aug 2006 12:42:25 GMT - - + True + True + False + False + True + False + False + False @@ -715,6 +750,7 @@ False True + diff --git a/system/fluents.py b/system/fluents.py index 3e68437..2dff247 100755 --- a/system/fluents.py +++ b/system/fluents.py @@ -32,7 +32,11 @@ class FluentApp: gtk.glade.set_custom_handler(self.custom_object_factory) self.widget_tree = gtk.glade.XML(GLADEFILENAME, 'appwindow') self.workflow = wf(self) - self['selection_tree'].set_identifier_list(self['identifier_list']) +# self['selection_tree'].set_identifier_list(self['identifier_list']) + self.dimlistcontroller = selections.DimListController(self['dim_list'], + self['selection_tree'], + self['identifier_list']) +# self['dim_list'].set_selection_tree(self['selection_tree']) def init_gui(self): self['appwindow'].set_size_request(800, 600) @@ -71,7 +75,9 @@ class FluentApp: self.project = project self.workflow.add_project(self.project) self.navigator_view.add_project(self.project) - self['selection_tree'].set_project(self.project) + self.dimlistcontroller.set_project(project) +# self['selection_tree'].set_project(self.project) +# self['dim_list'].set_project(self.project) def set_workflow(self, workflow): self.workflow = workflow @@ -143,6 +149,11 @@ class FluentApp: self.navigator_view.show() return self.navigator_view + def create_dim_list(self, str1, str2, int1, int2): + self.dim_list = selections.DimList() + self.dim_list.show() + return self.dim_list + def create_selection_tree(self, str1, str2, int1, int2): self.selection_tree = selections.SelectionTree() self.selection_tree.show() diff --git a/system/selections.py b/system/selections.py index 7dabf17..0e53deb 100644 --- a/system/selections.py +++ b/system/selections.py @@ -19,7 +19,316 @@ class SimpleMenu(gtk.Menu): self.append(item) item.show() + +class DimListController: + def __init__(self, dimlist, seltree, idlist): + + self._current_dim = None + + ## dimstore is a list of all dimensions in the application + self.dimstore = gtk.ListStore(gobject.TYPE_STRING) + + ## Two types of lines, one for CategoryDatasets and one for + ## Selections. The elements are title, link to dataset or selection + ## and the name of the dimension. + self.selstore = gtk.TreeStore(gobject.TYPE_STRING, + gobject.TYPE_PYOBJECT, + gobject.TYPE_STRING) + self.selstore_filter = self.selstore.filter_new() + self.selstore_filter.set_visible_func(self.dimension_filter) + + ## idstore is a list of currently selected identifiers + ## the list is cleared and rebuilt each time a new selection is + ## focused. + self.idstore = gtk.ListStore(gobject.TYPE_STRING) + + ## The widgets we are controlling + self.dimlist = dimlist + self.seltree = seltree + self.idlist = idlist + + ## Set up dimensions list + dimlist.set_model(self.dimstore) + + renderer = gtk.CellRendererText() + dim_column = gtk.TreeViewColumn('Dimension', renderer, text=0) + dimlist.insert_column(dim_column, 0) + + dimlist.connect('row-activated', self._dim_row_activated) + dimlist.connect('cursor-changed', self._dim_cursor_changed) + + ## Set up selection tree + seltree.set_model(self.selstore_filter) + + renderer = gtk.CellRendererText() + sel_column = gtk.TreeViewColumn('Dimension', renderer, text=0) + seltree.insert_column(sel_column, 0) + + seltree.connect('row-activated', self._sel_row_activated) + seltree.connect('cursor-changed', self._sel_cursor_changed) + + seltree.drag_dest_set(gtk.DEST_DEFAULT_ALL, + [("GTK_TREE_MODEL_ROW", gtk.TARGET_SAME_APP, 7)], + gtk.gdk.ACTION_LINK) + seltree.connect('drag-data-received', self.drag_data_received) + + ## Set up identifier list + idlist.set_model(self.idstore) + + renderer = gtk.CellRendererText() + dim_column = gtk.TreeViewColumn('Identifiers', renderer, text=0) + idlist.insert_column(dim_column, 0) + + + def get_dataset_iter(self, dataset): + """Returns the iterator to the selection tree row containing a + given dataset.""" + i = self.selstore.get_iter_first() + while i: + if self.selstore.get_value(i, 1) == dataset: + return i + i = self.selstore.iter_next(i) + return None + + def set_project(self, project): + """Dependency injection.""" + print "setting project" + self.dim_names = project.dim_names + self.update_dims() + project.add_selection_observer(self) + project.add_dataset_observer(self) + + def get_dimension(self, dim): + """Returns the iterator to the dimension with the given name, or + None if not found.""" + + i = self.dimstore.get_iter_first() + while i: + if self.dimstore.get_value(i, 0) == dim: + return i + i = self.dimstore.iter_next(i) + return None + + def dimension_filter(self, store, row): + """Filters out everything but the selected dimension.""" + row_dim = store.get_value(row, 2) + return row_dim == self._current_dim + + def update_dims(self): + """Update the list of dimensions shown""" + for dim in self.dim_names: + if not self.get_dimension(dim): + self.dimstore.insert_after(None, (dim,)) + + def add_selection(self, dataset, selection): + di = self.get_dataset_iter(dataset) + values = (selection.title, selection, selection.dims()[0]) + self.selstore.insert_after(di, None, values) + + def add_dataset(self, dataset): + di = self.get_dataset_iter(dataset) + if not di: + values = (dataset.get_name(), dataset, dataset.get_dim_name(0)) + i = self.selstore.insert_after(None, None, values) + for selection in dataset.as_selections(): + values = (selection.title, selection, dataset.get_dim_name(0)) + self.selstore.insert_after(i, None, values) + + def selection_changed(self, selection): + """Callback function from Project.""" + print 'selection changed' + self.update_dims() + + def dataset_changed(self): + """Callback function from Project.""" + print 'dataset changed' + self.update_dims() + + ## GTK Callbacks. + def _dim_cursor_changed(self, widget): + cursor = self.dimlist.get_cursor()[0] + i = self.dimstore.get_iter(cursor) + row = self.dimstore.get_value(i, 0) + self.set_dimension(row) + + def _dim_row_activated(self, widget, path, column): + self.seltree.grab_focus() + + def _sel_cursor_changed(self, widget): + "Show the list of identifier strings." + p = self.seltree.get_cursor()[0] + i = self.selstore.get_iter(p) + obj = self.selstore.get_value(i, 1) + + id_list = [] + if isinstance(obj, dataset.Selection): + id_list = obj[self._current_dim] + id_list.sort() + + self.idstore.clear() + for e in id_list: + self.idstore.append((e,)) + + def _sel_row_activated(self, widget, path, column): + pass + + def set_dimension(self, dimname): + self._current_dim = dimname + self.selstore_filter.refilter() + + dim = self.get_dimension(self._current_dim) + path = self.dimstore.get_path(dim) + + if self.dimlist.get_cursor()[0] != path: + self.dimlist.set_cursor(self.dimstore.get_path(dim)) + + def drag_data_received(self, widget, drag_context, x, y, + selection, info, timestamp): + + treestore, path = selection.tree_get_row_drag_data() + i = treestore.get_iter(path) + obj = treestore.get_value(i, 2) + if isinstance(obj, dataset.CategoryDataset): + self.add_dataset(obj) + self.set_dimension(obj.get_dim_name(0)) + + class SelectionTree(gtk.TreeView): + """Shows a list of CategoryDatasets for the selected dimension.""" + + def __init__(self): + self.store = gtk.TreeStore(gobject.TYPE_STRING, + gobject.TYPE_PYOBJECT, + gobject.TYPE_STRING) + + # Set up filter that shows only the selected dimension + self.filter = self.store.filter_new() + gtk.TreeView.__init__(self, self.filter) + self.filter.set_visible_func(self.dimension_filter) + + renderer = gtk.CellRendererText() + sel_column = gtk.TreeViewColumn('Selection', renderer, text=0) + self.insert_column(sel_column, 0) + + self.connect('row-activated', self._on_row_activated) + self.connect('cursor-changed', self._on_cursor_changed) + + 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.drag_data_received) + + self._identifier_list = None + + self.set_headers_visible(True) + self._dimension = None + + + def set_identifier_list(self, widget): + self._identifier_list = widget + + def set_project(self, project): + """Dependency injection. Set the current project.""" + self.project = project + project.add_selection_observer(self) + project.add_dataset_observer(self) + self.add_selection(None, project.get_selection()) + + def get_dataset_iter(self, dataset): + i = self.store.get_iter_first() + while i: + if self.store.get_value(i, 1) == dataset: + return i + i = self.store.iter_next(i) + return None + + def add_selection(self, dataset, selection): + if dataset == None: + return + di = self.get_dataset_iter(dataset) + self.store.insert_after(di, None, (selection.title, + selection, + dataset.dims()[0])) + + def add_dataset(self, dataset): + di = self.get_dataset_iter(dataset) + if not di: + i = self.store.insert_after(None, None, (dataset.get_name(), + dataset, + dataset.get_dim_name(0))) + for selection in dataset.as_selections(): + self.store.insert_after(i, None, (selection.title, + selection, + dataset.get_dim_name(0))) + + + + def dimension_filter(self, store, i): + """Filters out everything but the selected dimension.""" + i_dim = store.get_value(i, 2) + return i_dim == self._dimension + + def set_dimension(self, dimension): + self._dimension = dimension + self.filter.refilter() + + def dataset_changed(self): + pass + + def selection_changed(self, selection): + pass + + ## GTK callbacks + def _on_row_activated(self, treeview, path, column): + i = self.store.get_iter(path) + obj = self.store.get_value(i, 1) + if isinstance(obj, dataset.Dataset): + self.expand_row(path, True) + + def _on_cursor_changed(self, treeview): + p = self.get_cursor()[0] + self.scroll_to_cell(p) + i = self.store.get_iter(p) + obj = self.store.get_value(i, 1) + id_list = [] + if isinstance(obj, dataset.Selection): + id_list = obj[self._dimension] + + self._identifier_list.set_identifiers(id_list) + + def 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, dataset.CategoryDataset): + self.add_dataset(obj) + self.set_dimension(obj.get_dim_name(0)) + + + +class IdentifierList(gtk.TreeView): + + def __init__(self): + self.store = gtk.ListStore(gobject.TYPE_STRING) + gtk.TreeView.__init__(self, self.store) + self.set_headers_visible(True) + + # Set up identifier column to show active identifiers + renderer = gtk.CellRendererText() + ids_column = gtk.TreeViewColumn('Identifiers', renderer, text=0) + self.insert_column(ids_column, 0) + + def set_identifiers(self, identifiers): + "Show the list of identifier strings." + self.store.clear() + id_list = identifiers + id_list.sort() + for e in id_list: + self.store.append((e,)) + +class OldSelectionTree(gtk.TreeView): def __init__(self): self.store = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT) @@ -78,8 +387,8 @@ class SelectionTree(gtk.TreeView): return None while children: - if self.store.get(children, 1)[0] == selection: - return self.store.get(children, 1)[0] + if self.store.get_value(children, 1) == selection: + return self.store.get_value(children, 1) children = self.store.iter_next(children) return None @@ -139,23 +448,71 @@ class SelectionTree(gtk.TreeView): def _on_popup_menu(self): pass -class IdentifierList(gtk.TreeView): - +class DimList(gtk.TreeView): + """A widget that displays the list of dimensions in the program.""" + def __init__(self): self.store = gtk.ListStore(gobject.TYPE_STRING) gtk.TreeView.__init__(self, self.store) - self.set_headers_visible(True) - # Set up identifier column to show active identifiers renderer = gtk.CellRendererText() - ids_column = gtk.TreeViewColumn('Identifiers', renderer, text=0) - self.insert_column(ids_column, 0) + dim_column = gtk.TreeViewColumn('Dimension', renderer, text=0) + self.insert_column(dim_column, 0) + self._selection_list = None - def set_identifiers(self, identifiers): - "Show the list of identifier strings." - self.store.clear() - id_list = list(identifiers) - id_list.sort() - for e in id_list: - self.store.append((e,)) + self.connect('row-activated', self._on_row_activated) + self.connect('cursor-changed', self._on_cursor_changed) + + def set_project(self, project): + """Dependency injection. The widget list needs the project, but + there is not necessarily a current project when the application + is started.""" + + self.dim_names = project.dim_names + self.update_dims() + project.add_selection_observer(self) + project.add_dataset_observer(self) + + def set_selection_tree(self, selection_tree): + """Dependency injection. It is not known whether the selection tree + or the dimension list is created first.""" + self.selection_tree = selection_tree + + def get_dimension(self, dim): + """Returns the iterator to the dimension with the given name, or + None if not found.""" + + i = self.store.get_iter_first() + while i: + if self.store.get(i, 0)[0] == dim: + return i + i = self.store.iter_next(i) + return None + + def update_dims(self): + """Update the list of dimensions shown""" + for dim in self.dim_names: + if not self.get_dimension(dim): + self.store.insert_after(None, (dim,)) + + def selection_changed(self, selection): + """Callback function from Project.""" + self.update_dims() + + def dataset_changed(self): + """Callback function from Project.""" + self.update_dims() + + ## GTK Callbacks. + def _on_cursor_changed(self, widget): + cursor = self.get_cursor()[0] + print cursor + i = self.store.get_iter(cursor) + row = self.store.get_value(i, 0) + self.selection_tree.set_dimension(row) + self.scroll_to_cell(cursor) + + + def _on_row_activated(self, widget, path, column): + self.selection_tree.grab_focus() diff --git a/workflows/test_workflow.py b/workflows/test_workflow.py index 4ae0173..5fe6566 100644 --- a/workflows/test_workflow.py +++ b/workflows/test_workflow.py @@ -2,7 +2,7 @@ import gtk from system import dataset, logger, plots, workflow #import geneontology #import gostat -from scipy import array,randn,log +from scipy import array, randn, log, ones import cPickle import networkx @@ -43,6 +43,7 @@ class TestWorkflow (workflow.Workflow): save.add_function(DatasetSaveFunction()) self.add_stage(save) + class LoadAnnotationsFunction(workflow.Function): def __init__(self): @@ -112,9 +113,10 @@ class TestDataFunction(workflow.Function): graph.add_edge(x, y, 3) ds = dataset.GraphDataset(array(networkx.adj_matrix(graph))) ds_plot = plots.NetworkPlot(ds) - + + cds = dataset.CategoryDataset(ones([3, 3])) ds_scatter = plots.ScatterMarkerPlot(ds, ds, 'rows_0', 'rows_0', '0_1', '0_2') - return [X, ds, p, ds_plot, ds_scatter,p2] + return [X, ds, p, ds_plot, ds_scatter, p2, cds] class SelectFunction(workflow.Function): def __init__(self):