package flare.analytics.graph { import flare.animate.Transitioner; import flare.util.Property; import flare.vis.data.Data; import flare.vis.data.EdgeSprite; import flare.vis.data.NodeSprite; import flare.vis.operator.Operator; /** * Calculates the link distance from a source node based on a breadth-first * traversal. Link distance is calculated as the smallest number of edges * connecting a node to a source node. Any edge weights are ignored, to * include weighted edges in the calculation, use the * ShortestPaths operator instead. * *

Nodes are annotated with both the computed distance and the incoming * edge in the shortest link distance path to a source node. Edges are * annotated with a Boolean value indicating whether or not the edge lies * along one of these computed shortest paths.

*/ public class LinkDistance extends Operator { private var _d:Property = Property.$("props.distance"); private var _p:Property = Property.$("props.incoming"); private var _e:Property = Property.$("props.onpath"); private var _links:int = NodeSprite.GRAPH_LINKS; /** The roots of the breadth-first search. The elements of the array * should either be NodeSprite instances or integer indices into * the node array. */ public var sources:Array; /** The property in which to store the link distance. This property * is used to annotate nodes with the minimum link distance to one of * the source nodes. The default value is "props.distance". */ public function get distanceField():String { return _d.name; } public function set distanceField(f:String):void { _d = Property.$(f); } /** The property in which to store incoming edges along a shortest * path. This property is used to annotate nodes with the incoming * along a shortest path from one of the source nodes. By following * sequential incoming edges, one can recreate the shortest path from * the nearest source node. The default value is "props.incoming". */ public function get incomingField():String { return _p.name; } public function set incomingField(f:String):void { _p = Property.$(f); } /** The property in which to store a path inclusion flag for edges. * This property is used to mark edges as belonging to one of the * computed shortest paths: true indicates that the edge * participates in a shortest path, false indicates that * the edge does not lie along a shortest path. The default value is * "props.onpath". */ public function get onpathField():String { return _p.name; } public function set onpathField(f:String):void { _p = Property.$(f); } /** The link type to consider when calculating link distance. 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); } } // -------------------------------------------------------------------- /** * Creates a new LinkDistance operator. * @param sources an Array specifying the roots of the breadth-first * search. The elements of the array should either be * NodeSprite instances or integer indices into the node * array. */ public function LinkDistance(sources:Array=null) { this.sources = sources; } /** @inheritDoc */ public override function operate(t:Transitioner=null):void { calculate(visualization.data, sources); } /** * Calculates link distances from a set of source nodes for for each * node in the graph. Each node in the graph will be annotated with * its link distance from the nearest source node. * @param data the graph to calculate distances for * @param sources one or more source nodes from which to measure the * distance. This input can either be a single node or an array of * nodes. Nodes can be indicated as either a NodeSprite * instance or an integer index into the data.nodes * property. */ public function calculate(data:Data, sources:*):void { var i:int, n:NodeSprite; data.edges.setProperty(_e.name, false); data.nodes.visit(function(n:NodeSprite):void { _d.setValue(n, Number.POSITIVE_INFINITY); _p.setValue(n, null); }); // initialize queue var roots:Array = sources is Array ? sources as Array : [sources]; var queue:Array = []; for (i=0; i 0) { var u:NodeSprite = queue.shift(); var du:int = _d.getValue(u) + 1; u.visitEdges(function(e:EdgeSprite):void { var v:NodeSprite = e.other(u); var d:Number = _d.getValue(v); if (!isFinite(d)) { queue.push(v); _d.setValue(v, du); _p.setValue(v, e); } else if (d > du) { _d.setValue(v, du); _p.setValue(v, e); } }, _links); } data.nodes.visit(function(n:NodeSprite):void { var e:EdgeSprite = _p.getValue(n); if (e) _e.setValue(e, true); }); } } // end of class LinkDistance }