]> git.mjollnir.org Git - moodle.git/blob
944294c827a9371c02b5065fbb9e33872de80627
[moodle.git] /
1 package flare.vis.operator.layout
2 {
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;
11         
12         /**
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
19          * </code>).
20          * 
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>
24          * 
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>
35          * 
36          * @see flare.physics
37          */
38         public class ForceDirectedLayout extends Layout
39         {
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;
45                 
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;
51                 
52                 private var _t:Transitioner;
53                 
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; }
57                 
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; }
61                 
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; }
65                 
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; }
70                 
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; }
75                 
76                 /** The physics simulation driving this layout. */
77                 public function get simulation():Simulation { return _sim; }
78                 
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; }
83                 
84                 // --------------------------------------------------------------------
85                 
86                 /**
87                  * Creates a new ForceDirectedLayout.
88                  * @param iterations the number of iterations to run the simulation
89                  *  per invocation
90                  * @param sim the physics simulation to use for the layout. If null
91                  *  (the default), default simulation settings will be used
92                  */
93                 public function ForceDirectedLayout(enforceBounds:Boolean=false, 
94                         iterations:int=1, sim:Simulation=null)
95                 {
96                         _enforceBounds = enforceBounds;
97                         _iter = iterations;
98                         _sim = (sim==null ? new Simulation(0, 0, 0.1, -10) : sim);
99                 }
100                 
101                 /** @inheritDoc */
102                 public override function operate(t:Transitioner=null):void
103                 {
104                         _t = (t!=null ? t : Transitioner.DEFAULT);
105                         
106                         ++_gen; // update generation counter
107                         init(); // populate simulation
108                         
109                         // run simulation
110                         _sim.bounds = _enforceBounds ? layoutBounds : null;
111                         for (var i:uint=0; i<_iter; ++i) {
112                                 _sim.tick(_step);
113                         }
114                         visualization.data.nodes.visit(update); // update positions
115                         
116                         updateEdgePoints(_t);
117                         _t = null;
118                 }
119                 
120                 // -- value transfer --------------------------------------------------
121                 
122                 /**
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
126                  */
127                 protected function update(d:DataSprite):void
128                 {
129                         var p:Particle = d.props.particle;
130                         if (!p.fixed) {
131                                 var o:Object = _t.$(d);
132                                 o.x = p.x;
133                                 o.y = p.y;
134                         }
135                 }
136                 
137                 // -- simulation management -------------------------------------------
138                 
139                 /**
140                  * Initializes the Simulation for this ForceDirectedLayout
141                  */
142                 protected function init():void
143                 {
144                         var data:Data = visualization.data;
145                         var p:Particle, s:Spring, n:NodeSprite, e:EdgeSprite;
146                         
147                         // initialize all simulation entries
148                         for each (n in data.nodes) {
149                                 p = n.props.particle;
150                                 if (p == null) {
151                                         n.props.particle = (p = _sim.addParticle(_mass, n.x, n.y));
152                                         p.fixed = n.fixed;
153                                 } else {
154                                         p.x = n.x;
155                                         p.y = n.y;
156                                         p.fixed = n.fixed;
157                                 }
158                                 p.tag = _gen;
159                         }
160                         for each (e in data.edges) {
161                                 s = e.props.spring;
162                                 if (s == null) {
163                                         e.props.spring = (s = _sim.addSpring(
164                                                 e.source.props.particle, e.target.props.particle,
165                                                 _restLength, _tension, _damping));
166                                 }
167                                 s.tag = _gen;
168                         }
169                         
170                         // set up simulation parameters
171                         for each (n in data.nodes) {
172                                 p = n.props.particle;
173                                 p.mass = mass(n);
174                         }
175                         for each (e in data.edges) {
176                                 s = e.props.spring;
177                                 s.restLength = restLength(e);
178                                 s.tension = tension(e);
179                                 s.damping = damping(e);
180                         }
181                         
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();
187                 }
188                 
189                 /**
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.
193                  */
194                 public var mass:Function = function(d:DataSprite):Number {
195                         return _mass;
196                 }
197                 
198                 /**
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.
202                  */
203                 public var restLength:Function = function(e:EdgeSprite):Number {
204                         return _restLength;
205                 }
206                 
207                 /**
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
214                  * assignment.
215                  */
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);
220                 }
221                 
222                 /**
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.
227                  */
228                 public var damping:Function = function(e:EdgeSprite):Number {
229                         return Spring(e.props.spring).tension / 10;
230                 }
231                 
232         } // end of class ForceDirectedLayout
233 }