toolkit/javascript/d3/src/layout/force.js
changeset 47 c0b4a8b5a012
equal deleted inserted replaced
46:efd9c589177a 47:c0b4a8b5a012
       
     1 // A rudimentary force layout using Gauss-Seidel.
       
     2 d3.layout.force = function() {
       
     3   var force = {},
       
     4       event = d3.dispatch("tick"),
       
     5       size = [1, 1],
       
     6       drag,
       
     7       alpha,
       
     8       friction = .9,
       
     9       linkDistance = d3_layout_forceLinkDistance,
       
    10       linkStrength = d3_layout_forceLinkStrength,
       
    11       charge = -30,
       
    12       gravity = .1,
       
    13       theta = .8,
       
    14       interval,
       
    15       nodes = [],
       
    16       links = [],
       
    17       distances,
       
    18       strengths,
       
    19       charges;
       
    20 
       
    21   function repulse(node) {
       
    22     return function(quad, x1, y1, x2, y2) {
       
    23       if (quad.point !== node) {
       
    24         var dx = quad.cx - node.x,
       
    25             dy = quad.cy - node.y,
       
    26             dn = 1 / Math.sqrt(dx * dx + dy * dy);
       
    27 
       
    28         /* Barnes-Hut criterion. */
       
    29         if ((x2 - x1) * dn < theta) {
       
    30           var k = quad.charge * dn * dn;
       
    31           node.px -= dx * k;
       
    32           node.py -= dy * k;
       
    33           return true;
       
    34         }
       
    35 
       
    36         if (quad.point && isFinite(dn)) {
       
    37           var k = quad.pointCharge * dn * dn;
       
    38           node.px -= dx * k;
       
    39           node.py -= dy * k;
       
    40         }
       
    41       }
       
    42       return !quad.charge;
       
    43     };
       
    44   }
       
    45 
       
    46   function tick() {
       
    47     var n = nodes.length,
       
    48         m = links.length,
       
    49         q,
       
    50         i, // current index
       
    51         o, // current object
       
    52         s, // current source
       
    53         t, // current target
       
    54         l, // current distance
       
    55         k, // current force
       
    56         x, // x-distance
       
    57         y; // y-distance
       
    58 
       
    59     // gauss-seidel relaxation for links
       
    60     for (i = 0; i < m; ++i) {
       
    61       o = links[i];
       
    62       s = o.source;
       
    63       t = o.target;
       
    64       x = t.x - s.x;
       
    65       y = t.y - s.y;
       
    66       if (l = (x * x + y * y)) {
       
    67         l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l;
       
    68         x *= l;
       
    69         y *= l;
       
    70         t.x -= x * (k = s.weight / (t.weight + s.weight));
       
    71         t.y -= y * k;
       
    72         s.x += x * (k = 1 - k);
       
    73         s.y += y * k;
       
    74       }
       
    75     }
       
    76 
       
    77     // apply gravity forces
       
    78     if (k = alpha * gravity) {
       
    79       x = size[0] / 2;
       
    80       y = size[1] / 2;
       
    81       i = -1; if (k) while (++i < n) {
       
    82         o = nodes[i];
       
    83         o.x += (x - o.x) * k;
       
    84         o.y += (y - o.y) * k;
       
    85       }
       
    86     }
       
    87 
       
    88     // compute quadtree center of mass and apply charge forces
       
    89     if (charge) {
       
    90       d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges);
       
    91       i = -1; while (++i < n) {
       
    92         if (!(o = nodes[i]).fixed) {
       
    93           q.visit(repulse(o));
       
    94         }
       
    95       }
       
    96     }
       
    97 
       
    98     // position verlet integration
       
    99     i = -1; while (++i < n) {
       
   100       o = nodes[i];
       
   101       if (o.fixed) {
       
   102         o.x = o.px;
       
   103         o.y = o.py;
       
   104       } else {
       
   105         o.x -= (o.px - (o.px = o.x)) * friction;
       
   106         o.y -= (o.py - (o.py = o.y)) * friction;
       
   107       }
       
   108     }
       
   109 
       
   110     event.tick({type: "tick", alpha: alpha});
       
   111 
       
   112     // simulated annealing, basically
       
   113     return (alpha *= .99) < .005;
       
   114   }
       
   115 
       
   116   force.on = function(type, listener) {
       
   117     event.on(type, listener);
       
   118     return force;
       
   119   };
       
   120 
       
   121   force.nodes = function(x) {
       
   122     if (!arguments.length) return nodes;
       
   123     nodes = x;
       
   124     return force;
       
   125   };
       
   126 
       
   127   force.links = function(x) {
       
   128     if (!arguments.length) return links;
       
   129     links = x;
       
   130     return force;
       
   131   };
       
   132 
       
   133   force.size = function(x) {
       
   134     if (!arguments.length) return size;
       
   135     size = x;
       
   136     return force;
       
   137   };
       
   138 
       
   139   force.linkDistance = function(x) {
       
   140     if (!arguments.length) return linkDistance;
       
   141     linkDistance = d3.functor(x);
       
   142     return force;
       
   143   };
       
   144 
       
   145   // For backwards-compatibility.
       
   146   force.distance = force.linkDistance;
       
   147 
       
   148   force.linkStrength = function(x) {
       
   149     if (!arguments.length) return linkStrength;
       
   150     linkStrength = d3.functor(x);
       
   151     return force;
       
   152   };
       
   153 
       
   154   force.friction = function(x) {
       
   155     if (!arguments.length) return friction;
       
   156     friction = x;
       
   157     return force;
       
   158   };
       
   159 
       
   160   force.charge = function(x) {
       
   161     if (!arguments.length) return charge;
       
   162     charge = typeof x === "function" ? x : +x;
       
   163     return force;
       
   164   };
       
   165 
       
   166   force.gravity = function(x) {
       
   167     if (!arguments.length) return gravity;
       
   168     gravity = x;
       
   169     return force;
       
   170   };
       
   171 
       
   172   force.theta = function(x) {
       
   173     if (!arguments.length) return theta;
       
   174     theta = x;
       
   175     return force;
       
   176   };
       
   177 
       
   178   force.start = function() {
       
   179     var i,
       
   180         j,
       
   181         n = nodes.length,
       
   182         m = links.length,
       
   183         w = size[0],
       
   184         h = size[1],
       
   185         neighbors,
       
   186         o;
       
   187 
       
   188     for (i = 0; i < n; ++i) {
       
   189       (o = nodes[i]).index = i;
       
   190       o.weight = 0;
       
   191     }
       
   192 
       
   193     distances = [];
       
   194     strengths = [];
       
   195     for (i = 0; i < m; ++i) {
       
   196       o = links[i];
       
   197       if (typeof o.source == "number") o.source = nodes[o.source];
       
   198       if (typeof o.target == "number") o.target = nodes[o.target];
       
   199       distances[i] = linkDistance.call(this, o, i);
       
   200       strengths[i] = linkStrength.call(this, o, i);
       
   201       ++o.source.weight;
       
   202       ++o.target.weight;
       
   203     }
       
   204 
       
   205     for (i = 0; i < n; ++i) {
       
   206       o = nodes[i];
       
   207       if (isNaN(o.x)) o.x = position("x", w);
       
   208       if (isNaN(o.y)) o.y = position("y", h);
       
   209       if (isNaN(o.px)) o.px = o.x;
       
   210       if (isNaN(o.py)) o.py = o.y;
       
   211     }
       
   212 
       
   213     charges = [];
       
   214     if (typeof charge === "function") {
       
   215       for (i = 0; i < n; ++i) {
       
   216         charges[i] = +charge.call(this, nodes[i], i);
       
   217       }
       
   218     } else {
       
   219       for (i = 0; i < n; ++i) {
       
   220         charges[i] = charge;
       
   221       }
       
   222     }
       
   223 
       
   224     // initialize node position based on first neighbor
       
   225     function position(dimension, size) {
       
   226       var neighbors = neighbor(i),
       
   227           j = -1,
       
   228           m = neighbors.length,
       
   229           x;
       
   230       while (++j < m) if (!isNaN(x = neighbors[j][dimension])) return x;
       
   231       return Math.random() * size;
       
   232     }
       
   233 
       
   234     // initialize neighbors lazily
       
   235     function neighbor() {
       
   236       if (!neighbors) {
       
   237         neighbors = [];
       
   238         for (j = 0; j < n; ++j) {
       
   239           neighbors[j] = [];
       
   240         }
       
   241         for (j = 0; j < m; ++j) {
       
   242           var o = links[j];
       
   243           neighbors[o.source.index].push(o.target);
       
   244           neighbors[o.target.index].push(o.source);
       
   245         }
       
   246       }
       
   247       return neighbors[i];
       
   248     }
       
   249 
       
   250     return force.resume();
       
   251   };
       
   252 
       
   253   force.resume = function() {
       
   254     alpha = .1;
       
   255     d3.timer(tick);
       
   256     return force;
       
   257   };
       
   258 
       
   259   force.stop = function() {
       
   260     alpha = 0;
       
   261     return force;
       
   262   };
       
   263 
       
   264   // use `node.call(force.drag)` to make nodes draggable
       
   265   force.drag = function() {
       
   266     if (!drag) drag = d3.behavior.drag()
       
   267         .on("dragstart", dragstart)
       
   268         .on("drag", d3_layout_forceDrag)
       
   269         .on("dragend", d3_layout_forceDragEnd);
       
   270 
       
   271     this.on("mouseover.force", d3_layout_forceDragOver)
       
   272         .on("mouseout.force", d3_layout_forceDragOut)
       
   273         .call(drag);
       
   274   };
       
   275 
       
   276   function dragstart(d) {
       
   277     d3_layout_forceDragOver(d3_layout_forceDragNode = d);
       
   278     d3_layout_forceDragForce = force;
       
   279   }
       
   280 
       
   281   return force;
       
   282 };
       
   283 
       
   284 var d3_layout_forceDragForce,
       
   285     d3_layout_forceDragNode;
       
   286 
       
   287 function d3_layout_forceDragOver(d) {
       
   288   d.fixed |= 2;
       
   289 }
       
   290 
       
   291 function d3_layout_forceDragOut(d) {
       
   292   if (d !== d3_layout_forceDragNode) d.fixed &= 1;
       
   293 }
       
   294 
       
   295 function d3_layout_forceDragEnd() {
       
   296   d3_layout_forceDrag();
       
   297   d3_layout_forceDragNode.fixed &= 1;
       
   298   d3_layout_forceDragForce = d3_layout_forceDragNode = null;
       
   299 }
       
   300 
       
   301 function d3_layout_forceDrag() {
       
   302   d3_layout_forceDragNode.px += d3.event.dx;
       
   303   d3_layout_forceDragNode.py += d3.event.dy;
       
   304   d3_layout_forceDragForce.resume(); // restart annealing
       
   305 }
       
   306 
       
   307 function d3_layout_forceAccumulate(quad, alpha, charges) {
       
   308   var cx = 0,
       
   309       cy = 0;
       
   310   quad.charge = 0;
       
   311   if (!quad.leaf) {
       
   312     var nodes = quad.nodes,
       
   313         n = nodes.length,
       
   314         i = -1,
       
   315         c;
       
   316     while (++i < n) {
       
   317       c = nodes[i];
       
   318       if (c == null) continue;
       
   319       d3_layout_forceAccumulate(c, alpha, charges);
       
   320       quad.charge += c.charge;
       
   321       cx += c.charge * c.cx;
       
   322       cy += c.charge * c.cy;
       
   323     }
       
   324   }
       
   325   if (quad.point) {
       
   326     // jitter internal nodes that are coincident
       
   327     if (!quad.leaf) {
       
   328       quad.point.x += Math.random() - .5;
       
   329       quad.point.y += Math.random() - .5;
       
   330     }
       
   331     var k = alpha * charges[quad.point.index];
       
   332     quad.charge += quad.pointCharge = k;
       
   333     cx += k * quad.point.x;
       
   334     cy += k * quad.point.y;
       
   335   }
       
   336   quad.cx = cx / quad.charge;
       
   337   quad.cy = cy / quad.charge;
       
   338 }
       
   339 
       
   340 function d3_layout_forceLinkDistance(link) {
       
   341   return 20;
       
   342 }
       
   343 
       
   344 function d3_layout_forceLinkStrength(link) {
       
   345   return 1;
       
   346 }