1 package flare.vis.operator.layout
3 import flare.animate.Transitioner;
4 import flare.physics.Particle;
5 import flare.physics.Simulation;
6 import flare.physics.Spring;
7 import flare.vis.data.Data;
8 import flare.vis.data.DataSprite;
9 import flare.vis.data.EdgeSprite;
10 import flare.vis.data.NodeSprite;
13 * Layout that positions graph items based on a physics simulation of
14 * interacting forces; by default, nodes repel each other, edges act as
15 * springs, and drag forces (similar to air resistance) are applied. This
16 * algorithm can be run for multiple iterations for a run-once layout
17 * computation or repeatedly run in an animated fashion for a dynamic and
18 * interactive layout (set <code>Visualization.continuousUpdates = true
21 * <p>The running time of this layout algorithm is the greater of O(N log N)
22 * and O(E), where N is the number of nodes and E the number of edges.
23 * The addition of custom forces to the simulation may affect this.</p>
25 * <p>The force directed layout is implemented using the physics simulator
26 * provided by the <code>flare.physics</code> package. The
27 * <code>Simulation</code> used to drive this layout can be set explicitly,
28 * allowing any number of custom force directed layouts to be created
29 * through the selection of <code>IForce</code> modules. Each node in the
30 * layout is mapped to a <code>Particle</code> instance and each edge
31 * to a <code>Spring</code> in the simulation. Once the simulation has been
32 * initialized, you can retrieve these instances through the
33 * <code>node.props.particle</code> and <code>edge.props.spring</code>
34 * properties, respectively.</p>
38 public class ForceDirectedLayout extends Layout
40 private var _sim:Simulation;
41 private var _step:Number = 1;
42 private var _iter:int = 1;
43 private var _gen:uint = 0;
44 private var _enforceBounds:Boolean = false;
46 // simulation defaults
47 private var _mass:Number = 1;
48 private var _restLength:Number = 30;
49 private var _tension:Number = 0.3;
50 private var _damping:Number = 0.1;
52 private var _t:Transitioner;
54 /** The default mass value for node/particles. */
55 public function get defaultParticleMass():Number { return _mass; }
56 public function set defaultParticleMass(v:Number):void { _mass = v; }
58 /** The default spring rest length for edge/springs. */
59 public function get defaultSpringLength():Number { return _restLength; }
60 public function set defaultSpringLength(v:Number):void { _restLength = v; }
62 /** The default spring tension for edge/springs. */
63 public function get defaultSpringTension():Number { return _tension; }
64 public function set defaultSpringTension(v:Number):void { _tension = v; }
66 /** The number of iterations to run the simulation per invocation
67 * (default is 1, expecting continuous updates). */
68 public function get iterations():int { return _iter; }
69 public function set iterations(iter:int):void { _iter = iter; }
71 /** The number of time ticks to advance the simulation on each
72 * iteration (default is 1). */
73 public function get ticksPerIteration():int { return _step; }
74 public function set ticksPerIteration(ticks:int):void { _step = ticks; }
76 /** The physics simulation driving this layout. */
77 public function get simulation():Simulation { return _sim; }
79 /** Flag indicating if the layout bounds should be enforced.
80 * If true, the layoutBounds will limit node placement. */
81 public function get enforceBounds():Boolean { return _enforceBounds; }
82 public function set enforceBounds(b:Boolean):void { _enforceBounds = b; }
84 // --------------------------------------------------------------------
87 * Creates a new ForceDirectedLayout.
88 * @param iterations the number of iterations to run the simulation
90 * @param sim the physics simulation to use for the layout. If null
91 * (the default), default simulation settings will be used
93 public function ForceDirectedLayout(enforceBounds:Boolean=false,
94 iterations:int=1, sim:Simulation=null)
96 _enforceBounds = enforceBounds;
98 _sim = (sim==null ? new Simulation(0, 0, 0.1, -10) : sim);
102 public override function operate(t:Transitioner=null):void
104 _t = (t!=null ? t : Transitioner.DEFAULT);
106 ++_gen; // update generation counter
107 init(); // populate simulation
110 _sim.bounds = _enforceBounds ? layoutBounds : null;
111 for (var i:uint=0; i<_iter; ++i) {
114 visualization.data.nodes.visit(update); // update positions
116 updateEdgePoints(_t);
120 // -- value transfer --------------------------------------------------
123 * Transfer the physics simulation results to an item's layout.
124 * @param d a DataSprite
125 * @return true, to signal a visitor to continue
127 protected function update(d:DataSprite):void
129 var p:Particle = d.props.particle;
131 var o:Object = _t.$(d);
137 // -- simulation management -------------------------------------------
140 * Initializes the Simulation for this ForceDirectedLayout
142 protected function init():void
144 var data:Data = visualization.data;
145 var p:Particle, s:Spring, n:NodeSprite, e:EdgeSprite;
147 // initialize all simulation entries
148 for each (n in data.nodes) {
149 p = n.props.particle;
151 n.props.particle = (p = _sim.addParticle(_mass, n.x, n.y));
160 for each (e in data.edges) {
163 e.props.spring = (s = _sim.addSpring(
164 e.source.props.particle, e.target.props.particle,
165 _restLength, _tension, _damping));
170 // set up simulation parameters
171 for each (n in data.nodes) {
172 p = n.props.particle;
175 for each (e in data.edges) {
177 s.restLength = restLength(e);
178 s.tension = tension(e);
179 s.damping = damping(e);
182 // clean-up unused items
183 for each (p in _sim.particles)
184 if (p.tag != _gen) p.kill();
185 for each (s in _sim.springs)
186 if (s.tag != _gen) s.kill();
190 * Function for assigning mass values to particles. By default, this
191 * simply returns the default mass value. This function can be replaced
192 * to perform custom mass assignment.
194 public var mass:Function = function(d:DataSprite):Number {
199 * Function for assigning rest length values to springs. By default,
200 * this simply returns the default rest length value. This function can
201 * be replaced to perform custom rest length assignment.
203 public var restLength:Function = function(e:EdgeSprite):Number {
208 * Function for assigning tension values to springs. By default, this
209 * method computes spring tension adaptively, based on the connectivity
210 * of the attached particles, to create more stable layouts. More
211 * specifically, the tension is computed as the default tension value
212 * divided by the square root of the maximum degree of the attached
213 * particles. This function can be replaced to perform custom tension
216 public var tension:Function = function(e:EdgeSprite):Number {
217 var s:Spring = Spring(e.props.spring);
218 var n:Number = Math.max(s.p1.degree, s.p2.degree);
219 return _tension / Math.sqrt(n);
223 * Function for assigning damping constant values to springs. By
224 * default, this simply uses the spring's computed tension value
225 * divided by 10. This function can be replaced to perform custom
226 * damping assignment.
228 public var damping:Function = function(e:EdgeSprite):Number {
229 return Spring(e.props.spring).tension / 10;
232 } // end of class ForceDirectedLayout