package prefuse; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import prefuse.action.Action; import prefuse.activity.Activity; import prefuse.activity.ActivityMap; import prefuse.data.Graph; import prefuse.data.Schema; import prefuse.data.Table; import prefuse.data.Tree; import prefuse.data.Tuple; import prefuse.data.expression.Expression; import prefuse.data.expression.Predicate; import prefuse.data.expression.parser.ExpressionParser; import prefuse.data.tuple.CompositeTupleSet; import prefuse.data.tuple.DefaultTupleSet; import prefuse.data.tuple.TupleManager; import prefuse.data.tuple.TupleSet; import prefuse.render.DefaultRendererFactory; import prefuse.render.Renderer; import prefuse.render.RendererFactory; import prefuse.util.PrefuseConfig; import prefuse.util.PrefuseLib; import prefuse.util.collections.CompositeIterator; import prefuse.visual.AggregateTable; import prefuse.visual.VisualGraph; import prefuse.visual.VisualItem; import prefuse.visual.VisualTable; import prefuse.visual.VisualTree; import prefuse.visual.VisualTupleSet; import prefuse.visual.expression.ValidatedPredicate; import prefuse.visual.expression.VisiblePredicate; import prefuse.visual.tuple.TableDecoratorItem; import prefuse.visual.tuple.TableEdgeItem; import prefuse.visual.tuple.TableNodeItem; /** *

Central data structure representing an interactive Visualization. * This class is responsible for * managing the mappings between source data and onscreen VisualItems, * maintaining a list of {@link Display} instances responsible for rendering * of and interaction with the contents of this visualization, and * providing a collection of named Action instances for performing * data processing such as layout, animation, and size, shape, and color * assignment.

* *

The primary responsibility of the Visualization class is the creation * of visual abstractions of input data. Regardless of the data * structure (i.e., {@link prefuse.data.Table}, {@link prefuse.data.Graph}, * or {@link prefuse.data.Tree}), this class takes source data such as that * loaded from a file (see {@link prefuse.data.io}) or from a relational * database (see {@link prefuse.data.io.sql}) and creates a visual * representation of the data. These visual representations of the data are * data sets in their own right, providing access to the underlying source * data to be visualized while also adding addition data fields specific to a * visualization. These fields include spatial location (x, y * coordinates and item bounds), color (for stroke, fill, and text), size, * shape, and font. For a given input data set of type * {@link prefuse.data.Table}, {@link prefuse.data.Graph}, or * or {@link prefuse.data.Tree}, a corresponding instance of * {@link prefuse.visual.VisualTable}, {@link prefuse.visual.VisualGraph}, or * {@link prefuse.visual.VisualTree} is created and stored in the * visualization. These data types inherit the data values of the source * data (and indeed, manipulate it directly) while additionally providing * the aforementioned visual variables unique to that generated * visual abstraction. Similarly, all {@link prefuse.data.Tuple}, * {@link prefuse.data.Node}, or {@link prefuse.data.Edge} * instances used to represent an entry in the source data have a * corresponding {@link prefuse.visual.VisualItem}, * {@link prefuse.visual.NodeItem}, or {@link prefuse.visual.EdgeItem} * representing the interactive, visual realization of the backing data.

* *

The mapping of source data to a visual abstraction is accomplished * using {@link #add(String, TupleSet)} and the other "add" methods. These * methods will automatically create the visual abstraction, and store it * in this visualization, associating it with a provided data group name * . This group name allows for queries to this visualization that * consider only VisualItem instances from that particular group. This is * quite useful when crafting {@link prefuse.action.Action} instances that * process only a particular group of visual data. The Visualization class * provides mechanisms for querying any or all groups within the visualization, * using one or both of the group name or a filtering * {@link prefuse.data.expression.Predicate} to determine the items to * include (see {@link #items(Predicate)} for an examples). Source data * may be added multiple times to a Visualization under different group * names, allowing for multiple representations of the same backing data.

* *

Additionally, the Visualization class supports VisualItem instances * that are not directly grounded in backing source data. Examples include * {@link prefuse.visual.DecoratorItem} which "decorates" another pre-existing * VisualItem with a separate interactive visual object, and * {@link prefuse.visual.AggregateItem} which provides an interactive visual * representation of an aggregated of other VisualItems. Methods for adding * data groups of these kinds include {@link #addDecorators(String, String)} * and {@link #addAggregates(String)}.

* *

All of the examples discussed above are examples of primary, or * visual, data groups of VisualItems. Visualizations also support * secondary, or focus data groups that maintain additional * collections of the VisualItems stored in the primary groups. Examples * include a set of focus items (such as those that have been clicked * by the user), selected items (items selected by a user), or search * items (all matches to a search query). The exact semantics of these * groups and the mechanisms by which they are populated is determined by * application creators, but some defaults are provided. The Visualization * class includes some default group names, namely {@link #FOCUS_ITEMS}, * {@link #SELECTED_ITEMS}, and {@link #SEARCH_ITEMS} for the above * mentioned tasks. By default, both the {@link #FOCUS_ITEMS}, * {@link #SELECTED_ITEMS} focus groups are included in the Visualization, * represented using {@link prefuse.data.tuple.DefaultTupleSet} instances. * Also, some of the interactive controls provided by the * {@link prefuse.controls} package populate these sets by default. See * {@link prefuse.controls.FocusControl} for an example.

* *

Visualizations also maintain references to all the {@link Display} * instances providing interactive views of the content of this * visualization. {@link Display} instances registers themselves with * the visualization either in their constructor or through * the {@link Display#setVisualization(Visualization)} method, so they * do not otherwise need to be added manually. Displays can be configured * to show all or only a subset of the data in the Visualization. A * filtering {@link prefuse.data.expression.Predicate} can be used to * control what items are drawn by the displaying, including limiting * the Display to particular data groups (for example, using a * {@link prefuse.visual.expression.InGroupPredicate}). The Visualization's * {@link #repaint()} method will trigger a repaint on all Displays * associated with the visualization.

* *

Finally, the Visualization class provides a map of named * {@link prefuse.action.Action} instances that can be invoked to perform * processing on the VisualItems contained in the visualization. * Using the {@link #putAction(String, Action)} will add a named Action * to the visualization, registering the Action such that a reference * to this Visualization will be available within the scope of the * Action's {@link prefuse.action.Action#run(double)} method. Processing * Actions can later be invoked by name using the {@link #run(String)} * method and other similar methods. This functionality not only * provides a convenient means of organizing a Visualization-specific * collection of processing Actions, it also allows for a layer of indirection * between an Action and its name. This allows Actions to be dynamically * swapped at runtime. For example, an application may make a call to * invoke an Action named "layout", but the actual layout processing maybe * be dynamically swapped by changing the Action that corresponds to that * name. For more information on processing Actions, see the * {@link prefuse.action} packages and the top-level * {@link prefuse.action.Action} class.

* * @author jeffrey heer */ public class Visualization { /** Data group name for indicating all groups */ public static final String ALL_ITEMS = PrefuseConfig.get("visualization.allItems"); /** Default data group name for focus items */ public static final String FOCUS_ITEMS = PrefuseConfig.get("visualization.focusItems"); /** Default data group name for selected items */ public static final String SELECTED_ITEMS = PrefuseConfig.get("visualization.selectedItems"); /** Default data group name for search result items */ public static final String SEARCH_ITEMS = PrefuseConfig.get("visualization.searchItems"); // visual abstraction // filtered tables and groups private Map m_visual; private Map m_source; private Map m_focus; // actions private ActivityMap m_actions; // renderers private RendererFactory m_renderers; // displays private ArrayList m_displays; // ------------------------------------------------------------------------ // Constructor /** * Create a new, empty Visualization. Uses a DefaultRendererFactory. */ public Visualization() { m_actions = new ActivityMap(); m_renderers = new DefaultRendererFactory(); m_visual = new HashMap(); m_source = new HashMap(); m_focus = new HashMap(); m_displays = new ArrayList(); addFocusGroup(Visualization.FOCUS_ITEMS, new DefaultTupleSet()); addFocusGroup(Visualization.SELECTED_ITEMS, new DefaultTupleSet()); } // ------------------------------------------------------------------------ // Data Methods /** * Add a data set to this visualization, using the given data group name. * A visual abstraction of the data will be created and registered with * the visualization. An exception will be thrown if the group name is * already in use. * @param group the data group name for the visualized data * @param data the data to visualize * @return a visual abstraction of the input data, a VisualTupleSet * instance */ public synchronized VisualTupleSet add(String group, TupleSet data) { return add(group, data, null); } /** * Add a data set to this visualization, using the given data group name. * A visual abstraction of the data will be created and registered with * the visualization. An exception will be thrown if the group name is * already in use. * @param group the data group name for the visualized data * @param data the data to visualize * @param filter a filter Predicate determining which data Tuples in the * input data set are visualized * @return a visual abstraction of the input data, a VisualTupleSet * instance */ public synchronized VisualTupleSet add( String group, TupleSet data, Predicate filter) { if ( data instanceof Table ) { return addTable(group, (Table)data, filter); } else if ( data instanceof Tree ) { return addTree(group, (Tree)data, filter); } else if ( data instanceof Graph ) { return addGraph(group, (Graph)data, filter); } else { throw new IllegalArgumentException("Unsupported TupleSet type."); } } /** * Add an empty VisualTable to this visualization, using the given data * group name. This adds a group of VisualItems that do not have a * backing data set, useful for creating interactive visual objects * that do not represent data. An exception will be thrown if the group * name is already in use. * @param group the data group name for the visualized data * @return the added VisualTable */ public synchronized VisualTable addTable(String group) { VisualTable vt = new VisualTable(this, group); m_visual.put(group, vt); return vt; } /** * Add an empty VisualTable to this visualization, using the given data * group name and table schema. This adds a group of VisualItems that do * not have a backing data set, useful for creating interactive visual * objects that do not represent data. An exception will be thrown if the * group name is already in use. * @param group the data group name for the visualized data * @param schema the data schema to use for the VisualTable * @return the added VisualTable */ public synchronized VisualTable addTable(String group, Schema schema) { VisualTable vt = new VisualTable(this, group, schema); m_visual.put(group, vt); return vt; } /** * Adds a data table to this visualization, using the given data group * name. A visual abstraction of the data will be created and registered * with the visualization. An exception will be thrown if the group name * is already in use. * @param group the data group name for the visualized data * @param table the data table to visualize */ public synchronized VisualTable addTable(String group, Table table) { return addTable(group, table, (Predicate)null); } /** * Adds a data table to this visualization, using the given data group * name. A visual abstraction of the data will be created and registered * with the visualization. An exception will be thrown if the group name * is already in use. * @param group the data group name for the visualized data * @param table the data table to visualize * @param filter a filter Predicate determining which data Tuples in the * input table are visualized */ public synchronized VisualTable addTable( String group, Table table, Predicate filter) { VisualTable vt = new VisualTable(table, this, group, filter); m_visual.put(group, vt); m_source.put(group, table); return vt; } /** * Adds a data table to this visualization, using the given data group * name. A visual abstraction of the data will be created and registered * with the visualization. An exception will be thrown if the group name * is already in use. * @param group the data group name for the visualized data * @param table the data table to visualize * @param schema the data schema to use for the created VisualTable */ public synchronized VisualTable addTable( String group, Table table, Schema schema) { return addTable(group, table, null, schema); } /** * Adds a data table to this visualization, using the given data group * name. A visual abstraction of the data will be created and registered * with the visualization. An exception will be thrown if the group name * is already in use. * @param group the data group name for the visualized data * @param table the data table to visualize * @param filter a filter Predicate determining which data Tuples in the * input table are visualized * @param schema the data schema to use for the created VisualTable */ public synchronized VisualTable addTable( String group, Table table, Predicate filter, Schema schema) { VisualTable vt = new VisualTable(table, this, group, filter, schema); m_visual.put(group, vt); m_source.put(group, table); return vt; } // ------------------------------------------------------------------------ /** * Adds a graph to this visualization, using the given data group * name. A visual abstraction of the data will be created and registered * with the visualization. An exception will be thrown if the group name * is already in use. * @param group the data group name for the visualized graph. The nodes * and edges will be available in the "group.nodes" and "group.edges" * subgroups. * @param graph the graph to visualize */ public synchronized VisualGraph addGraph(String group, Graph graph) { return addGraph(group, graph, null); } /** * Adds a graph to this visualization, using the given data group * name. A visual abstraction of the data will be created and registered * with the visualization. An exception will be thrown if the group name * is already in use. * @param group the data group name for the visualized graph. The nodes * and edges will be available in the "group.nodes" and "group.edges" * subgroups. * @param graph the graph to visualize * @param filter a filter Predicate determining which data Tuples in the * input graph are visualized */ public synchronized VisualGraph addGraph( String group, Graph graph, Predicate filter) { return addGraph(group, graph, filter, VisualItem.SCHEMA, VisualItem.SCHEMA); } /** * Adds a graph to this visualization, using the given data group * name. A visual abstraction of the data will be created and registered * with the visualization. An exception will be thrown if the group name * is already in use. * @param group the data group name for the visualized graph. The nodes * and edges will be available in the "group.nodes" and "group.edges" * subgroups. * @param graph the graph to visualize * @param filter a filter Predicate determining which data Tuples in the * input graph are visualized * @param nodeSchema the data schema to use for the visual node table * @param edgeSchema the data schema to use for the visual edge table */ public synchronized VisualGraph addGraph(String group, Graph graph, Predicate filter, Schema nodeSchema, Schema edgeSchema) { String ngroup = PrefuseLib.getGroupName(group, Graph.NODES); String egroup = PrefuseLib.getGroupName(group, Graph.EDGES); VisualTable nt, et; nt = addTable(ngroup, graph.getNodeTable(), filter, nodeSchema); et = addTable(egroup, graph.getEdgeTable(), filter, edgeSchema); VisualGraph vg = new VisualGraph(nt, et, graph.isDirected(), graph.getNodeKeyField(), graph.getEdgeSourceField(), graph.getEdgeTargetField()); vg.setVisualization(this); vg.setGroup(group); m_visual.put(group, vg); m_source.put(group, graph); TupleManager ntm = new TupleManager(nt, vg, TableNodeItem.class); TupleManager etm = new TupleManager(et, vg, TableEdgeItem.class); nt.setTupleManager(ntm); et.setTupleManager(etm); vg.setTupleManagers(ntm, etm); return vg; } /** * Adds a tree to this visualization, using the given data group * name. A visual abstraction of the data will be created and registered * with the visualization. An exception will be thrown if the group name * is already in use. * @param group the data group name for the visualized tree. The nodes * and edges will be available in the "group.nodes" and "group.edges" * subgroups. * @param tree the tree to visualize */ public synchronized VisualTree addTree(String group, Tree tree) { return addTree(group, tree, null); } /** * Adds a tree to this visualization, using the given data group * name. A visual abstraction of the data will be created and registered * with the visualization. An exception will be thrown if the group name * is already in use. * @param group the data group name for the visualized tree. The nodes * and edges will be available in the "group.nodes" and "group.edges" * subgroups. * @param tree the tree to visualize * @param filter a filter Predicate determining which data Tuples in the * input graph are visualized */ public synchronized VisualTree addTree( String group, Tree tree, Predicate filter) { return addTree(group, tree, filter, VisualItem.SCHEMA, VisualItem.SCHEMA); } /** * Adds a tree to this visualization, using the given data group * name. A visual abstraction of the data will be created and registered * with the visualization. An exception will be thrown if the group name * is already in use. * @param group the data group name for the visualized tree. The nodes * and edges will be available in the "group.nodes" and "group.edges" * subgroups. * @param tree the tree to visualize * @param filter a filter Predicate determining which data Tuples in the * input graph are visualized * @param nodeSchema the data schema to use for the visual node table * @param edgeSchema the data schema to use for the visual edge table */ public synchronized VisualTree addTree(String group, Tree tree, Predicate filter, Schema nodeSchema, Schema edgeSchema) { String ngroup = PrefuseLib.getGroupName(group, Graph.NODES); String egroup = PrefuseLib.getGroupName(group, Graph.EDGES); VisualTable nt, et; nt = addTable(ngroup, tree.getNodeTable(), filter, nodeSchema); et = addTable(egroup, tree.getEdgeTable(), filter, edgeSchema); VisualTree vt = new VisualTree(nt, et, tree.getNodeKeyField(), tree.getEdgeSourceField(), tree.getEdgeTargetField()); vt.setVisualization(this); vt.setGroup(group); m_visual.put(group, vt); m_source.put(group, tree); TupleManager ntm = new TupleManager(nt, vt, TableNodeItem.class); TupleManager etm = new TupleManager(et, vt, TableEdgeItem.class); nt.setTupleManager(ntm); et.setTupleManager(etm); vt.setTupleManagers(ntm, etm); return vt; } /** * Add a group of aggregates to this visualization. Aggregates are * used to visually represent groups of VisualItems. * @param group the data group name for the aggregates. * @return the generated AggregateTable * @see prefuse.visual.AggregateTable */ public synchronized AggregateTable addAggregates(String group) { return addAggregates(group, VisualItem.SCHEMA); } /** * Add a group of aggregates to this visualization. Aggregates are * used to visually represent groups of VisualItems. * @param group the data group name for the aggregates. * @param schema the data schema to use for the AggregateTable * @return the generated AggregateTable * @see prefuse.visual.AggregateTable */ public synchronized AggregateTable addAggregates(String group, Schema schema) { AggregateTable vat = new AggregateTable(this, group, schema); m_visual.put(group, vat); return vat; } /** * Add a derived table, a VisualTable that is cascaded from an * existing VisualTable. This is useful for creating VisualItems * that inherit a set of visual properties from another group of * VisualItems. This might be used, for example, in the creation * of small multiples where only a few visual attributes vary * across the multiples. * @param group the data group to use for the derived table * @param source the source data group to derive from * @param filter a Predicate filter indicating which tuples of the * source group should be inheritable by the new group * @param override a data schema indicating which data fields * should not be inherited, but managed locally by the derived group * @return the derived VisualTable */ public synchronized VisualTable addDerivedTable( String group, String source, Predicate filter, Schema override) { VisualTable src = (VisualTable)getGroup(source); VisualTable vt = new VisualTable(src, this, group, filter, override); m_visual.put(group, vt); return vt; } /** * Add a group of decorators to an existing visual data group. Decorators * are VisualItem instances intended to "decorate" another VisualItem, * such as providing a label or dedicated interactive control, and are * realizeed as {@link prefuse.visual.DecoratorItem} instances that provide * access to the decorated item in addition to the standard VisualItem * properties. The generated table is created using the * {@link #addDerivedTable(String, String, Predicate, Schema)} method, * but with no VisualItem properties inherited from the source group. * @param group the data group to use for the decorators * @param source the source data group to decorate * @return the generated VisualTable of DecoratorItem instances */ public synchronized VisualTable addDecorators(String group,String source) { return addDecorators(group, source, (Predicate)null); } /** * Add a group of decorators to an existing visual data group. Decorators * are VisualItem instances intended to "decorate" another VisualItem, * such as providing a label or dedicated interactive control, and are * realizeed as {@link prefuse.visual.DecoratorItem} instances that provide * access to the decorated item in addition to the standard VisualItem * properties. * @param group the data group to use for the decorators * @param source the source data group to decorate * @param schema schema indicating which variables should not be * inherited from the source data group and instead be managed locally * by the generated VisualTable * @return the generated VisualTable of DecoratorItem instances */ public synchronized VisualTable addDecorators( String group, String source, Schema schema) { return addDecorators(group, source, null, schema); } /** * Add a group of decorators to an existing visual data group. Decorators * are VisualItem instances intended to "decorate" another VisualItem, * such as providing a label or dedicated interactive control, and are * realizeed as {@link prefuse.visual.DecoratorItem} instances that provide * access to the decorated item in addition to the standard VisualItem * properties. * @param group the data group to use for the decorators * @param source the source data group to decorate * @param filter a Predicate filter indicating which tuples of the * source group should be inheritable by the new group * @return the generated VisualTable of DecoratorItem instances */ public synchronized VisualTable addDecorators( String group, String source, Predicate filter) { VisualTable t = addDerivedTable(group,source,filter,VisualItem.SCHEMA); t.setTupleManager(new TupleManager(t, null, TableDecoratorItem.class)); return t; } /** * Add a group of decorators to an existing visual data group. Decorators * are VisualItem instances intended to "decorate" another VisualItem, * such as providing a label or dedicated interactive control, and are * realizeed as {@link prefuse.visual.DecoratorItem} instances that provide * access to the decorated item in addition to the standard VisualItem * properties. * @param group the data group to use for the decorators * @param source the source data group to decorate * @param filter a Predicate filter indicating which tuples of the * source group should be inheritable by the new group * @param schema schema indicating which variables should not be * inherited from the source data group and instead be managed locally * by the generated VisualTable * @return the generated VisualTable of DecoratorItem instances */ public synchronized VisualTable addDecorators( String group, String source, Predicate filter, Schema schema) { VisualTable t = addDerivedTable(group, source, filter, schema); t.setTupleManager(new TupleManager(t, null, TableDecoratorItem.class)); return t; } /** * Removes a data group from this Visualization. If the group is a focus * group, the group will simply be removed, and any subsequent attempts to * retrieve the group will return null. If the group is a primary group, it * will be removed, and any members of the group will also be removed * from any registered focus groups. * @param group the data group to remove * @return true if the group was found and removed, false if the group * was not found in this visualization. */ public synchronized boolean removeGroup(String group) { // check for focus group first TupleSet ts = getFocusGroup(group); if ( ts != null ) { // invalidate the item to reflect group membership change for ( Iterator items = ts.tuples(ValidatedPredicate.TRUE); items.hasNext(); ) { ((VisualItem)items.next()).setValidated(false); } ts.clear(); // trigger group removal callback m_focus.remove(group); return true; } // focus group not found, check for primary group ts = getVisualGroup(group); if ( ts == null ) { // exit with false if group not found return false; } // remove group members from focus sets and invalidate them TupleSet[] focus = new TupleSet[m_focus.size()]; m_focus.values().toArray(focus); for ( Iterator items = ts.tuples(); items.hasNext(); ) { VisualItem item = (VisualItem)items.next(); for ( int j=0; j