package flare.vis.data { import flare.util.IEvaluable; import flare.util.Property; import flare.util.heap.FibonacciHeap; import flare.util.heap.HeapNode; import flash.utils.Dictionary; /** * Calculates a spanning tree for a graph structure. This class can * create spanning trees by breadth-first search, depth-first search, or by * computing a minimum spanning tree. The default is to find a minimum * spanning tree, which in turn defaults to breadth-first search if no edge * weight function is provided. * *
This class can annotate graph edges as belonging to the spanning tree
* (done if the annotateEdges
property is true), and can
* construct a Tree
instance (done if the
* buildTree
property is true). Generated
Tree
* instances are stored in the
tree
property. Generated trees
* contain the original nodes and edges in the input graph, and any
* previous parent or child links for input nodes will be cleared and
* overwritten.
This class is intended as a support class for creating spanning trees
* for flare.vis.data.Data
instances. To create annotated
* spanning trees for other purposes, see the
* flare.analytics.graph.SpanningTree
class, which provides a
* tree builder that can also be used a visualization operator.
spanningField
property will be set for
* each edge in the graph. */
public var annotateEdges:Boolean;
/** The tree created by this operator. */
public function get tree():Tree { return _tree; }
/** The traveral policy used to generate the spanning tree. Should be
* one of DEPTH_FIRST, BREADTH_FIRST, or MINIMUM_SPAN (default). */
public function get policy():String { return _policy; }
public function set policy(p:String):void {
if (p==DEPTH_FIRST || p==BREADTH_FIRST || p==MINIMUM_SPAN) {
_policy = p;
} else {
throw new Error("Unrecognized policy: "+p);
}
}
/** The property with which to annotate edges that make up the spanning
* tree. The default value is "props.spanning". This property is used
* to annotate edges as "true" (if in the spanning forest) or "false"
* (if not in the spanning forest). */
public function get spanningField():String { return _s.name; }
public function set spanningField(f:String):void { _s = Property.$(f); }
/** The root node for the spanning tree. */
public var root:NodeSprite;
/** The link type to consider when constructing a spanning tree. Should
* be one of NodeSprite.GRAPH_LINKS
,
* NodeSprite.IN_LINKS
, or
* NodeSprite.OUT_LINKS
. */
public function get links():int { return _links; }
public function set links(linkType:int):void {
if (linkType == NodeSprite.GRAPH_LINKS ||
linkType == NodeSprite.IN_LINKS ||
linkType == NodeSprite.OUT_LINKS)
{
_links = linkType;
} else {
throw new Error("Unsupported link type: "+linkType);
}
}
/** A function determining edge weights used in the spanning tree
* calculation. When setting this value, one can pass in either a
* Function, which should take an EdgeSprite as input and return a
* Number as output, or a String, in which case the string will be
* used as a property name from which to retrieve the edge weight
* value from an EdgeSprite instance. If the value is null (the
* default) all edges will be assumed to have weight 1.
*
* NOTE: Edge weights must be greater than or equal to zero! *
*/ public function get edgeWeight():Function { return _w; } public function set edgeWeight(w:*):void { if (w==null) { _w = null; } else if (w is String) { _w = Property.$(String(w)).getValue; } else if (w is IEvaluable) { _w = IEvaluable(w).eval; } else if (w is Function) { _w = w; } else { throw new Error("Unrecognized edgeWeight value. " + "The value should be a Function or String."); } } // -------------------------------------------------------------------- /** * Creates a new SpanningTree operator * @param policy the spanning tree creation policy. The default is *SpanningTree.MINIMUM_SPAN
* @param buildTree if true, this operator will build a new
* Tree
instance containing the spanning tree
* @param annotateEdges if true, this operator will annotate the
* edges of the original graph as belonging to the spanning tree
* @param root the root node from which to compute the spanning tree
* @param edgeWeight the edge weight values. This can either be a
* Function
that returns weight values or a
* String
providing the name of a property to look up on
* EdgeSprite
instances.
*/
public function TreeBuilder(policy:String=null,
buildTree:Boolean=true, annotateEdges:Boolean=false,
root:NodeSprite=null, edgeWeight:*=null)
{
if (policy) this.policy = policy;
this.buildTree = buildTree;
this.annotateEdges = annotateEdges;
this.root = root;
this.edgeWeight = edgeWeight;
}
/**
* Calculates the spanning tree.
* @param data the data set containing a graph
* @param n the root of the spanning tree
*/
public function calculate(data:Data, n:NodeSprite):void
{
var w:Function = edgeWeight;
if (n==null) { _tree = null; return; } // do nothing for null root
if (!buildTree && !annotateEdges) return; // nothing to do
// initialize
if (buildTree) {
data.nodes.visit(function(nn:NodeSprite):void {
nn.removeEdges(NodeSprite.TREE_LINKS);
});
_tree = new Tree();
_tree.root = n;
} else {
_tree = null;
}
if (annotateEdges) {
data.edges.setProperty(_s.name, false);
}
switch (_policy) {
case DEPTH_FIRST:
depthFirstTree(data, n);
return;
case BREADTH_FIRST:
breadthFirstTree(data, n);
return;
case MINIMUM_SPAN:
if (w==null) {
breadthFirstTree(data, n);
} else {
minimumSpanningTree(data, n, w);
}
return;
}
}
// -- Minimum Spanning Tree -------------------------------------------
private function minimumSpanningTree(data:Data, n:NodeSprite, w:Function):void
{
var hn:HeapNode, weight:Number, e:EdgeSprite;
_tree = null;
if (buildTree) {
_tree = new Tree();
_tree.root = n;
}
// initialize the heap
var heap:FibonacciHeap = new FibonacciHeap();
data.nodes.visit(function(nn:NodeSprite):void {
nn.props.heapNode = heap.insert(nn);
});
heap.decreaseKey(n.props.heapNode, 0);
// collect spanning tree edges (Prim's algorithm)
while (!heap.empty) {
hn = heap.removeMin();
n = hn.data as NodeSprite;
// add saved tree edge to spanning tree
if ((e=(n.props.treeEdge as EdgeSprite))) {
if (annotateEdges) _s.setValue(e, true);
if (buildTree) _tree.addChildEdge(e);
}
n.visitEdges(function(e:EdgeSprite):void {
var nn:NodeSprite = e.other(n);
var hnn:HeapNode = nn.props.heapNode;
weight = (w==null ? 1 : w(e));
if (hnn.inHeap && weight < hnn.key) {
nn.props.treeEdge = e; // set tree edge
heap.decreaseKey(hnn, weight);
}
}, _links);
}
// clean-up nodes
data.nodes.visit(function(nn:NodeSprite):void {
delete nn.props.treeEdge;
delete nn.props.heapNode;
});
}
// -- Breadth-First Traversal -----------------------------------------
private function breadthFirstTree(data:Data, n:NodeSprite):void
{
var visited:Dictionary = buildTree ? null : new Dictionary();
var q:Vector.