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.LinkedHashMap;
import java.util.Map;
import prefuse.action.Action;
import prefuse.activity.Activity;
import prefuse.activity.ActivityMap;
import prefuse.data.Graph;
import prefuse.data.Node;
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;
/**
* <p>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.</p>
*
* <p>The primary responsibility of the Visualization class is the creation
* of <em>visual abstractions</em> 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.</p>
*
* <p>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 <em>data group name
* </em>. 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.</p>
*
* <p>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)}.</p>
*
* <p>All of the examples discussed above are examples of <em>primary, or
* visual, data groups</em> of VisualItems. Visualizations also support
* <em>secondary, or focus data groups</em> 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.</p>
*
* <p>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.</p>
*
* <p>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.</p>
*
* @author <a href="http://jheer.org">jeffrey heer</a>
*/
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 LinkedHashMap();
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.");
}
}
protected void checkGroupExists(String group) {
if ( m_visual.containsKey(group) || m_focus.containsKey(group) ) {
throw new IllegalArgumentException(
"Group name \'"+group+"\' already in use");
}
}
protected void addDataGroup(String group, VisualTupleSet ts, TupleSet src) {
checkGroupExists(group);
m_visual.put(group, ts);
if ( src != null )
m_source.put(group, src);
}
// -- Tables --------------------------------------------------------------
/**
* 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);
addDataGroup(group, vt, null);
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);
addDataGroup(group, vt, null);
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);
addDataGroup(group, vt, 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);
addDataGroup(group, vt, table);
return vt;
}
/**
* Add a VisualTable to this visualization, using the table's
* pre-set group name. An exception will be thrown if the group
* name is already in use. This method allows you to insert custom
* implementations of VisualTable into a Visualization. It is intended
* for advanced users and should <b>NOT</b> be used if you do not know
* what you are doing. In almost all cases, one of the other add methods
* is preferred.
* @param table the pre-built VisualTable to add
* @return the added VisualTable
*/
public synchronized VisualTable addTable(VisualTable table) {
addDataGroup(table.getGroup(), table, table.getParentTable());
table.setVisualization(this);
return table;
}
// -- Graphs and Trees ----------------------------------------------------
/**
* 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)
{
checkGroupExists(group); // check before adding sub-tables
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);
addDataGroup(group, vg, 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)
{
checkGroupExists(group); // check before adding sub-tables
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);
addDataGroup(group, vt, 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;
}
// -- Aggregates ----------------------------------------------------------
/**
* 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);
addDataGroup(group, vat, null);
return vat;
}
// -- Derived Tables and Decorators ---------------------------------------
/**
* 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);
addDataGroup(group, vt, getSourceData(source));
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 <b>not</b> 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 <b>not</b> 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;
}
// -- Data Removal --------------------------------------------------------
/**
* 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<focus.length; ++j ) {
focus[j].removeTuple(item);
}
item.setValidated(false);
}
// remove data
if ( ts instanceof CompositeTupleSet ) {
CompositeTupleSet cts = (CompositeTupleSet)ts;
for ( Iterator names = cts.setNames(); names.hasNext(); ) {
String name = (String)names.next();
String subgroup = PrefuseLib.getGroupName(group,name);
m_visual.remove(subgroup);
m_source.remove(subgroup);
}
}
m_visual.remove(group);
m_source.remove(group);
return true;
}
/**
* Reset this visualization, clearing out all visualization tuples. All
* data sets added using the "addXXX" methods will be removed from the
* visualization. All registered focus groups added using the
* addFocusGroup() methods will be retained, but will be cleared of all
* tuples.
*/
public synchronized void reset() {
// first clear out all the focus groups
Iterator iter = m_focus.entrySet().iterator();
while ( iter.hasNext() ) {
Map.Entry entry = (Map.Entry)iter.next();
TupleSet ts = (TupleSet)entry.getValue();
ts.clear();
}
// finally clear out all map entries
m_visual.clear();
m_source.clear();
}
// ------------------------------------------------------------------------
// Groups
/**
* Get the source data TupleSet backing the given visual data group.
* @return the backing source data set, or null if there is no such
* data set
*/
public TupleSet getSourceData(String group) {
return (TupleSet)m_source.get(group);
}
/**
* Get the source data TupleSet backing the given visual tuple set.
* @return the backing source data set, or null if there is no such
* data set
*/
public TupleSet getSourceData(VisualTupleSet ts) {
return (TupleSet)m_source.get(ts.getGroup());
}
/**
* Get the Tuple from a backing source data set that corresponds most
* closely to the given VisualItem.
* @param item the VisualItem for which to retreive the source tuple
* @return the data source tuple, or null if no such tuple could
* be found
*/
public Tuple getSourceTuple(VisualItem item) {
// get the source group and tuple set, exit if none
String group = item.getGroup();
TupleSet source = getSourceData(group);
if ( source == null ) return null;
// first get the source table and row value
int row = item.getRow();
Table t = item.getTable();
while ( t instanceof VisualTable ) {
VisualTable vt = (VisualTable)t;
row = vt.getParentRow(row);
t = vt.getParentTable();
}
// now get the appropriate source tuple
// graphs maintain their own tuple managers so treat them specially
String cgroup = PrefuseLib.getChildGroup(group);
if ( cgroup != null ) {
String pgroup = PrefuseLib.getParentGroup(group);
Graph g = (Graph)getSourceData(pgroup);
if ( t == g.getNodeTable() ) {
return g.getNode(row);
} else {
return g.getEdge(row);
}
} else {
return t.getTuple(row);
}
}
/**
* Get the VisualItem associated with a source data tuple, if it exists.
* @param group the data group from which to lookup the source tuple,
* only primary visual groups are valid, focus groups will not work
* @param t the source data tuple
* @return the associated VisualItem from the given data group, or
* null if no such VisualItem exists
*/
public VisualItem getVisualItem(String group, Tuple t) {
TupleSet ts = getVisualGroup(group);
VisualTable vt;
if ( ts instanceof VisualTable ) {
vt = (VisualTable)ts;
} else if ( ts instanceof Graph ) {
Graph g = (Graph)ts;
vt = (VisualTable)(t instanceof Node ? g.getNodeTable()
: g.getEdgeTable());
} else {
return null;
}
int pr = t.getRow();
int cr = vt.getChildRow(pr);
return cr<0 ? null : vt.getItem(cr);
}
// ------------------------------------------------------------------------
/**
* Get the TupleSet associated with the given data group name.
* @param group a visual data group name
* @return the data group TupleSet
*/
public TupleSet getGroup(String group) {
TupleSet ts = getVisualGroup(group);
if ( ts == null )
ts = getFocusGroup(group);
return ts;
}
/**
* Indicates if a given VisualItem is contained in the given visual
* data group.
* @param item the VisualItem instance
* @param group the data group to check for containment
* @return true if the VisualItem is in the group, false otherwise
*/
public boolean isInGroup(VisualItem item, String group) {
if ( ALL_ITEMS.equals(group) )
return true;
if ( item.getGroup() == group )
return true;
TupleSet tset = getGroup(group);
return ( tset==null ? false : tset.containsTuple(item) );
}
/**
* Add a new secondary, or focus, group to this visualization. By
* default the added group is an instance of
* {@link prefuse.data.tuple.DefaultTupleSet}.
* @param group the name of the focus group to add
*/
public void addFocusGroup(String group) {
checkGroupExists(group);
m_focus.put(group, new DefaultTupleSet());
}
/**
* Add a new secondary, or focus, group to this visualization.
* @param group the name of the focus group to add
* @param tset the TupleSet for the focus group
*/
public void addFocusGroup(String group, TupleSet tset) {
checkGroupExists(group);
m_focus.put(group, tset);
}
// ------------------------------------------------------------------------
// VisualItems
/**
* Get the size of the given visual data group.
* @param group the visual data group
* @return the size (number of tuples) of the group
*/
public int size(String group) {
TupleSet tset = getGroup(group);
return ( tset==null ? 0 : tset.getTupleCount() );
}
/**
* Retrieve the visual data group of the given group name. Only primary
* visual groups will be considered.
* @param group the visual data group
* @return the requested data group, or null if not found
*/
public TupleSet getVisualGroup(String group) {
return (TupleSet)m_visual.get(group);
}
/**
* Retrieve the focus data group of the given group name. Only secondary,
* or focus, groups will be considered.
* @param group the focus data group
* @return the requested data group, or null if not found
*/
public TupleSet getFocusGroup(String group) {
return (TupleSet)m_focus.get(group);
}
/**
* Invalidate the bounds of all VisualItems in the given group. This
* will cause the bounds to be recomputed for all items upon the next
* redraw.
* @param group the visual data group to invalidate
*/
public void invalidate(String group) {
Iterator items = items(group, ValidatedPredicate.TRUE);
while ( items.hasNext() ) {
VisualItem item = (VisualItem)items.next();
item.setValidated(false);
}
}
/**
* Invalidate the bounds of all VisualItems in this visualization. This
* will cause the bounds to be recomputed for all items upon the next
* redraw.
*/
public void invalidateAll() {
invalidate(ALL_ITEMS);
}
/**
* Get an iterator over all visible items.
* @return an iterator over all visible items.
*/
public Iterator visibleItems() {
return items(VisiblePredicate.TRUE);
}
/**
* Get an iterator over all visible items in the specified group.
* @param group the visual data group name
* @return an iterator over all visible items in the specified group
*/
public Iterator visibleItems(String group) {
return items(group, VisiblePredicate.TRUE);
}
/**
* Get an iterator over all items, visible or not.
* @return an iterator over all items, visible or not.
*/
public Iterator items() {
return items((Predicate)null);
}
/**
* Get an iterator over all items which match the given
* Predicate filter.
* @param filter a Predicate indicating which items should be included
* in the iteration
* @return a filtered iterator over VisualItems
*/
public Iterator items(Predicate filter) {
int size = m_visual.size();
if ( size == 0 ) {
return Collections.EMPTY_LIST.iterator();
} else if ( size == 1 ) {
Iterator it = m_visual.keySet().iterator();
return items((String)it.next(), filter);
} else {
CompositeIterator iter = new CompositeIterator(m_visual.size());
Iterator it = m_visual.keySet().iterator();
for ( int i=0; it.hasNext(); ) {
String group = (String)it.next();
if ( !PrefuseLib.isChildGroup(group) )
iter.setIterator(i++, items(group, filter));
}
return iter;
}
}
/**
* Get an iterator over all items in the specified group.
* @param group the visual data group name
* @return an iterator over all items in the specified group.
*/
public Iterator items(String group) {
return items(group, (Predicate)null);
}
/**
* Get an iterator over all items in the given group which match the given
* filter expression.
* @param group the visual data group to iterate over
* @param expr an expression string that should parse to a Predicate
* indicating which items should be included in the iteration. The input
* string will be parsed using the
* {@link prefuse.data.expression.parser.ExpressionParser} class. If a
* parse error occurs, an empty iterator is returned.
* @return a filtered iterator over VisualItems
*/
public Iterator items(String group, String expr) {
Expression e = ExpressionParser.parse(expr);
if ( !(e instanceof Predicate) || ExpressionParser.getError()!=null )
return Collections.EMPTY_LIST.iterator();
return items(group, (Predicate)e);
}
/**
* Get an iterator over all items in the given group which match the given
* Predicate filter.
* @param group the visual data group to iterate over
* @param filter a Predicate indicating which items should be included in
* the iteration.
* @return a filtered iterator over VisualItems
*/
public Iterator items(String group, Predicate filter) {
if ( ALL_ITEMS.equals(group) )
return items(filter);
TupleSet t = getGroup(group);
return ( t==null ? Collections.EMPTY_LIST.iterator()
: t.tuples(filter) );
}
// ------------------------------------------------------------------------
// Batch Methods
/**
* Set a data field value for all items in a given data group matching a
* given filter predicate.
* @param group the visual data group name
* @param p the filter predicate determining which items to modify
* @param field the data field / column name to set
* @param val the value to set
*/
public void setValue(String group, Predicate p, String field, Object val) {
Iterator items = items(group, p);
while ( items.hasNext() ) {
VisualItem item = (VisualItem)items.next();
item.set(field, val);
}
}
/**
* Sets the visbility status for all items in a given data group matching
* a given filter predicate.
* @param group the visual data group name
* @param p the filter predicate determining which items to modify
* @param value the visibility value to set
*/
public void setVisible(String group, Predicate p, boolean value) {
Iterator items = items(group, p);
while ( items.hasNext() ) {
VisualItem item = (VisualItem)items.next();
item.setVisible(value);
}
}
/**
* Sets the interactivity status for all items in a given data group
* matching a given filter predicate.
* @param group the visual data group name
* @param p the filter predicate determining which items to modify
* @param value the interactivity value to set
*/
public void setInteractive(String group, Predicate p, boolean value) {
Iterator items = items(group, p);
while ( items.hasNext() ) {
VisualItem item = (VisualItem)items.next();
item.setInteractive(value);
}
}
// ------------------------------------------------------------------------
// Action Methods
/**
* Add a data processing Action to this Visualization. The Action will be
* updated to use this Visualization in its data processing.
* @param name the name of the Action
* @param action the Action to add
*/
public Action putAction(String name, Action action) {
action.setVisualization(this);
m_actions.put(name, action);
return action;
}
/**
* Get the data processing Action with the given name.
* @param name the name of the Action
* @return the requested Action, or null if the name was not found
*/
public Action getAction(String name) {
return (Action)m_actions.get(name);
}
/**
* Remove a data processing Action registered with this visualization.
* If the removed action is currently running, it will be canceled.
* The visualization reference held by the removed Action will be set to
* null.<br/>
* <strong>NOTE:</strong> Errors may occur if the removed Action is
* included in an "always run after" relation with another registered
* Action that has not been removed from this visualization. It is the
* currently the responsibility of clients to avoid this situation.
* @param name the name of the Action
* @return the removed Action, or null if no action was found
*/
public Action removeAction(String name) {
// TODO: create registry of always run after relations to automatically
// resolve action references?
Action a = getAction(name);
if ( a != null ) {
a.cancel();
m_actions.remove(name);
a.setVisualization(null);
}
return a;
}
/**
* Schedule the Action with the given name to run immediately. The running
* of all Actions is managed by the
* {@link prefuse.activity.ActivityManager}, which runs in a dedicated
* thread.
* @param action the name of the Action to run
* @return the Action scheduled to run
*/
public Activity run(String action) {
return m_actions.run(action);
}
/**
* Schedule the Action with the given name to run after the specified
* delay. The running of all Actions is managed by the
* {@link prefuse.activity.ActivityManager}, which runs in a dedicated
* thread.
* @param action the name of the Action to run
* @param delay the amount of time to wait, in milliseconds, before
* running the Action
* @return the Action scheduled to run
*/
public Activity runAfter(String action, long delay) {
return m_actions.runAt(action, System.currentTimeMillis()+delay);
}
/**
* Schedule the Action with the given name to run at the specified
* time. The running of all Actions is managed by the
* {@link prefuse.activity.ActivityManager}, which runs in a dedicated
* thread.
* @param action the name of the Action to run
* @param startTime the absolute system time, in milliseconds since the
* epoch, at which to run the Action.
* @return the Action scheduled to run
*/
public Activity runAt(String action, long startTime) {
return m_actions.runAt(action, startTime);
}
/**
* Schedule the Action with the given name to run after another Action
* finishes running. This relationship will only hold for one round of
* scheduling. If the "before" Action is run a second time, the "after"
* action will not be run a second time. The running of all Actions is
* managed by the {@link prefuse.activity.ActivityManager}, which runs
* in a dedicated thread.
* @param before the name of the Action to wait for
* @param after the name of the Action to run after the first one finishes
* @return the Action scheduled to run after the first one finishes
*/
public Activity runAfter(String before, String after) {
return m_actions.runAfter(before, after);
}
/**
* Schedule the Action with the given name to always run after another Action
* finishes running. The running of all Actions is managed by the
* {@link prefuse.activity.ActivityManager}, which runs in a dedicated
* thread.
* @param before the name of the Action to wait for
* @param after the name of the Action to run after the first one finishes
* @return the Action scheduled to always run after the first one finishes
*/
public Activity alwaysRunAfter(String before, String after) {
return m_actions.alwaysRunAfter(before, after);
}
/**
* Cancel the Action with the given name, if it has been scheduled.
* @param action the name of the Action to cancel
* @return the canceled Action
*/
public Activity cancel(String action) {
return m_actions.cancel(action);
}
// ------------------------------------------------------------------------
// Renderers
/**
* Set the RendererFactory used by this Visualization. The RendererFactory
* is responsible for providing the Renderer instances used to draw
* the VisualItems.
* @param rf the RendererFactory to use.
*/
public void setRendererFactory(RendererFactory rf) {
invalidateAll();
m_renderers = rf;
}
/**
* Get the RendererFactory used by this Visualization.
* @return this Visualization's RendererFactory
*/
public RendererFactory getRendererFactory() {
return m_renderers;
}
/**
* Get the renderer for the given item. Consults this visualization's
* {@link prefuse.render.RendererFactory} and returns the result.
* @param item the item to retreive the renderer for
* @return the {@link prefuse.render.Renderer} for drawing the
* given item
*/
public Renderer getRenderer(VisualItem item) {
if ( item.getVisualization() != this ) {
throw new IllegalArgumentException(
"Input item not a member of this visualization.");
}
return m_renderers.getRenderer(item);
}
/**
* Issue a repaint request, causing all displays associated with this
* visualization to be repainted.
*/
public synchronized void repaint() {
Iterator items = items(ValidatedPredicate.FALSE);
while ( items.hasNext() ) {
((VisualItem)items.next()).validateBounds();
}
for ( int i=0; i<m_displays.size(); ++i ) {
getDisplay(i).repaint();
}
}
/**
* Get the bounding rectangle for all items in the given group.
* @param group the visual data group
* @return the bounding box of the items
*/
public Rectangle2D getBounds(String group) {
return getBounds(group, new Rectangle2D.Double());
}
/**
* Get the bounding rectangle for all items in the given group.
* @param group the visual data group name
* @param r a rectangle in which to store the computed bounding box
* @return the input rectangle r, updated to hold the computed
* bounding box
*/
public Rectangle2D getBounds(String group, Rectangle2D r) {
Iterator iter = visibleItems(group);
if ( iter.hasNext() ) {
VisualItem item = (VisualItem)iter.next();
r.setRect(item.getBounds());
}
while ( iter.hasNext() ) {
VisualItem item = (VisualItem)iter.next();
Rectangle2D.union(item.getBounds(), r, r);
}
return r;
}
// ------------------------------------------------------------------------
// Displays
/**
* Get the number of displays associated with this visualization.
* @return the number of displays
*/
public int getDisplayCount() {
return m_displays.size();
}
/**
* Add a display to this visualization. Called automatically by the
* {@link prefuse.Display#setVisualization(Visualization)} method.
* @param display the Display to add
*/
void addDisplay(Display display) {
m_displays.add(display);
}
/**
* Get the display at the given list index. Displays are numbered by the
* order in which they are added to this visualization.
* @param idx the list index
* @return the Display at the given index
*/
public Display getDisplay(int idx) {
return (Display)m_displays.get(idx);
}
/**
* Remove a display from this visualization.
* @param display the display to remove
* @return true if the display was removed, false if it was not found
*/
boolean removeDisplay(Display display) {
return m_displays.remove(display);
}
/**
* Report damage to associated displays, indicating a region that will need
* to be redrawn.
* @param item the item responsible for the damage
* @param region the damaged region, in item-space coordinates
*/
public void damageReport(VisualItem item, Rectangle2D region) {
for ( int i=0; i<m_displays.size(); ++i ) {
Display d = getDisplay(i);
if ( d.getPredicate().getBoolean(item) ) {
d.damageReport(region);
}
}
}
} // end of class Visualization