├── codeFlower.html ├── codeFlower.js ├── d3.geom.js ├── d3.js ├── d3.layout.js └── dataConverter.js /codeFlower.html: -------------------------------------------------------------------------------- 1 | 38 | 39 |
40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /codeFlower.js: -------------------------------------------------------------------------------- 1 | var CodeFlower = function(selector, w, h) { 2 | this.w = w; 3 | this.h = h; 4 | 5 | d3.select(selector).selectAll("svg").remove(); 6 | 7 | this.svg = d3.select(selector).append("svg:svg") 8 | .attr('width', w) 9 | .attr('height', h); 10 | 11 | this.svg.append("svg:rect") 12 | .style("stroke", "#999") 13 | .style("fill", "#fff") 14 | .attr('width', w) 15 | .attr('height', h); 16 | 17 | this.force = d3.layout.force() 18 | .on("tick", this.tick.bind(this)) 19 | .charge(function(d) { return d._children ? -d.size / 100 : -40; }) 20 | .linkDistance(function(d) { return d.target._children ? 80 : 25; }) 21 | .size([h, w]); 22 | }; 23 | 24 | CodeFlower.prototype.update = function(json) { 25 | if (json) this.json = json; 26 | 27 | this.json.fixed = true; 28 | this.json.x = this.w / 2; 29 | this.json.y = this.h / 2; 30 | 31 | var nodes = this.flatten(this.json); 32 | var links = d3.layout.tree().links(nodes); 33 | var total = nodes.length || 1; 34 | 35 | // remove existing text (will readd it afterwards to be sure it's on top) 36 | this.svg.selectAll("text").remove(); 37 | 38 | // Restart the force layout 39 | this.force 40 | .gravity(Math.atan(total / 50) / Math.PI * 0.4) 41 | .nodes(nodes) 42 | .links(links) 43 | .start(); 44 | 45 | // Update the links 46 | this.link = this.svg.selectAll("line.link") 47 | .data(links, function(d) { return d.target.name; }); 48 | 49 | // Enter any new links 50 | this.link.enter().insert("svg:line", ".node") 51 | .attr("class", "link") 52 | .attr("x1", function(d) { return d.source.x; }) 53 | .attr("y1", function(d) { return d.source.y; }) 54 | .attr("x2", function(d) { return d.target.x; }) 55 | .attr("y2", function(d) { return d.target.y; }); 56 | 57 | // Exit any old links. 58 | this.link.exit().remove(); 59 | 60 | // Update the nodes 61 | this.node = this.svg.selectAll("circle.node") 62 | .data(nodes, function(d) { return d.name; }) 63 | .classed("collapsed", function(d) { return d._children ? 1 : 0; }); 64 | 65 | this.node.transition() 66 | .attr("r", function(d) { return d.children ? 3.5 : Math.pow(d.size, 2/5) || 1; }); 67 | 68 | // Enter any new nodes 69 | this.node.enter().append('svg:circle') 70 | .attr("class", "node") 71 | .classed('directory', function(d) { return (d._children || d.children) ? 1 : 0; }) 72 | .attr("r", function(d) { return d.children ? 3.5 : Math.pow(d.size, 2/5) || 1; }) 73 | .style("fill", function color(d) { 74 | return "hsl(" + parseInt(360 / total * d.id, 10) + ",90%,70%)"; 75 | }) 76 | .call(this.force.drag) 77 | .on("click", this.click.bind(this)) 78 | .on("mouseover", this.mouseover.bind(this)) 79 | .on("mouseout", this.mouseout.bind(this)); 80 | 81 | // Exit any old nodes 82 | this.node.exit().remove(); 83 | 84 | this.text = this.svg.append('svg:text') 85 | .attr('class', 'nodetext') 86 | .attr('dy', 0) 87 | .attr('dx', 0) 88 | .attr('text-anchor', 'middle'); 89 | 90 | return this; 91 | }; 92 | 93 | CodeFlower.prototype.flatten = function(root) { 94 | var nodes = [], i = 0; 95 | 96 | function recurse(node) { 97 | if (node.children) { 98 | node.size = node.children.reduce(function(p, v) { 99 | return p + recurse(v); 100 | }, 0); 101 | } 102 | if (!node.id) node.id = ++i; 103 | nodes.push(node); 104 | return node.size; 105 | } 106 | 107 | root.size = recurse(root); 108 | return nodes; 109 | }; 110 | 111 | CodeFlower.prototype.click = function(d) { 112 | // Toggle children on click. 113 | if (d.children) { 114 | d._children = d.children; 115 | d.children = null; 116 | } else { 117 | d.children = d._children; 118 | d._children = null; 119 | } 120 | this.update(); 121 | }; 122 | 123 | CodeFlower.prototype.mouseover = function(d) { 124 | this.text.attr('transform', 'translate(' + d.x + ',' + (d.y - 5 - (d.children ? 3.5 : Math.sqrt(d.size) / 2)) + ')') 125 | .text(d.name + ": " + d.size + " loc") 126 | .style('display', null); 127 | }; 128 | 129 | CodeFlower.prototype.mouseout = function(d) { 130 | this.text.style('display', 'none'); 131 | }; 132 | 133 | CodeFlower.prototype.tick = function() { 134 | var h = this.h; 135 | var w = this.w; 136 | this.link.attr("x1", function(d) { return d.source.x; }) 137 | .attr("y1", function(d) { return d.source.y; }) 138 | .attr("x2", function(d) { return d.target.x; }) 139 | .attr("y2", function(d) { return d.target.y; }); 140 | 141 | this.node.attr("transform", function(d) { 142 | return "translate(" + Math.max(5, Math.min(w - 5, d.x)) + "," + Math.max(5, Math.min(h - 5, d.y)) + ")"; 143 | }); 144 | }; 145 | 146 | CodeFlower.prototype.cleanup = function() { 147 | this.update([]); 148 | this.force.stop(); 149 | }; -------------------------------------------------------------------------------- /d3.geom.js: -------------------------------------------------------------------------------- 1 | (function(){d3.geom = {}; 2 | /** 3 | * Computes a contour for a given input grid function using the marching 5 | * squares algorithm. Returns the contour polygon as an array of points. 6 | * 7 | * @param grid a two-input function(x, y) that returns true for values 8 | * inside the contour and false for values outside the contour. 9 | * @param start an optional starting point [x, y] on the grid. 10 | * @returns polygon [[x1, y1], [x2, y2], …] 11 | */ 12 | d3.geom.contour = function(grid, start) { 13 | var s = start || d3_geom_contourStart(grid), // starting point 14 | c = [], // contour polygon 15 | x = s[0], // current x position 16 | y = s[1], // current y position 17 | dx = 0, // next x direction 18 | dy = 0, // next y direction 19 | pdx = NaN, // previous x direction 20 | pdy = NaN, // previous y direction 21 | i = 0; 22 | 23 | do { 24 | // determine marching squares index 25 | i = 0; 26 | if (grid(x-1, y-1)) i += 1; 27 | if (grid(x, y-1)) i += 2; 28 | if (grid(x-1, y )) i += 4; 29 | if (grid(x, y )) i += 8; 30 | 31 | // determine next direction 32 | if (i === 6) { 33 | dx = pdy === -1 ? -1 : 1; 34 | dy = 0; 35 | } else if (i === 9) { 36 | dx = 0; 37 | dy = pdx === 1 ? -1 : 1; 38 | } else { 39 | dx = d3_geom_contourDx[i]; 40 | dy = d3_geom_contourDy[i]; 41 | } 42 | 43 | // update contour polygon 44 | if (dx != pdx && dy != pdy) { 45 | c.push([x, y]); 46 | pdx = dx; 47 | pdy = dy; 48 | } 49 | 50 | x += dx; 51 | y += dy; 52 | } while (s[0] != x || s[1] != y); 53 | 54 | return c; 55 | }; 56 | 57 | // lookup tables for marching directions 58 | var d3_geom_contourDx = [1, 0, 1, 1,-1, 0,-1, 1,0, 0,0,0,-1, 0,-1,NaN], 59 | d3_geom_contourDy = [0,-1, 0, 0, 0,-1, 0, 0,1,-1,1,1, 0,-1, 0,NaN]; 60 | 61 | function d3_geom_contourStart(grid) { 62 | var x = 0, 63 | y = 0; 64 | 65 | // search for a starting point; begin at origin 66 | // and proceed along outward-expanding diagonals 67 | while (true) { 68 | if (grid(x,y)) { 69 | return [x,y]; 70 | } 71 | if (x === 0) { 72 | x = y + 1; 73 | y = 0; 74 | } else { 75 | x = x - 1; 76 | y = y + 1; 77 | } 78 | } 79 | } 80 | /** 81 | * Computes the 2D convex hull of a set of points using Graham's scanning 82 | * algorithm. The algorithm has been implemented as described in Cormen, 83 | * Leiserson, and Rivest's Introduction to Algorithms. The running time of 84 | * this algorithm is O(n log n), where n is the number of input points. 85 | * 86 | * @param vertices [[x1, y1], [x2, y2], …] 87 | * @returns polygon [[x1, y1], [x2, y2], …] 88 | */ 89 | d3.geom.hull = function(vertices) { 90 | if (vertices.length < 3) return []; 91 | 92 | var len = vertices.length, 93 | plen = len - 1, 94 | points = [], 95 | stack = [], 96 | i, j, h = 0, x1, y1, x2, y2, u, v, a, sp; 97 | 98 | // find the starting ref point: leftmost point with the minimum y coord 99 | for (i=1; i= (x2*x2 + y2*y2)) { 129 | points[i].index = -1; 130 | } else { 131 | points[u].index = -1; 132 | a = points[i].angle; 133 | u = i; 134 | v = j; 135 | } 136 | } else { 137 | a = points[i].angle; 138 | u = i; 139 | v = j; 140 | } 141 | } 142 | 143 | // initialize the stack 144 | stack.push(h); 145 | for (i=0, j=0; i<2; ++j) { 146 | if (points[j].index !== -1) { 147 | stack.push(points[j].index); 148 | i++; 149 | } 150 | } 151 | sp = stack.length; 152 | 153 | // do graham's scan 154 | for (; j 0; 177 | } 178 | // Note: requires coordinates to be counterclockwise and convex! 179 | d3.geom.polygon = function(coordinates) { 180 | 181 | coordinates.area = function() { 182 | var i = 0, 183 | n = coordinates.length, 184 | a = coordinates[n - 1][0] * coordinates[0][1], 185 | b = coordinates[n - 1][1] * coordinates[0][0]; 186 | while (++i < n) { 187 | a += coordinates[i - 1][0] * coordinates[i][1]; 188 | b += coordinates[i - 1][1] * coordinates[i][0]; 189 | } 190 | return (b - a) * .5; 191 | }; 192 | 193 | coordinates.centroid = function(k) { 194 | var i = -1, 195 | n = coordinates.length - 1, 196 | x = 0, 197 | y = 0, 198 | a, 199 | b, 200 | c; 201 | if (!arguments.length) k = -1 / (6 * coordinates.area()); 202 | while (++i < n) { 203 | a = coordinates[i]; 204 | b = coordinates[i + 1]; 205 | c = a[0] * b[1] - b[0] * a[1]; 206 | x += (a[0] + b[0]) * c; 207 | y += (a[1] + b[1]) * c; 208 | } 209 | return [x * k, y * k]; 210 | }; 211 | 212 | // The Sutherland-Hodgman clipping algorithm. 213 | coordinates.clip = function(subject) { 214 | var input, 215 | i = -1, 216 | n = coordinates.length, 217 | j, 218 | m, 219 | a = coordinates[n - 1], 220 | b, 221 | c, 222 | d; 223 | while (++i < n) { 224 | input = subject.slice(); 225 | subject.length = 0; 226 | b = coordinates[i]; 227 | c = input[(m = input.length) - 1]; 228 | j = -1; 229 | while (++j < m) { 230 | d = input[j]; 231 | if (d3_geom_polygonInside(d, a, b)) { 232 | if (!d3_geom_polygonInside(c, a, b)) { 233 | subject.push(d3_geom_polygonIntersect(c, d, a, b)); 234 | } 235 | subject.push(d); 236 | } else if (d3_geom_polygonInside(c, a, b)) { 237 | subject.push(d3_geom_polygonIntersect(c, d, a, b)); 238 | } 239 | c = d; 240 | } 241 | a = b; 242 | } 243 | return subject; 244 | }; 245 | 246 | return coordinates; 247 | }; 248 | 249 | function d3_geom_polygonInside(p, a, b) { 250 | return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]); 251 | } 252 | 253 | // Intersect two infinite lines cd and ab. 254 | function d3_geom_polygonIntersect(c, d, a, b) { 255 | var x1 = c[0], x2 = d[0], x3 = a[0], x4 = b[0], 256 | y1 = c[1], y2 = d[1], y3 = a[1], y4 = b[1], 257 | x13 = x1 - x3, 258 | x21 = x2 - x1, 259 | x43 = x4 - x3, 260 | y13 = y1 - y3, 261 | y21 = y2 - y1, 262 | y43 = y4 - y3, 263 | ua = (x43 * y13 - y43 * x13) / (y43 * x21 - x43 * y21); 264 | return [x1 + ua * x21, y1 + ua * y21]; 265 | } 266 | // Adapted from Nicolas Garcia Belmonte's JIT implementation: 267 | // http://blog.thejit.org/2010/02/12/voronoi-tessellation/ 268 | // http://blog.thejit.org/assets/voronoijs/voronoi.js 269 | // See lib/jit/LICENSE for details. 270 | 271 | // Notes: 272 | // 273 | // This implementation does not clip the returned polygons, so if you want to 274 | // clip them to a particular shape you will need to do that either in SVG or by 275 | // post-processing with d3.geom.polygon's clip method. 276 | // 277 | // If any vertices are coincident or have NaN positions, the behavior of this 278 | // method is undefined. Most likely invalid polygons will be returned. You 279 | // should filter invalid points, and consolidate coincident points, before 280 | // computing the tessellation. 281 | 282 | /** 283 | * @param vertices [[x1, y1], [x2, y2], …] 284 | * @returns polygons [[[x1, y1], [x2, y2], …], …] 285 | */ 286 | d3.geom.voronoi = function(vertices) { 287 | var polygons = vertices.map(function() { return []; }); 288 | 289 | d3_voronoi_tessellate(vertices, function(e) { 290 | var s1, 291 | s2, 292 | x1, 293 | x2, 294 | y1, 295 | y2; 296 | if (e.a === 1 && e.b >= 0) { 297 | s1 = e.ep.r; 298 | s2 = e.ep.l; 299 | } else { 300 | s1 = e.ep.l; 301 | s2 = e.ep.r; 302 | } 303 | if (e.a === 1) { 304 | y1 = s1 ? s1.y : -1e6; 305 | x1 = e.c - e.b * y1; 306 | y2 = s2 ? s2.y : 1e6; 307 | x2 = e.c - e.b * y2; 308 | } else { 309 | x1 = s1 ? s1.x : -1e6; 310 | y1 = e.c - e.a * x1; 311 | x2 = s2 ? s2.x : 1e6; 312 | y2 = e.c - e.a * x2; 313 | } 314 | var v1 = [x1, y1], 315 | v2 = [x2, y2]; 316 | polygons[e.region.l.index].push(v1, v2); 317 | polygons[e.region.r.index].push(v1, v2); 318 | }); 319 | 320 | // Reconnect the polygon segments into counterclockwise loops. 321 | return polygons.map(function(polygon, i) { 322 | var cx = vertices[i][0], 323 | cy = vertices[i][1]; 324 | polygon.forEach(function(v) { 325 | v.angle = Math.atan2(v[0] - cx, v[1] - cy); 326 | }); 327 | return polygon.sort(function(a, b) { 328 | return a.angle - b.angle; 329 | }).filter(function(d, i) { 330 | return !i || (d.angle - polygon[i - 1].angle > 1e-10); 331 | }); 332 | }); 333 | }; 334 | 335 | var d3_voronoi_opposite = {"l": "r", "r": "l"}; 336 | 337 | function d3_voronoi_tessellate(vertices, callback) { 338 | 339 | var Sites = { 340 | list: vertices 341 | .map(function(v, i) { 342 | return { 343 | index: i, 344 | x: v[0], 345 | y: v[1] 346 | }; 347 | }) 348 | .sort(function(a, b) { 349 | return a.y < b.y ? -1 350 | : a.y > b.y ? 1 351 | : a.x < b.x ? -1 352 | : a.x > b.x ? 1 353 | : 0; 354 | }), 355 | bottomSite: null 356 | }; 357 | 358 | var EdgeList = { 359 | list: [], 360 | leftEnd: null, 361 | rightEnd: null, 362 | 363 | init: function() { 364 | EdgeList.leftEnd = EdgeList.createHalfEdge(null, "l"); 365 | EdgeList.rightEnd = EdgeList.createHalfEdge(null, "l"); 366 | EdgeList.leftEnd.r = EdgeList.rightEnd; 367 | EdgeList.rightEnd.l = EdgeList.leftEnd; 368 | EdgeList.list.unshift(EdgeList.leftEnd, EdgeList.rightEnd); 369 | }, 370 | 371 | createHalfEdge: function(edge, side) { 372 | return { 373 | edge: edge, 374 | side: side, 375 | vertex: null, 376 | "l": null, 377 | "r": null 378 | }; 379 | }, 380 | 381 | insert: function(lb, he) { 382 | he.l = lb; 383 | he.r = lb.r; 384 | lb.r.l = he; 385 | lb.r = he; 386 | }, 387 | 388 | leftBound: function(p) { 389 | var he = EdgeList.leftEnd; 390 | do { 391 | he = he.r; 392 | } while (he != EdgeList.rightEnd && Geom.rightOf(he, p)); 393 | he = he.l; 394 | return he; 395 | }, 396 | 397 | del: function(he) { 398 | he.l.r = he.r; 399 | he.r.l = he.l; 400 | he.edge = null; 401 | }, 402 | 403 | right: function(he) { 404 | return he.r; 405 | }, 406 | 407 | left: function(he) { 408 | return he.l; 409 | }, 410 | 411 | leftRegion: function(he) { 412 | return he.edge == null 413 | ? Sites.bottomSite 414 | : he.edge.region[he.side]; 415 | }, 416 | 417 | rightRegion: function(he) { 418 | return he.edge == null 419 | ? Sites.bottomSite 420 | : he.edge.region[d3_voronoi_opposite[he.side]]; 421 | } 422 | }; 423 | 424 | var Geom = { 425 | 426 | bisect: function(s1, s2) { 427 | var newEdge = { 428 | region: {"l": s1, "r": s2}, 429 | ep: {"l": null, "r": null} 430 | }; 431 | 432 | var dx = s2.x - s1.x, 433 | dy = s2.y - s1.y, 434 | adx = dx > 0 ? dx : -dx, 435 | ady = dy > 0 ? dy : -dy; 436 | 437 | newEdge.c = s1.x * dx + s1.y * dy 438 | + (dx * dx + dy * dy) * .5; 439 | 440 | if (adx > ady) { 441 | newEdge.a = 1; 442 | newEdge.b = dy / dx; 443 | newEdge.c /= dx; 444 | } else { 445 | newEdge.b = 1; 446 | newEdge.a = dx / dy; 447 | newEdge.c /= dy; 448 | } 449 | 450 | return newEdge; 451 | }, 452 | 453 | intersect: function(el1, el2) { 454 | var e1 = el1.edge, 455 | e2 = el2.edge; 456 | if (!e1 || !e2 || (e1.region.r == e2.region.r)) { 457 | return null; 458 | } 459 | var d = (e1.a * e2.b) - (e1.b * e2.a); 460 | if (Math.abs(d) < 1e-10) { 461 | return null; 462 | } 463 | var xint = (e1.c * e2.b - e2.c * e1.b) / d, 464 | yint = (e2.c * e1.a - e1.c * e2.a) / d, 465 | e1r = e1.region.r, 466 | e2r = e2.region.r, 467 | el, 468 | e; 469 | if ((e1r.y < e2r.y) || 470 | (e1r.y == e2r.y && e1r.x < e2r.x)) { 471 | el = el1; 472 | e = e1; 473 | } else { 474 | el = el2; 475 | e = e2; 476 | } 477 | var rightOfSite = (xint >= e.region.r.x); 478 | if ((rightOfSite && (el.side === "l")) || 479 | (!rightOfSite && (el.side === "r"))) { 480 | return null; 481 | } 482 | return { 483 | x: xint, 484 | y: yint 485 | }; 486 | }, 487 | 488 | rightOf: function(he, p) { 489 | var e = he.edge, 490 | topsite = e.region.r, 491 | rightOfSite = (p.x > topsite.x); 492 | 493 | if (rightOfSite && (he.side === "l")) { 494 | return 1; 495 | } 496 | if (!rightOfSite && (he.side === "r")) { 497 | return 0; 498 | } 499 | if (e.a === 1) { 500 | var dyp = p.y - topsite.y, 501 | dxp = p.x - topsite.x, 502 | fast = 0, 503 | above = 0; 504 | 505 | if ((!rightOfSite && (e.b < 0)) || 506 | (rightOfSite && (e.b >= 0))) { 507 | above = fast = (dyp >= e.b * dxp); 508 | } else { 509 | above = ((p.x + p.y * e.b) > e.c); 510 | if (e.b < 0) { 511 | above = !above; 512 | } 513 | if (!above) { 514 | fast = 1; 515 | } 516 | } 517 | if (!fast) { 518 | var dxs = topsite.x - e.region.l.x; 519 | above = (e.b * (dxp * dxp - dyp * dyp)) < 520 | (dxs * dyp * (1 + 2 * dxp / dxs + e.b * e.b)); 521 | 522 | if (e.b < 0) { 523 | above = !above; 524 | } 525 | } 526 | } else /* e.b == 1 */ { 527 | var yl = e.c - e.a * p.x, 528 | t1 = p.y - yl, 529 | t2 = p.x - topsite.x, 530 | t3 = yl - topsite.y; 531 | 532 | above = (t1 * t1) > (t2 * t2 + t3 * t3); 533 | } 534 | return he.side === "l" ? above : !above; 535 | }, 536 | 537 | endPoint: function(edge, side, site) { 538 | edge.ep[side] = site; 539 | if (!edge.ep[d3_voronoi_opposite[side]]) return; 540 | callback(edge); 541 | }, 542 | 543 | distance: function(s, t) { 544 | var dx = s.x - t.x, 545 | dy = s.y - t.y; 546 | return Math.sqrt(dx * dx + dy * dy); 547 | } 548 | }; 549 | 550 | var EventQueue = { 551 | list: [], 552 | 553 | insert: function(he, site, offset) { 554 | he.vertex = site; 555 | he.ystar = site.y + offset; 556 | for (var i=0, list=EventQueue.list, l=list.length; i next.ystar || 559 | (he.ystar == next.ystar && 560 | site.x > next.vertex.x)) { 561 | continue; 562 | } else { 563 | break; 564 | } 565 | } 566 | list.splice(i, 0, he); 567 | }, 568 | 569 | del: function(he) { 570 | for (var i=0, ls=EventQueue.list, l=ls.length; i top.y) { 646 | temp = bot; 647 | bot = top; 648 | top = temp; 649 | pm = "r"; 650 | } 651 | e = Geom.bisect(bot, top); 652 | bisector = EdgeList.createHalfEdge(e, pm); 653 | EdgeList.insert(llbnd, bisector); 654 | Geom.endPoint(e, d3_voronoi_opposite[pm], v); 655 | p = Geom.intersect(llbnd, bisector); 656 | if (p) { 657 | EventQueue.del(llbnd); 658 | EventQueue.insert(llbnd, p, Geom.distance(p, bot)); 659 | } 660 | p = Geom.intersect(bisector, rrbnd); 661 | if (p) { 662 | EventQueue.insert(bisector, p, Geom.distance(p, bot)); 663 | } 664 | } else { 665 | break; 666 | } 667 | }//end while 668 | 669 | for (lbnd = EdgeList.right(EdgeList.leftEnd); 670 | lbnd != EdgeList.rightEnd; 671 | lbnd = EdgeList.right(lbnd)) { 672 | callback(lbnd.edge); 673 | } 674 | } 675 | /** 676 | * @param vertices [[x1, y1], [x2, y2], …] 677 | * @returns triangles [[[x1, y1], [x2, y2], [x3, y3]], …] 678 | */ 679 | d3.geom.delaunay = function(vertices) { 680 | var edges = vertices.map(function() { return []; }), 681 | triangles = []; 682 | 683 | // Use the Voronoi tessellation to determine Delaunay edges. 684 | d3_voronoi_tessellate(vertices, function(e) { 685 | edges[e.region.l.index].push(vertices[e.region.r.index]); 686 | }); 687 | 688 | // Reconnect the edges into counterclockwise triangles. 689 | edges.forEach(function(edge, i) { 690 | var v = vertices[i], 691 | cx = v[0], 692 | cy = v[1]; 693 | edge.forEach(function(v) { 694 | v.angle = Math.atan2(v[0] - cx, v[1] - cy); 695 | }); 696 | edge.sort(function(a, b) { 697 | return a.angle - b.angle; 698 | }); 699 | for (var j = 0, m = edge.length - 1; j < m; j++) { 700 | triangles.push([v, edge[j], edge[j + 1]]); 701 | } 702 | }); 703 | 704 | return triangles; 705 | }; 706 | // Constructs a new quadtree for the specified array of points. A quadtree is a 707 | // two-dimensional recursive spatial subdivision. This implementation uses 708 | // square partitions, dividing each square into four equally-sized squares. Each 709 | // point exists in a unique node; if multiple points are in the same position, 710 | // some points may be stored on internal nodes rather than leaf nodes. Quadtrees 711 | // can be used to accelerate various spatial operations, such as the Barnes-Hut 712 | // approximation for computing n-body forces, or collision detection. 713 | d3.geom.quadtree = function(points, x1, y1, x2, y2) { 714 | var p, 715 | i = -1, 716 | n = points.length; 717 | 718 | // Type conversion for deprecated API. 719 | if (n && isNaN(points[0].x)) points = points.map(d3_geom_quadtreePoint); 720 | 721 | // Allow bounds to be specified explicitly. 722 | if (arguments.length < 5) { 723 | if (arguments.length === 3) { 724 | y2 = x2 = y1; 725 | y1 = x1; 726 | } else { 727 | x1 = y1 = Infinity; 728 | x2 = y2 = -Infinity; 729 | 730 | // Compute bounds. 731 | while (++i < n) { 732 | p = points[i]; 733 | if (p.x < x1) x1 = p.x; 734 | if (p.y < y1) y1 = p.y; 735 | if (p.x > x2) x2 = p.x; 736 | if (p.y > y2) y2 = p.y; 737 | } 738 | 739 | // Squarify the bounds. 740 | var dx = x2 - x1, 741 | dy = y2 - y1; 742 | if (dx > dy) y2 = y1 + dx; 743 | else x2 = x1 + dy; 744 | } 745 | } 746 | 747 | // Recursively inserts the specified point p at the node n or one of its 748 | // descendants. The bounds are defined by [x1, x2] and [y1, y2]. 749 | function insert(n, p, x1, y1, x2, y2) { 750 | if (isNaN(p.x) || isNaN(p.y)) return; // ignore invalid points 751 | if (n.leaf) { 752 | var v = n.point; 753 | if (v) { 754 | // If the point at this leaf node is at the same position as the new 755 | // point we are adding, we leave the point associated with the 756 | // internal node while adding the new point to a child node. This 757 | // avoids infinite recursion. 758 | if ((Math.abs(v.x - p.x) + Math.abs(v.y - p.y)) < .01) { 759 | insertChild(n, p, x1, y1, x2, y2); 760 | } else { 761 | n.point = null; 762 | insertChild(n, v, x1, y1, x2, y2); 763 | insertChild(n, p, x1, y1, x2, y2); 764 | } 765 | } else { 766 | n.point = p; 767 | } 768 | } else { 769 | insertChild(n, p, x1, y1, x2, y2); 770 | } 771 | } 772 | 773 | // Recursively inserts the specified point p into a descendant of node n. The 774 | // bounds are defined by [x1, x2] and [y1, y2]. 775 | function insertChild(n, p, x1, y1, x2, y2) { 776 | // Compute the split point, and the quadrant in which to insert p. 777 | var sx = (x1 + x2) * .5, 778 | sy = (y1 + y2) * .5, 779 | right = p.x >= sx, 780 | bottom = p.y >= sy, 781 | i = (bottom << 1) + right; 782 | 783 | // Recursively insert into the child node. 784 | n.leaf = false; 785 | n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode()); 786 | 787 | // Update the bounds as we recurse. 788 | if (right) x1 = sx; else x2 = sx; 789 | if (bottom) y1 = sy; else y2 = sy; 790 | insert(n, p, x1, y1, x2, y2); 791 | } 792 | 793 | // Create the root node. 794 | var root = d3_geom_quadtreeNode(); 795 | 796 | root.add = function(p) { 797 | insert(root, p, x1, y1, x2, y2); 798 | }; 799 | 800 | root.visit = function(f) { 801 | d3_geom_quadtreeVisit(f, root, x1, y1, x2, y2); 802 | }; 803 | 804 | // Insert all points. 805 | points.forEach(root.add); 806 | return root; 807 | }; 808 | 809 | function d3_geom_quadtreeNode() { 810 | return { 811 | leaf: true, 812 | nodes: [], 813 | point: null 814 | }; 815 | } 816 | 817 | function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) { 818 | if (!f(node, x1, y1, x2, y2)) { 819 | var sx = (x1 + x2) * .5, 820 | sy = (y1 + y2) * .5, 821 | children = node.nodes; 822 | if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy); 823 | if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy); 824 | if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2); 825 | if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2); 826 | } 827 | } 828 | 829 | function d3_geom_quadtreePoint(p) { 830 | return { 831 | x: p[0], 832 | y: p[1] 833 | }; 834 | } 835 | })(); 836 | -------------------------------------------------------------------------------- /d3.layout.js: -------------------------------------------------------------------------------- 1 | (function(){d3.layout = {}; 2 | // Implements hierarchical edge bundling using Holten's algorithm. For each 3 | // input link, a path is computed that travels through the tree, up the parent 4 | // hierarchy to the least common ancestor, and then back down to the destination 5 | // node. Each path is simply an array of nodes. 6 | d3.layout.bundle = function() { 7 | return function(links) { 8 | var paths = [], 9 | i = -1, 10 | n = links.length; 11 | while (++i < n) paths.push(d3_layout_bundlePath(links[i])); 12 | return paths; 13 | }; 14 | }; 15 | 16 | function d3_layout_bundlePath(link) { 17 | var start = link.source, 18 | end = link.target, 19 | lca = d3_layout_bundleLeastCommonAncestor(start, end), 20 | points = [start]; 21 | while (start !== lca) { 22 | start = start.parent; 23 | points.push(start); 24 | } 25 | var k = points.length; 26 | while (end !== lca) { 27 | points.splice(k, 0, end); 28 | end = end.parent; 29 | } 30 | return points; 31 | } 32 | 33 | function d3_layout_bundleAncestors(node) { 34 | var ancestors = [], 35 | parent = node.parent; 36 | while (parent != null) { 37 | ancestors.push(node); 38 | node = parent; 39 | parent = parent.parent; 40 | } 41 | ancestors.push(node); 42 | return ancestors; 43 | } 44 | 45 | function d3_layout_bundleLeastCommonAncestor(a, b) { 46 | if (a === b) return a; 47 | var aNodes = d3_layout_bundleAncestors(a), 48 | bNodes = d3_layout_bundleAncestors(b), 49 | aNode = aNodes.pop(), 50 | bNode = bNodes.pop(), 51 | sharedNode = null; 52 | while (aNode === bNode) { 53 | sharedNode = aNode; 54 | aNode = aNodes.pop(); 55 | bNode = bNodes.pop(); 56 | } 57 | return sharedNode; 58 | } 59 | d3.layout.chord = function() { 60 | var chord = {}, 61 | chords, 62 | groups, 63 | matrix, 64 | n, 65 | padding = 0, 66 | sortGroups, 67 | sortSubgroups, 68 | sortChords; 69 | 70 | function relayout() { 71 | var subgroups = {}, 72 | groupSums = [], 73 | groupIndex = d3.range(n), 74 | subgroupIndex = [], 75 | k, 76 | x, 77 | x0, 78 | i, 79 | j; 80 | 81 | chords = []; 82 | groups = []; 83 | 84 | // Compute the sum. 85 | k = 0, i = -1; while (++i < n) { 86 | x = 0, j = -1; while (++j < n) { 87 | x += matrix[i][j]; 88 | } 89 | groupSums.push(x); 90 | subgroupIndex.push(d3.range(n)); 91 | k += x; 92 | } 93 | 94 | // Sort groups… 95 | if (sortGroups) { 96 | groupIndex.sort(function(a, b) { 97 | return sortGroups(groupSums[a], groupSums[b]); 98 | }); 99 | } 100 | 101 | // Sort subgroups… 102 | if (sortSubgroups) { 103 | subgroupIndex.forEach(function(d, i) { 104 | d.sort(function(a, b) { 105 | return sortSubgroups(matrix[i][a], matrix[i][b]); 106 | }); 107 | }); 108 | } 109 | 110 | // Convert the sum to scaling factor for [0, 2pi]. 111 | // TODO Allow start and end angle to be specified. 112 | // TODO Allow padding to be specified as percentage? 113 | k = (2 * Math.PI - padding * n) / k; 114 | 115 | // Compute the start and end angle for each group and subgroup. 116 | // Note: Opera has a bug reordering object literal properties! 117 | x = 0, i = -1; while (++i < n) { 118 | x0 = x, j = -1; while (++j < n) { 119 | var di = groupIndex[i], 120 | dj = subgroupIndex[di][j], 121 | v = matrix[di][dj], 122 | a0 = x, 123 | a1 = x += v * k; 124 | subgroups[di + "-" + dj] = { 125 | index: di, 126 | subindex: dj, 127 | startAngle: a0, 128 | endAngle: a1, 129 | value: v 130 | }; 131 | } 132 | groups.push({ 133 | index: di, 134 | startAngle: x0, 135 | endAngle: x, 136 | value: (x - x0) / k 137 | }); 138 | x += padding; 139 | } 140 | 141 | // Generate chords for each (non-empty) subgroup-subgroup link. 142 | i = -1; while (++i < n) { 143 | j = i - 1; while (++j < n) { 144 | var source = subgroups[i + "-" + j], 145 | target = subgroups[j + "-" + i]; 146 | if (source.value || target.value) { 147 | chords.push(source.value < target.value 148 | ? {source: target, target: source} 149 | : {source: source, target: target}); 150 | } 151 | } 152 | } 153 | 154 | if (sortChords) resort(); 155 | } 156 | 157 | function resort() { 158 | chords.sort(function(a, b) { 159 | return sortChords( 160 | (a.source.value + a.target.value) / 2, 161 | (b.source.value + b.target.value) / 2); 162 | }); 163 | } 164 | 165 | chord.matrix = function(x) { 166 | if (!arguments.length) return matrix; 167 | n = (matrix = x) && matrix.length; 168 | chords = groups = null; 169 | return chord; 170 | }; 171 | 172 | chord.padding = function(x) { 173 | if (!arguments.length) return padding; 174 | padding = x; 175 | chords = groups = null; 176 | return chord; 177 | }; 178 | 179 | chord.sortGroups = function(x) { 180 | if (!arguments.length) return sortGroups; 181 | sortGroups = x; 182 | chords = groups = null; 183 | return chord; 184 | }; 185 | 186 | chord.sortSubgroups = function(x) { 187 | if (!arguments.length) return sortSubgroups; 188 | sortSubgroups = x; 189 | chords = null; 190 | return chord; 191 | }; 192 | 193 | chord.sortChords = function(x) { 194 | if (!arguments.length) return sortChords; 195 | sortChords = x; 196 | if (chords) resort(); 197 | return chord; 198 | }; 199 | 200 | chord.chords = function() { 201 | if (!chords) relayout(); 202 | return chords; 203 | }; 204 | 205 | chord.groups = function() { 206 | if (!groups) relayout(); 207 | return groups; 208 | }; 209 | 210 | return chord; 211 | }; 212 | // A rudimentary force layout using Gauss-Seidel. 213 | d3.layout.force = function() { 214 | var force = {}, 215 | event = d3.dispatch("tick"), 216 | size = [1, 1], 217 | drag, 218 | alpha, 219 | friction = .9, 220 | linkDistance = d3_layout_forceLinkDistance, 221 | linkStrength = d3_layout_forceLinkStrength, 222 | charge = -30, 223 | gravity = .1, 224 | theta = .8, 225 | interval, 226 | nodes = [], 227 | links = [], 228 | distances, 229 | strengths, 230 | charges; 231 | 232 | function repulse(node) { 233 | return function(quad, x1, y1, x2, y2) { 234 | if (quad.point !== node) { 235 | var dx = quad.cx - node.x, 236 | dy = quad.cy - node.y, 237 | dn = 1 / Math.sqrt(dx * dx + dy * dy); 238 | 239 | /* Barnes-Hut criterion. */ 240 | if ((x2 - x1) * dn < theta) { 241 | var k = quad.charge * dn * dn; 242 | node.px -= dx * k; 243 | node.py -= dy * k; 244 | return true; 245 | } 246 | 247 | if (quad.point && isFinite(dn)) { 248 | var k = quad.pointCharge * dn * dn; 249 | node.px -= dx * k; 250 | node.py -= dy * k; 251 | } 252 | } 253 | return !quad.charge; 254 | }; 255 | } 256 | 257 | function tick() { 258 | var n = nodes.length, 259 | m = links.length, 260 | q, 261 | i, // current index 262 | o, // current object 263 | s, // current source 264 | t, // current target 265 | l, // current distance 266 | k, // current force 267 | x, // x-distance 268 | y; // y-distance 269 | 270 | // gauss-seidel relaxation for links 271 | for (i = 0; i < m; ++i) { 272 | o = links[i]; 273 | s = o.source; 274 | t = o.target; 275 | x = t.x - s.x; 276 | y = t.y - s.y; 277 | if (l = (x * x + y * y)) { 278 | l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l; 279 | x *= l; 280 | y *= l; 281 | t.x -= x * (k = s.weight / (t.weight + s.weight)); 282 | t.y -= y * k; 283 | s.x += x * (k = 1 - k); 284 | s.y += y * k; 285 | } 286 | } 287 | 288 | // apply gravity forces 289 | if (k = alpha * gravity) { 290 | x = size[0] / 2; 291 | y = size[1] / 2; 292 | i = -1; if (k) while (++i < n) { 293 | o = nodes[i]; 294 | o.x += (x - o.x) * k; 295 | o.y += (y - o.y) * k; 296 | } 297 | } 298 | 299 | // compute quadtree center of mass and apply charge forces 300 | if (charge) { 301 | d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges); 302 | i = -1; while (++i < n) { 303 | if (!(o = nodes[i]).fixed) { 304 | q.visit(repulse(o)); 305 | } 306 | } 307 | } 308 | 309 | // position verlet integration 310 | i = -1; while (++i < n) { 311 | o = nodes[i]; 312 | if (o.fixed) { 313 | o.x = o.px; 314 | o.y = o.py; 315 | } else { 316 | o.x -= (o.px - (o.px = o.x)) * friction; 317 | o.y -= (o.py - (o.py = o.y)) * friction; 318 | } 319 | } 320 | 321 | event.tick({type: "tick", alpha: alpha}); 322 | 323 | // simulated annealing, basically 324 | return (alpha *= .99) < .005; 325 | } 326 | 327 | force.on = function(type, listener) { 328 | event.on(type, listener); 329 | return force; 330 | }; 331 | 332 | force.nodes = function(x) { 333 | if (!arguments.length) return nodes; 334 | nodes = x; 335 | return force; 336 | }; 337 | 338 | force.links = function(x) { 339 | if (!arguments.length) return links; 340 | links = x; 341 | return force; 342 | }; 343 | 344 | force.size = function(x) { 345 | if (!arguments.length) return size; 346 | size = x; 347 | return force; 348 | }; 349 | 350 | force.linkDistance = function(x) { 351 | if (!arguments.length) return linkDistance; 352 | linkDistance = d3.functor(x); 353 | return force; 354 | }; 355 | 356 | // For backwards-compatibility. 357 | force.distance = force.linkDistance; 358 | 359 | force.linkStrength = function(x) { 360 | if (!arguments.length) return linkStrength; 361 | linkStrength = d3.functor(x); 362 | return force; 363 | }; 364 | 365 | force.friction = function(x) { 366 | if (!arguments.length) return friction; 367 | friction = x; 368 | return force; 369 | }; 370 | 371 | force.charge = function(x) { 372 | if (!arguments.length) return charge; 373 | charge = typeof x === "function" ? x : +x; 374 | return force; 375 | }; 376 | 377 | force.gravity = function(x) { 378 | if (!arguments.length) return gravity; 379 | gravity = x; 380 | return force; 381 | }; 382 | 383 | force.theta = function(x) { 384 | if (!arguments.length) return theta; 385 | theta = x; 386 | return force; 387 | }; 388 | 389 | force.start = function() { 390 | var i, 391 | j, 392 | n = nodes.length, 393 | m = links.length, 394 | w = size[0], 395 | h = size[1], 396 | neighbors, 397 | o; 398 | 399 | for (i = 0; i < n; ++i) { 400 | (o = nodes[i]).index = i; 401 | o.weight = 0; 402 | } 403 | 404 | distances = []; 405 | strengths = []; 406 | for (i = 0; i < m; ++i) { 407 | o = links[i]; 408 | if (typeof o.source == "number") o.source = nodes[o.source]; 409 | if (typeof o.target == "number") o.target = nodes[o.target]; 410 | distances[i] = linkDistance.call(this, o, i); 411 | strengths[i] = linkStrength.call(this, o, i); 412 | ++o.source.weight; 413 | ++o.target.weight; 414 | } 415 | 416 | for (i = 0; i < n; ++i) { 417 | o = nodes[i]; 418 | if (isNaN(o.x)) o.x = position("x", w); 419 | if (isNaN(o.y)) o.y = position("y", h); 420 | if (isNaN(o.px)) o.px = o.x; 421 | if (isNaN(o.py)) o.py = o.y; 422 | } 423 | 424 | charges = []; 425 | if (typeof charge === "function") { 426 | for (i = 0; i < n; ++i) { 427 | charges[i] = +charge.call(this, nodes[i], i); 428 | } 429 | } else { 430 | for (i = 0; i < n; ++i) { 431 | charges[i] = charge; 432 | } 433 | } 434 | 435 | // initialize node position based on first neighbor 436 | function position(dimension, size) { 437 | var neighbors = neighbor(i), 438 | j = -1, 439 | m = neighbors.length, 440 | x; 441 | while (++j < m) if (!isNaN(x = neighbors[j][dimension])) return x; 442 | return Math.random() * size; 443 | } 444 | 445 | // initialize neighbors lazily 446 | function neighbor() { 447 | if (!neighbors) { 448 | neighbors = []; 449 | for (j = 0; j < n; ++j) { 450 | neighbors[j] = []; 451 | } 452 | for (j = 0; j < m; ++j) { 453 | var o = links[j]; 454 | neighbors[o.source.index].push(o.target); 455 | neighbors[o.target.index].push(o.source); 456 | } 457 | } 458 | return neighbors[i]; 459 | } 460 | 461 | return force.resume(); 462 | }; 463 | 464 | force.resume = function() { 465 | alpha = .1; 466 | d3.timer(tick); 467 | return force; 468 | }; 469 | 470 | force.stop = function() { 471 | alpha = 0; 472 | return force; 473 | }; 474 | 475 | // use `node.call(force.drag)` to make nodes draggable 476 | force.drag = function() { 477 | if (!drag) drag = d3.behavior.drag() 478 | .origin(Object) 479 | .on("dragstart", dragstart) 480 | .on("drag", d3_layout_forceDrag) 481 | .on("dragend", d3_layout_forceDragEnd); 482 | 483 | this.on("mouseover.force", d3_layout_forceDragOver) 484 | .on("mouseout.force", d3_layout_forceDragOut) 485 | .call(drag); 486 | }; 487 | 488 | function dragstart(d) { 489 | d3_layout_forceDragOver(d3_layout_forceDragNode = d); 490 | d3_layout_forceDragForce = force; 491 | } 492 | 493 | return force; 494 | }; 495 | 496 | var d3_layout_forceDragForce, 497 | d3_layout_forceDragNode; 498 | 499 | function d3_layout_forceDragOver(d) { 500 | d.fixed |= 2; 501 | } 502 | 503 | function d3_layout_forceDragOut(d) { 504 | if (d !== d3_layout_forceDragNode) d.fixed &= 1; 505 | } 506 | 507 | function d3_layout_forceDragEnd() { 508 | d3_layout_forceDrag(); 509 | d3_layout_forceDragNode.fixed &= 1; 510 | d3_layout_forceDragForce = d3_layout_forceDragNode = null; 511 | } 512 | 513 | function d3_layout_forceDrag() { 514 | d3_layout_forceDragNode.px = d3.event.x; 515 | d3_layout_forceDragNode.py = d3.event.y; 516 | d3_layout_forceDragForce.resume(); // restart annealing 517 | } 518 | 519 | function d3_layout_forceAccumulate(quad, alpha, charges) { 520 | var cx = 0, 521 | cy = 0; 522 | quad.charge = 0; 523 | if (!quad.leaf) { 524 | var nodes = quad.nodes, 525 | n = nodes.length, 526 | i = -1, 527 | c; 528 | while (++i < n) { 529 | c = nodes[i]; 530 | if (c == null) continue; 531 | d3_layout_forceAccumulate(c, alpha, charges); 532 | quad.charge += c.charge; 533 | cx += c.charge * c.cx; 534 | cy += c.charge * c.cy; 535 | } 536 | } 537 | if (quad.point) { 538 | // jitter internal nodes that are coincident 539 | if (!quad.leaf) { 540 | quad.point.x += Math.random() - .5; 541 | quad.point.y += Math.random() - .5; 542 | } 543 | var k = alpha * charges[quad.point.index]; 544 | quad.charge += quad.pointCharge = k; 545 | cx += k * quad.point.x; 546 | cy += k * quad.point.y; 547 | } 548 | quad.cx = cx / quad.charge; 549 | quad.cy = cy / quad.charge; 550 | } 551 | 552 | function d3_layout_forceLinkDistance(link) { 553 | return 20; 554 | } 555 | 556 | function d3_layout_forceLinkStrength(link) { 557 | return 1; 558 | } 559 | d3.layout.partition = function() { 560 | var hierarchy = d3.layout.hierarchy(), 561 | size = [1, 1]; // width, height 562 | 563 | function position(node, x, dx, dy) { 564 | var children = node.children; 565 | node.x = x; 566 | node.y = node.depth * dy; 567 | node.dx = dx; 568 | node.dy = dy; 569 | if (children && (n = children.length)) { 570 | var i = -1, 571 | n, 572 | c, 573 | d; 574 | dx = node.value ? dx / node.value : 0; 575 | while (++i < n) { 576 | position(c = children[i], x, d = c.value * dx, dy); 577 | x += d; 578 | } 579 | } 580 | } 581 | 582 | function depth(node) { 583 | var children = node.children, 584 | d = 0; 585 | if (children && (n = children.length)) { 586 | var i = -1, 587 | n; 588 | while (++i < n) d = Math.max(d, depth(children[i])); 589 | } 590 | return 1 + d; 591 | } 592 | 593 | function partition(d, i) { 594 | var nodes = hierarchy.call(this, d, i); 595 | position(nodes[0], 0, size[0], size[1] / depth(nodes[0])); 596 | return nodes; 597 | } 598 | 599 | partition.size = function(x) { 600 | if (!arguments.length) return size; 601 | size = x; 602 | return partition; 603 | }; 604 | 605 | return d3_layout_hierarchyRebind(partition, hierarchy); 606 | }; 607 | d3.layout.pie = function() { 608 | var value = Number, 609 | sort = d3_layout_pieSortByValue, 610 | startAngle = 0, 611 | endAngle = 2 * Math.PI; 612 | 613 | function pie(data, i) { 614 | 615 | // Compute the numeric values for each data element. 616 | var values = data.map(function(d, i) { return +value.call(pie, d, i); }); 617 | 618 | // Compute the start angle. 619 | var a = +(typeof startAngle === "function" 620 | ? startAngle.apply(this, arguments) 621 | : startAngle); 622 | 623 | // Compute the angular scale factor: from value to radians. 624 | var k = ((typeof endAngle === "function" 625 | ? endAngle.apply(this, arguments) 626 | : endAngle) - startAngle) 627 | / d3.sum(values); 628 | 629 | // Optionally sort the data. 630 | var index = d3.range(data.length); 631 | if (sort != null) index.sort(sort === d3_layout_pieSortByValue 632 | ? function(i, j) { return values[j] - values[i]; } 633 | : function(i, j) { return sort(data[i], data[j]); }); 634 | 635 | // Compute the arcs! 636 | var arcs = index.map(function(i) { 637 | return { 638 | data: data[i], 639 | value: d = values[i], 640 | startAngle: a, 641 | endAngle: a += d * k 642 | }; 643 | }); 644 | 645 | // Return the arcs in the original data's order. 646 | return data.map(function(d, i) { 647 | return arcs[index[i]]; 648 | }); 649 | } 650 | 651 | /** 652 | * Specifies the value function *x*, which returns a nonnegative numeric value 653 | * for each datum. The default value function is `Number`. The value function 654 | * is passed two arguments: the current datum and the current index. 655 | */ 656 | pie.value = function(x) { 657 | if (!arguments.length) return value; 658 | value = x; 659 | return pie; 660 | }; 661 | 662 | /** 663 | * Specifies a sort comparison operator *x*. The comparator is passed two data 664 | * elements from the data array, a and b; it returns a negative value if a is 665 | * less than b, a positive value if a is greater than b, and zero if a equals 666 | * b. 667 | */ 668 | pie.sort = function(x) { 669 | if (!arguments.length) return sort; 670 | sort = x; 671 | return pie; 672 | }; 673 | 674 | /** 675 | * Specifies the overall start angle of the pie chart. Defaults to 0. The 676 | * start angle can be specified either as a constant or as a function; in the 677 | * case of a function, it is evaluated once per array (as opposed to per 678 | * element). 679 | */ 680 | pie.startAngle = function(x) { 681 | if (!arguments.length) return startAngle; 682 | startAngle = x; 683 | return pie; 684 | }; 685 | 686 | /** 687 | * Specifies the overall end angle of the pie chart. Defaults to 2π. The 688 | * end angle can be specified either as a constant or as a function; in the 689 | * case of a function, it is evaluated once per array (as opposed to per 690 | * element). 691 | */ 692 | pie.endAngle = function(x) { 693 | if (!arguments.length) return endAngle; 694 | endAngle = x; 695 | return pie; 696 | }; 697 | 698 | return pie; 699 | }; 700 | 701 | var d3_layout_pieSortByValue = {}; 702 | // data is two-dimensional array of x,y; we populate y0 703 | d3.layout.stack = function() { 704 | var values = Object, 705 | order = d3_layout_stackOrders["default"], 706 | offset = d3_layout_stackOffsets["zero"], 707 | out = d3_layout_stackOut, 708 | x = d3_layout_stackX, 709 | y = d3_layout_stackY; 710 | 711 | function stack(data, index) { 712 | 713 | // Convert series to canonical two-dimensional representation. 714 | var series = data.map(function(d, i) { 715 | return values.call(stack, d, i); 716 | }); 717 | 718 | // Convert each series to canonical [[x,y]] representation. 719 | var points = series.map(function(d, i) { 720 | return d.map(function(v, i) { 721 | return [x.call(stack, v, i), y.call(stack, v, i)]; 722 | }); 723 | }); 724 | 725 | // Compute the order of series, and permute them. 726 | var orders = order.call(stack, points, index); 727 | series = d3.permute(series, orders); 728 | points = d3.permute(points, orders); 729 | 730 | // Compute the baseline… 731 | var offsets = offset.call(stack, points, index); 732 | 733 | // And propagate it to other series. 734 | var n = series.length, 735 | m = series[0].length, 736 | i, 737 | j, 738 | o; 739 | for (j = 0; j < m; ++j) { 740 | out.call(stack, series[0][j], o = offsets[j], points[0][j][1]); 741 | for (i = 1; i < n; ++i) { 742 | out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]); 743 | } 744 | } 745 | 746 | return data; 747 | } 748 | 749 | stack.values = function(x) { 750 | if (!arguments.length) return values; 751 | values = x; 752 | return stack; 753 | }; 754 | 755 | stack.order = function(x) { 756 | if (!arguments.length) return order; 757 | order = typeof x === "function" ? x : d3_layout_stackOrders[x]; 758 | return stack; 759 | }; 760 | 761 | stack.offset = function(x) { 762 | if (!arguments.length) return offset; 763 | offset = typeof x === "function" ? x : d3_layout_stackOffsets[x]; 764 | return stack; 765 | }; 766 | 767 | stack.x = function(z) { 768 | if (!arguments.length) return x; 769 | x = z; 770 | return stack; 771 | }; 772 | 773 | stack.y = function(z) { 774 | if (!arguments.length) return y; 775 | y = z; 776 | return stack; 777 | }; 778 | 779 | stack.out = function(z) { 780 | if (!arguments.length) return out; 781 | out = z; 782 | return stack; 783 | }; 784 | 785 | return stack; 786 | } 787 | 788 | function d3_layout_stackX(d) { 789 | return d.x; 790 | } 791 | 792 | function d3_layout_stackY(d) { 793 | return d.y; 794 | } 795 | 796 | function d3_layout_stackOut(d, y0, y) { 797 | d.y0 = y0; 798 | d.y = y; 799 | } 800 | 801 | var d3_layout_stackOrders = { 802 | 803 | "inside-out": function(data) { 804 | var n = data.length, 805 | i, 806 | j, 807 | max = data.map(d3_layout_stackMaxIndex), 808 | sums = data.map(d3_layout_stackReduceSum), 809 | index = d3.range(n).sort(function(a, b) { return max[a] - max[b]; }), 810 | top = 0, 811 | bottom = 0, 812 | tops = [], 813 | bottoms = []; 814 | for (i = 0; i < n; ++i) { 815 | j = index[i]; 816 | if (top < bottom) { 817 | top += sums[j]; 818 | tops.push(j); 819 | } else { 820 | bottom += sums[j]; 821 | bottoms.push(j); 822 | } 823 | } 824 | return bottoms.reverse().concat(tops); 825 | }, 826 | 827 | "reverse": function(data) { 828 | return d3.range(data.length).reverse(); 829 | }, 830 | 831 | "default": function(data) { 832 | return d3.range(data.length); 833 | } 834 | 835 | }; 836 | 837 | var d3_layout_stackOffsets = { 838 | 839 | "silhouette": function(data) { 840 | var n = data.length, 841 | m = data[0].length, 842 | sums = [], 843 | max = 0, 844 | i, 845 | j, 846 | o, 847 | y0 = []; 848 | for (j = 0; j < m; ++j) { 849 | for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; 850 | if (o > max) max = o; 851 | sums.push(o); 852 | } 853 | for (j = 0; j < m; ++j) { 854 | y0[j] = (max - sums[j]) / 2; 855 | } 856 | return y0; 857 | }, 858 | 859 | "wiggle": function(data) { 860 | var n = data.length, 861 | x = data[0], 862 | m = x.length, 863 | max = 0, 864 | i, 865 | j, 866 | k, 867 | s1, 868 | s2, 869 | s3, 870 | dx, 871 | o, 872 | o0, 873 | y0 = []; 874 | y0[0] = o = o0 = 0; 875 | for (j = 1; j < m; ++j) { 876 | for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1]; 877 | for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) { 878 | for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) { 879 | s3 += (data[k][j][1] - data[k][j - 1][1]) / dx; 880 | } 881 | s2 += s3 * data[i][j][1]; 882 | } 883 | y0[j] = o -= s1 ? s2 / s1 * dx : 0; 884 | if (o < o0) o0 = o; 885 | } 886 | for (j = 0; j < m; ++j) y0[j] -= o0; 887 | return y0; 888 | }, 889 | 890 | "expand": function(data) { 891 | var n = data.length, 892 | m = data[0].length, 893 | k = 1 / n, 894 | i, 895 | j, 896 | o, 897 | y0 = []; 898 | for (j = 0; j < m; ++j) { 899 | for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; 900 | if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; 901 | else for (i = 0; i < n; i++) data[i][j][1] = k; 902 | } 903 | for (j = 0; j < m; ++j) y0[j] = 0; 904 | return y0; 905 | }, 906 | 907 | "zero": function(data) { 908 | var j = -1, 909 | m = data[0].length, 910 | y0 = []; 911 | while (++j < m) y0[j] = 0; 912 | return y0; 913 | } 914 | 915 | }; 916 | 917 | function d3_layout_stackMaxIndex(array) { 918 | var i = 1, 919 | j = 0, 920 | v = array[0][1], 921 | k, 922 | n = array.length; 923 | for (; i < n; ++i) { 924 | if ((k = array[i][1]) > v) { 925 | j = i; 926 | v = k; 927 | } 928 | } 929 | return j; 930 | } 931 | 932 | function d3_layout_stackReduceSum(d) { 933 | return d.reduce(d3_layout_stackSum, 0); 934 | } 935 | 936 | function d3_layout_stackSum(p, d) { 937 | return p + d[1]; 938 | } 939 | d3.layout.histogram = function() { 940 | var frequency = true, 941 | valuer = Number, 942 | ranger = d3_layout_histogramRange, 943 | binner = d3_layout_histogramBinSturges; 944 | 945 | function histogram(data, i) { 946 | var bins = [], 947 | values = data.map(valuer, this), 948 | range = ranger.call(this, values, i), 949 | thresholds = binner.call(this, range, values, i), 950 | bin, 951 | i = -1, 952 | n = values.length, 953 | m = thresholds.length - 1, 954 | k = frequency ? 1 : 1 / n, 955 | x; 956 | 957 | // Initialize the bins. 958 | while (++i < m) { 959 | bin = bins[i] = []; 960 | bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]); 961 | bin.y = 0; 962 | } 963 | 964 | // Fill the bins, ignoring values outside the range. 965 | i = -1; while(++i < n) { 966 | x = values[i]; 967 | if ((x >= range[0]) && (x <= range[1])) { 968 | bin = bins[d3.bisect(thresholds, x, 1, m) - 1]; 969 | bin.y += k; 970 | bin.push(data[i]); 971 | } 972 | } 973 | 974 | return bins; 975 | } 976 | 977 | // Specifies how to extract a value from the associated data. The default 978 | // value function is `Number`, which is equivalent to the identity function. 979 | histogram.value = function(x) { 980 | if (!arguments.length) return valuer; 981 | valuer = x; 982 | return histogram; 983 | }; 984 | 985 | // Specifies the range of the histogram. Values outside the specified range 986 | // will be ignored. The argument `x` may be specified either as a two-element 987 | // array representing the minimum and maximum value of the range, or as a 988 | // function that returns the range given the array of values and the current 989 | // index `i`. The default range is the extent (minimum and maximum) of the 990 | // values. 991 | histogram.range = function(x) { 992 | if (!arguments.length) return ranger; 993 | ranger = d3.functor(x); 994 | return histogram; 995 | }; 996 | 997 | // Specifies how to bin values in the histogram. The argument `x` may be 998 | // specified as a number, in which case the range of values will be split 999 | // uniformly into the given number of bins. Or, `x` may be an array of 1000 | // threshold values, defining the bins; the specified array must contain the 1001 | // rightmost (upper) value, thus specifying n + 1 values for n bins. Or, `x` 1002 | // may be a function which is evaluated, being passed the range, the array of 1003 | // values, and the current index `i`, returning an array of thresholds. The 1004 | // default bin function will divide the values into uniform bins using 1005 | // Sturges' formula. 1006 | histogram.bins = function(x) { 1007 | if (!arguments.length) return binner; 1008 | binner = typeof x === "number" 1009 | ? function(range) { return d3_layout_histogramBinFixed(range, x); } 1010 | : d3.functor(x); 1011 | return histogram; 1012 | }; 1013 | 1014 | // Specifies whether the histogram's `y` value is a count (frequency) or a 1015 | // probability (density). The default value is true. 1016 | histogram.frequency = function(x) { 1017 | if (!arguments.length) return frequency; 1018 | frequency = !!x; 1019 | return histogram; 1020 | }; 1021 | 1022 | return histogram; 1023 | }; 1024 | 1025 | function d3_layout_histogramBinSturges(range, values) { 1026 | return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1)); 1027 | } 1028 | 1029 | function d3_layout_histogramBinFixed(range, n) { 1030 | var x = -1, 1031 | b = +range[0], 1032 | m = (range[1] - b) / n, 1033 | f = []; 1034 | while (++x <= n) f[x] = m * x + b; 1035 | return f; 1036 | } 1037 | 1038 | function d3_layout_histogramRange(values) { 1039 | return [d3.min(values), d3.max(values)]; 1040 | } 1041 | d3.layout.hierarchy = function() { 1042 | var sort = d3_layout_hierarchySort, 1043 | children = d3_layout_hierarchyChildren, 1044 | value = d3_layout_hierarchyValue; 1045 | 1046 | // Recursively compute the node depth and value. 1047 | // Also converts the data representation into a standard hierarchy structure. 1048 | function recurse(data, depth, nodes) { 1049 | var childs = children.call(hierarchy, data, depth), 1050 | node = d3_layout_hierarchyInline ? data : {data: data}; 1051 | node.depth = depth; 1052 | nodes.push(node); 1053 | if (childs && (n = childs.length)) { 1054 | var i = -1, 1055 | n, 1056 | c = node.children = [], 1057 | v = 0, 1058 | j = depth + 1; 1059 | while (++i < n) { 1060 | d = recurse(childs[i], j, nodes); 1061 | d.parent = node; 1062 | c.push(d); 1063 | v += d.value; 1064 | } 1065 | if (sort) c.sort(sort); 1066 | if (value) node.value = v; 1067 | } else if (value) { 1068 | node.value = +value.call(hierarchy, data, depth) || 0; 1069 | } 1070 | return node; 1071 | } 1072 | 1073 | // Recursively re-evaluates the node value. 1074 | function revalue(node, depth) { 1075 | var children = node.children, 1076 | v = 0; 1077 | if (children && (n = children.length)) { 1078 | var i = -1, 1079 | n, 1080 | j = depth + 1; 1081 | while (++i < n) v += revalue(children[i], j); 1082 | } else if (value) { 1083 | v = +value.call(hierarchy, d3_layout_hierarchyInline ? node : node.data, depth) || 0; 1084 | } 1085 | if (value) node.value = v; 1086 | return v; 1087 | } 1088 | 1089 | function hierarchy(d) { 1090 | var nodes = []; 1091 | recurse(d, 0, nodes); 1092 | return nodes; 1093 | } 1094 | 1095 | hierarchy.sort = function(x) { 1096 | if (!arguments.length) return sort; 1097 | sort = x; 1098 | return hierarchy; 1099 | }; 1100 | 1101 | hierarchy.children = function(x) { 1102 | if (!arguments.length) return children; 1103 | children = x; 1104 | return hierarchy; 1105 | }; 1106 | 1107 | hierarchy.value = function(x) { 1108 | if (!arguments.length) return value; 1109 | value = x; 1110 | return hierarchy; 1111 | }; 1112 | 1113 | // Re-evaluates the `value` property for the specified hierarchy. 1114 | hierarchy.revalue = function(root) { 1115 | revalue(root, 0); 1116 | return root; 1117 | }; 1118 | 1119 | return hierarchy; 1120 | }; 1121 | 1122 | // A method assignment helper for hierarchy subclasses. 1123 | function d3_layout_hierarchyRebind(object, hierarchy) { 1124 | object.sort = d3.rebind(object, hierarchy.sort); 1125 | object.children = d3.rebind(object, hierarchy.children); 1126 | object.links = d3_layout_hierarchyLinks; 1127 | object.value = d3.rebind(object, hierarchy.value); 1128 | 1129 | // If the new API is used, enabling inlining. 1130 | object.nodes = function(d) { 1131 | d3_layout_hierarchyInline = true; 1132 | return (object.nodes = object)(d); 1133 | }; 1134 | 1135 | return object; 1136 | } 1137 | 1138 | function d3_layout_hierarchyChildren(d) { 1139 | return d.children; 1140 | } 1141 | 1142 | function d3_layout_hierarchyValue(d) { 1143 | return d.value; 1144 | } 1145 | 1146 | function d3_layout_hierarchySort(a, b) { 1147 | return b.value - a.value; 1148 | } 1149 | 1150 | // Returns an array source+target objects for the specified nodes. 1151 | function d3_layout_hierarchyLinks(nodes) { 1152 | return d3.merge(nodes.map(function(parent) { 1153 | return (parent.children || []).map(function(child) { 1154 | return {source: parent, target: child}; 1155 | }); 1156 | })); 1157 | } 1158 | 1159 | // For backwards-compatibility, don't enable inlining by default. 1160 | var d3_layout_hierarchyInline = false; 1161 | d3.layout.pack = function() { 1162 | var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), 1163 | size = [1, 1]; 1164 | 1165 | function pack(d, i) { 1166 | var nodes = hierarchy.call(this, d, i), 1167 | root = nodes[0]; 1168 | 1169 | // Recursively compute the layout. 1170 | root.x = 0; 1171 | root.y = 0; 1172 | d3_layout_packTree(root); 1173 | 1174 | // Scale the layout to fit the requested size. 1175 | var w = size[0], 1176 | h = size[1], 1177 | k = 1 / Math.max(2 * root.r / w, 2 * root.r / h); 1178 | d3_layout_packTransform(root, w / 2, h / 2, k); 1179 | 1180 | return nodes; 1181 | } 1182 | 1183 | pack.size = function(x) { 1184 | if (!arguments.length) return size; 1185 | size = x; 1186 | return pack; 1187 | }; 1188 | 1189 | return d3_layout_hierarchyRebind(pack, hierarchy); 1190 | }; 1191 | 1192 | function d3_layout_packSort(a, b) { 1193 | return a.value - b.value; 1194 | } 1195 | 1196 | function d3_layout_packInsert(a, b) { 1197 | var c = a._pack_next; 1198 | a._pack_next = b; 1199 | b._pack_prev = a; 1200 | b._pack_next = c; 1201 | c._pack_prev = b; 1202 | } 1203 | 1204 | function d3_layout_packSplice(a, b) { 1205 | a._pack_next = b; 1206 | b._pack_prev = a; 1207 | } 1208 | 1209 | function d3_layout_packIntersects(a, b) { 1210 | var dx = b.x - a.x, 1211 | dy = b.y - a.y, 1212 | dr = a.r + b.r; 1213 | return (dr * dr - dx * dx - dy * dy) > .001; // within epsilon 1214 | } 1215 | 1216 | function d3_layout_packCircle(nodes) { 1217 | var xMin = Infinity, 1218 | xMax = -Infinity, 1219 | yMin = Infinity, 1220 | yMax = -Infinity, 1221 | n = nodes.length, 1222 | a, b, c, j, k; 1223 | 1224 | function bound(node) { 1225 | xMin = Math.min(node.x - node.r, xMin); 1226 | xMax = Math.max(node.x + node.r, xMax); 1227 | yMin = Math.min(node.y - node.r, yMin); 1228 | yMax = Math.max(node.y + node.r, yMax); 1229 | } 1230 | 1231 | // Create node links. 1232 | nodes.forEach(d3_layout_packLink); 1233 | 1234 | // Create first node. 1235 | a = nodes[0]; 1236 | a.x = -a.r; 1237 | a.y = 0; 1238 | bound(a); 1239 | 1240 | // Create second node. 1241 | if (n > 1) { 1242 | b = nodes[1]; 1243 | b.x = b.r; 1244 | b.y = 0; 1245 | bound(b); 1246 | 1247 | // Create third node and build chain. 1248 | if (n > 2) { 1249 | c = nodes[2]; 1250 | d3_layout_packPlace(a, b, c); 1251 | bound(c); 1252 | d3_layout_packInsert(a, c); 1253 | a._pack_prev = c; 1254 | d3_layout_packInsert(c, b); 1255 | b = a._pack_next; 1256 | 1257 | // Now iterate through the rest. 1258 | for (var i = 3; i < n; i++) { 1259 | d3_layout_packPlace(a, b, c = nodes[i]); 1260 | 1261 | // Search for the closest intersection. 1262 | var isect = 0, s1 = 1, s2 = 1; 1263 | for (j = b._pack_next; j !== b; j = j._pack_next, s1++) { 1264 | if (d3_layout_packIntersects(j, c)) { 1265 | isect = 1; 1266 | break; 1267 | } 1268 | } 1269 | if (isect == 1) { 1270 | for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) { 1271 | if (d3_layout_packIntersects(k, c)) { 1272 | if (s2 < s1) { 1273 | isect = -1; 1274 | j = k; 1275 | } 1276 | break; 1277 | } 1278 | } 1279 | } 1280 | 1281 | // Update node chain. 1282 | if (isect == 0) { 1283 | d3_layout_packInsert(a, c); 1284 | b = c; 1285 | bound(c); 1286 | } else if (isect > 0) { 1287 | d3_layout_packSplice(a, j); 1288 | b = j; 1289 | i--; 1290 | } else { // isect < 0 1291 | d3_layout_packSplice(j, b); 1292 | a = j; 1293 | i--; 1294 | } 1295 | } 1296 | } 1297 | } 1298 | 1299 | // Re-center the circles and return the encompassing radius. 1300 | var cx = (xMin + xMax) / 2, 1301 | cy = (yMin + yMax) / 2, 1302 | cr = 0; 1303 | for (var i = 0; i < n; i++) { 1304 | var node = nodes[i]; 1305 | node.x -= cx; 1306 | node.y -= cy; 1307 | cr = Math.max(cr, node.r + Math.sqrt(node.x * node.x + node.y * node.y)); 1308 | } 1309 | 1310 | // Remove node links. 1311 | nodes.forEach(d3_layout_packUnlink); 1312 | 1313 | return cr; 1314 | } 1315 | 1316 | function d3_layout_packLink(node) { 1317 | node._pack_next = node._pack_prev = node; 1318 | } 1319 | 1320 | function d3_layout_packUnlink(node) { 1321 | delete node._pack_next; 1322 | delete node._pack_prev; 1323 | } 1324 | 1325 | function d3_layout_packTree(node) { 1326 | var children = node.children; 1327 | if (children && children.length) { 1328 | children.forEach(d3_layout_packTree); 1329 | node.r = d3_layout_packCircle(children); 1330 | } else { 1331 | node.r = Math.sqrt(node.value); 1332 | } 1333 | } 1334 | 1335 | function d3_layout_packTransform(node, x, y, k) { 1336 | var children = node.children; 1337 | node.x = (x += k * node.x); 1338 | node.y = (y += k * node.y); 1339 | node.r *= k; 1340 | if (children) { 1341 | var i = -1, n = children.length; 1342 | while (++i < n) d3_layout_packTransform(children[i], x, y, k); 1343 | } 1344 | } 1345 | 1346 | function d3_layout_packPlace(a, b, c) { 1347 | var db = a.r + c.r, 1348 | dx = b.x - a.x, 1349 | dy = b.y - a.y; 1350 | if (db && (dx || dy)) { 1351 | var da = b.r + c.r, 1352 | dc = Math.sqrt(dx * dx + dy * dy), 1353 | cos = Math.max(-1, Math.min(1, (db * db + dc * dc - da * da) / (2 * db * dc))), 1354 | theta = Math.acos(cos), 1355 | x = cos * (db /= dc), 1356 | y = Math.sin(theta) * db; 1357 | c.x = a.x + x * dx + y * dy; 1358 | c.y = a.y + x * dy - y * dx; 1359 | } else { 1360 | c.x = a.x + db; 1361 | c.y = a.y; 1362 | } 1363 | } 1364 | // Implements a hierarchical layout using the cluster (or dendogram) algorithm. 1365 | d3.layout.cluster = function() { 1366 | var hierarchy = d3.layout.hierarchy().sort(null).value(null), 1367 | separation = d3_layout_treeSeparation, 1368 | size = [1, 1]; // width, height 1369 | 1370 | function cluster(d, i) { 1371 | var nodes = hierarchy.call(this, d, i), 1372 | root = nodes[0], 1373 | previousNode, 1374 | x = 0, 1375 | kx, 1376 | ky; 1377 | 1378 | // First walk, computing the initial x & y values. 1379 | d3_layout_treeVisitAfter(root, function(node) { 1380 | var children = node.children; 1381 | if (children && children.length) { 1382 | node.x = d3_layout_clusterX(children); 1383 | node.y = d3_layout_clusterY(children); 1384 | } else { 1385 | node.x = previousNode ? x += separation(node, previousNode) : 0; 1386 | node.y = 0; 1387 | previousNode = node; 1388 | } 1389 | }); 1390 | 1391 | // Compute the left-most, right-most, and depth-most nodes for extents. 1392 | var left = d3_layout_clusterLeft(root), 1393 | right = d3_layout_clusterRight(root), 1394 | x0 = left.x - separation(left, right) / 2, 1395 | x1 = right.x + separation(right, left) / 2; 1396 | 1397 | // Second walk, normalizing x & y to the desired size. 1398 | d3_layout_treeVisitAfter(root, function(node) { 1399 | node.x = (node.x - x0) / (x1 - x0) * size[0]; 1400 | node.y = (1 - node.y / root.y) * size[1]; 1401 | }); 1402 | 1403 | return nodes; 1404 | } 1405 | 1406 | cluster.separation = function(x) { 1407 | if (!arguments.length) return separation; 1408 | separation = x; 1409 | return cluster; 1410 | }; 1411 | 1412 | cluster.size = function(x) { 1413 | if (!arguments.length) return size; 1414 | size = x; 1415 | return cluster; 1416 | }; 1417 | 1418 | return d3_layout_hierarchyRebind(cluster, hierarchy); 1419 | }; 1420 | 1421 | function d3_layout_clusterY(children) { 1422 | return 1 + d3.max(children, function(child) { 1423 | return child.y; 1424 | }); 1425 | } 1426 | 1427 | function d3_layout_clusterX(children) { 1428 | return children.reduce(function(x, child) { 1429 | return x + child.x; 1430 | }, 0) / children.length; 1431 | } 1432 | 1433 | function d3_layout_clusterLeft(node) { 1434 | var children = node.children; 1435 | return children && children.length ? d3_layout_clusterLeft(children[0]) : node; 1436 | } 1437 | 1438 | function d3_layout_clusterRight(node) { 1439 | var children = node.children, n; 1440 | return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node; 1441 | } 1442 | // Node-link tree diagram using the Reingold-Tilford "tidy" algorithm 1443 | d3.layout.tree = function() { 1444 | var hierarchy = d3.layout.hierarchy().sort(null).value(null), 1445 | separation = d3_layout_treeSeparation, 1446 | size = [1, 1]; // width, height 1447 | 1448 | function tree(d, i) { 1449 | var nodes = hierarchy.call(this, d, i), 1450 | root = nodes[0]; 1451 | 1452 | function firstWalk(node, previousSibling) { 1453 | var children = node.children, 1454 | layout = node._tree; 1455 | if (children && (n = children.length)) { 1456 | var n, 1457 | firstChild = children[0], 1458 | previousChild, 1459 | ancestor = firstChild, 1460 | child, 1461 | i = -1; 1462 | while (++i < n) { 1463 | child = children[i]; 1464 | firstWalk(child, previousChild); 1465 | ancestor = apportion(child, previousChild, ancestor); 1466 | previousChild = child; 1467 | } 1468 | d3_layout_treeShift(node); 1469 | var midpoint = .5 * (firstChild._tree.prelim + child._tree.prelim); 1470 | if (previousSibling) { 1471 | layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); 1472 | layout.mod = layout.prelim - midpoint; 1473 | } else { 1474 | layout.prelim = midpoint; 1475 | } 1476 | } else { 1477 | if (previousSibling) { 1478 | layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); 1479 | } 1480 | } 1481 | } 1482 | 1483 | function secondWalk(node, x) { 1484 | node.x = node._tree.prelim + x; 1485 | var children = node.children; 1486 | if (children && (n = children.length)) { 1487 | var i = -1, 1488 | n; 1489 | x += node._tree.mod; 1490 | while (++i < n) { 1491 | secondWalk(children[i], x); 1492 | } 1493 | } 1494 | } 1495 | 1496 | function apportion(node, previousSibling, ancestor) { 1497 | if (previousSibling) { 1498 | var vip = node, 1499 | vop = node, 1500 | vim = previousSibling, 1501 | vom = node.parent.children[0], 1502 | sip = vip._tree.mod, 1503 | sop = vop._tree.mod, 1504 | sim = vim._tree.mod, 1505 | som = vom._tree.mod, 1506 | shift; 1507 | while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) { 1508 | vom = d3_layout_treeLeft(vom); 1509 | vop = d3_layout_treeRight(vop); 1510 | vop._tree.ancestor = node; 1511 | shift = vim._tree.prelim + sim - vip._tree.prelim - sip + separation(vim, vip); 1512 | if (shift > 0) { 1513 | d3_layout_treeMove(d3_layout_treeAncestor(vim, node, ancestor), node, shift); 1514 | sip += shift; 1515 | sop += shift; 1516 | } 1517 | sim += vim._tree.mod; 1518 | sip += vip._tree.mod; 1519 | som += vom._tree.mod; 1520 | sop += vop._tree.mod; 1521 | } 1522 | if (vim && !d3_layout_treeRight(vop)) { 1523 | vop._tree.thread = vim; 1524 | vop._tree.mod += sim - sop; 1525 | } 1526 | if (vip && !d3_layout_treeLeft(vom)) { 1527 | vom._tree.thread = vip; 1528 | vom._tree.mod += sip - som; 1529 | ancestor = node; 1530 | } 1531 | } 1532 | return ancestor; 1533 | } 1534 | 1535 | // Initialize temporary layout variables. 1536 | d3_layout_treeVisitAfter(root, function(node, previousSibling) { 1537 | node._tree = { 1538 | ancestor: node, 1539 | prelim: 0, 1540 | mod: 0, 1541 | change: 0, 1542 | shift: 0, 1543 | number: previousSibling ? previousSibling._tree.number + 1 : 0 1544 | }; 1545 | }); 1546 | 1547 | // Compute the layout using Buchheim et al.'s algorithm. 1548 | firstWalk(root); 1549 | secondWalk(root, -root._tree.prelim); 1550 | 1551 | // Compute the left-most, right-most, and depth-most nodes for extents. 1552 | var left = d3_layout_treeSearch(root, d3_layout_treeLeftmost), 1553 | right = d3_layout_treeSearch(root, d3_layout_treeRightmost), 1554 | deep = d3_layout_treeSearch(root, d3_layout_treeDeepest), 1555 | x0 = left.x - separation(left, right) / 2, 1556 | x1 = right.x + separation(right, left) / 2, 1557 | y1 = deep.depth || 1; 1558 | 1559 | // Clear temporary layout variables; transform x and y. 1560 | d3_layout_treeVisitAfter(root, function(node) { 1561 | node.x = (node.x - x0) / (x1 - x0) * size[0]; 1562 | node.y = node.depth / y1 * size[1]; 1563 | delete node._tree; 1564 | }); 1565 | 1566 | return nodes; 1567 | } 1568 | 1569 | tree.separation = function(x) { 1570 | if (!arguments.length) return separation; 1571 | separation = x; 1572 | return tree; 1573 | }; 1574 | 1575 | tree.size = function(x) { 1576 | if (!arguments.length) return size; 1577 | size = x; 1578 | return tree; 1579 | }; 1580 | 1581 | return d3_layout_hierarchyRebind(tree, hierarchy); 1582 | }; 1583 | 1584 | function d3_layout_treeSeparation(a, b) { 1585 | return a.parent == b.parent ? 1 : 2; 1586 | } 1587 | 1588 | // function d3_layout_treeSeparationRadial(a, b) { 1589 | // return (a.parent == b.parent ? 1 : 2) / a.depth; 1590 | // } 1591 | 1592 | function d3_layout_treeLeft(node) { 1593 | var children = node.children; 1594 | return children && children.length ? children[0] : node._tree.thread; 1595 | } 1596 | 1597 | function d3_layout_treeRight(node) { 1598 | var children = node.children, 1599 | n; 1600 | return children && (n = children.length) ? children[n - 1] : node._tree.thread; 1601 | } 1602 | 1603 | function d3_layout_treeSearch(node, compare) { 1604 | var children = node.children; 1605 | if (children && (n = children.length)) { 1606 | var child, 1607 | n, 1608 | i = -1; 1609 | while (++i < n) { 1610 | if (compare(child = d3_layout_treeSearch(children[i], compare), node) > 0) { 1611 | node = child; 1612 | } 1613 | } 1614 | } 1615 | return node; 1616 | } 1617 | 1618 | function d3_layout_treeRightmost(a, b) { 1619 | return a.x - b.x; 1620 | } 1621 | 1622 | function d3_layout_treeLeftmost(a, b) { 1623 | return b.x - a.x; 1624 | } 1625 | 1626 | function d3_layout_treeDeepest(a, b) { 1627 | return a.depth - b.depth; 1628 | } 1629 | 1630 | function d3_layout_treeVisitAfter(node, callback) { 1631 | function visit(node, previousSibling) { 1632 | var children = node.children; 1633 | if (children && (n = children.length)) { 1634 | var child, 1635 | previousChild = null, 1636 | i = -1, 1637 | n; 1638 | while (++i < n) { 1639 | child = children[i]; 1640 | visit(child, previousChild); 1641 | previousChild = child; 1642 | } 1643 | } 1644 | callback(node, previousSibling); 1645 | } 1646 | visit(node, null); 1647 | } 1648 | 1649 | function d3_layout_treeShift(node) { 1650 | var shift = 0, 1651 | change = 0, 1652 | children = node.children, 1653 | i = children.length, 1654 | child; 1655 | while (--i >= 0) { 1656 | child = children[i]._tree; 1657 | child.prelim += shift; 1658 | child.mod += shift; 1659 | shift += child.shift + (change += child.change); 1660 | } 1661 | } 1662 | 1663 | function d3_layout_treeMove(ancestor, node, shift) { 1664 | ancestor = ancestor._tree; 1665 | node = node._tree; 1666 | var change = shift / (node.number - ancestor.number); 1667 | ancestor.change += change; 1668 | node.change -= change; 1669 | node.shift += shift; 1670 | node.prelim += shift; 1671 | node.mod += shift; 1672 | } 1673 | 1674 | function d3_layout_treeAncestor(vim, node, ancestor) { 1675 | return vim._tree.ancestor.parent == node.parent 1676 | ? vim._tree.ancestor 1677 | : ancestor; 1678 | } 1679 | // Squarified Treemaps by Mark Bruls, Kees Huizing, and Jarke J. van Wijk 1680 | // Modified to support a target aspect ratio by Jeff Heer 1681 | d3.layout.treemap = function() { 1682 | var hierarchy = d3.layout.hierarchy(), 1683 | round = Math.round, 1684 | size = [1, 1], // width, height 1685 | padding = null, 1686 | pad = d3_layout_treemapPadNull, 1687 | sticky = false, 1688 | stickies, 1689 | ratio = 0.5 * (1 + Math.sqrt(5)); // golden ratio 1690 | 1691 | // Compute the area for each child based on value & scale. 1692 | function scale(children, k) { 1693 | var i = -1, 1694 | n = children.length, 1695 | child, 1696 | area; 1697 | while (++i < n) { 1698 | area = (child = children[i]).value * (k < 0 ? 0 : k); 1699 | child.area = isNaN(area) || area <= 0 ? 0 : area; 1700 | } 1701 | } 1702 | 1703 | // Recursively arranges the specified node's children into squarified rows. 1704 | function squarify(node) { 1705 | var children = node.children; 1706 | if (children && children.length) { 1707 | var rect = pad(node), 1708 | row = [], 1709 | remaining = children.slice(), // copy-on-write 1710 | child, 1711 | best = Infinity, // the best row score so far 1712 | score, // the current row score 1713 | u = Math.min(rect.dx, rect.dy), // initial orientation 1714 | n; 1715 | scale(remaining, rect.dx * rect.dy / node.value); 1716 | row.area = 0; 1717 | while ((n = remaining.length) > 0) { 1718 | row.push(child = remaining[n - 1]); 1719 | row.area += child.area; 1720 | if ((score = worst(row, u)) <= best) { // continue with this orientation 1721 | remaining.pop(); 1722 | best = score; 1723 | } else { // abort, and try a different orientation 1724 | row.area -= row.pop().area; 1725 | position(row, u, rect, false); 1726 | u = Math.min(rect.dx, rect.dy); 1727 | row.length = row.area = 0; 1728 | best = Infinity; 1729 | } 1730 | } 1731 | if (row.length) { 1732 | position(row, u, rect, true); 1733 | row.length = row.area = 0; 1734 | } 1735 | children.forEach(squarify); 1736 | } 1737 | } 1738 | 1739 | // Recursively resizes the specified node's children into existing rows. 1740 | // Preserves the existing layout! 1741 | function stickify(node) { 1742 | var children = node.children; 1743 | if (children && children.length) { 1744 | var rect = pad(node), 1745 | remaining = children.slice(), // copy-on-write 1746 | child, 1747 | row = []; 1748 | scale(remaining, rect.dx * rect.dy / node.value); 1749 | row.area = 0; 1750 | while (child = remaining.pop()) { 1751 | row.push(child); 1752 | row.area += child.area; 1753 | if (child.z != null) { 1754 | position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length); 1755 | row.length = row.area = 0; 1756 | } 1757 | } 1758 | children.forEach(stickify); 1759 | } 1760 | } 1761 | 1762 | // Computes the score for the specified row, as the worst aspect ratio. 1763 | function worst(row, u) { 1764 | var s = row.area, 1765 | r, 1766 | rmax = 0, 1767 | rmin = Infinity, 1768 | i = -1, 1769 | n = row.length; 1770 | while (++i < n) { 1771 | if (!(r = row[i].area)) continue; 1772 | if (r < rmin) rmin = r; 1773 | if (r > rmax) rmax = r; 1774 | } 1775 | s *= s; 1776 | u *= u; 1777 | return s 1778 | ? Math.max((u * rmax * ratio) / s, s / (u * rmin * ratio)) 1779 | : Infinity; 1780 | } 1781 | 1782 | // Positions the specified row of nodes. Modifies `rect`. 1783 | function position(row, u, rect, flush) { 1784 | var i = -1, 1785 | n = row.length, 1786 | x = rect.x, 1787 | y = rect.y, 1788 | v = u ? round(row.area / u) : 0, 1789 | o; 1790 | if (u == rect.dx) { // horizontal subdivision 1791 | if (flush || v > rect.dy) v = v ? rect.dy : 0; // over+underflow 1792 | while (++i < n) { 1793 | o = row[i]; 1794 | o.x = x; 1795 | o.y = y; 1796 | o.dy = v; 1797 | x += o.dx = v ? round(o.area / v) : 0; 1798 | } 1799 | o.z = true; 1800 | o.dx += rect.x + rect.dx - x; // rounding error 1801 | rect.y += v; 1802 | rect.dy -= v; 1803 | } else { // vertical subdivision 1804 | if (flush || v > rect.dx) v = v ? rect.dx : 0; // over+underflow 1805 | while (++i < n) { 1806 | o = row[i]; 1807 | o.x = x; 1808 | o.y = y; 1809 | o.dx = v; 1810 | y += o.dy = v ? round(o.area / v) : 0; 1811 | } 1812 | o.z = false; 1813 | o.dy += rect.y + rect.dy - y; // rounding error 1814 | rect.x += v; 1815 | rect.dx -= v; 1816 | } 1817 | } 1818 | 1819 | function treemap(d) { 1820 | var nodes = stickies || hierarchy(d), 1821 | root = nodes[0]; 1822 | root.x = 0; 1823 | root.y = 0; 1824 | root.dx = size[0]; 1825 | root.dy = size[1]; 1826 | if (stickies) hierarchy.revalue(root); 1827 | scale([root], root.dx * root.dy / root.value); 1828 | (stickies ? stickify : squarify)(root); 1829 | if (sticky) stickies = nodes; 1830 | return nodes; 1831 | } 1832 | 1833 | treemap.size = function(x) { 1834 | if (!arguments.length) return size; 1835 | size = x; 1836 | return treemap; 1837 | }; 1838 | 1839 | treemap.padding = function(x) { 1840 | if (!arguments.length) return padding; 1841 | 1842 | function padFunction(node) { 1843 | var p = x.call(treemap, node, node.depth); 1844 | return p == null 1845 | ? d3_layout_treemapPadNull(node) 1846 | : d3_layout_treemapPad(node, typeof p === "number" ? [p, p, p, p] : p); 1847 | } 1848 | 1849 | function padConstant(node) { 1850 | return d3_layout_treemapPad(node, x); 1851 | } 1852 | 1853 | var type; 1854 | pad = (padding = x) == null ? d3_layout_treemapPadNull 1855 | : (type = typeof x) === "function" ? padFunction 1856 | : type === "number" ? (x = [x, x, x, x], padConstant) 1857 | : padConstant; 1858 | return treemap; 1859 | }; 1860 | 1861 | treemap.round = function(x) { 1862 | if (!arguments.length) return round != Number; 1863 | round = x ? Math.round : Number; 1864 | return treemap; 1865 | }; 1866 | 1867 | treemap.sticky = function(x) { 1868 | if (!arguments.length) return sticky; 1869 | sticky = x; 1870 | stickies = null; 1871 | return treemap; 1872 | }; 1873 | 1874 | treemap.ratio = function(x) { 1875 | if (!arguments.length) return ratio; 1876 | ratio = x; 1877 | return treemap; 1878 | }; 1879 | 1880 | return d3_layout_hierarchyRebind(treemap, hierarchy); 1881 | }; 1882 | 1883 | function d3_layout_treemapPadNull(node) { 1884 | return {x: node.x, y: node.y, dx: node.dx, dy: node.dy}; 1885 | } 1886 | 1887 | function d3_layout_treemapPad(node, padding) { 1888 | var x = node.x + padding[3], 1889 | y = node.y + padding[0], 1890 | dx = node.dx - padding[1] - padding[3], 1891 | dy = node.dy - padding[0] - padding[2]; 1892 | if (dx < 0) { x += dx / 2; dx = 0; } 1893 | if (dy < 0) { y += dy / 2; dy = 0; } 1894 | return {x: x, y: y, dx: dx, dy: dy}; 1895 | } 1896 | })(); 1897 | -------------------------------------------------------------------------------- /dataConverter.js: -------------------------------------------------------------------------------- 1 | var convertToJSON = function(data, origin) { 2 | return (origin == 'cloc') ? convertFromClocToJSON(data) : convertFromWcToJSON(data); 3 | }; 4 | 5 | /** 6 | * Convert the output of cloc in csv to JSON format 7 | * 8 | * > cloc . --csv --exclude-dir=vendor,tmp --by-file --report-file=data.cloc 9 | */ 10 | var convertFromClocToJSON = function(data) { 11 | var lines = data.split("\n"); 12 | lines.shift(); // drop the header line 13 | 14 | var json = {}; 15 | lines.forEach(function(line) { 16 | var cols = line.split(','); 17 | var filename = cols[1]; 18 | if (!filename) return; 19 | var elements = filename.split(/[\/\\]/); 20 | var current = json; 21 | elements.forEach(function(element) { 22 | if (!current[element]) { 23 | current[element] = {}; 24 | } 25 | current = current[element]; 26 | }); 27 | current.language = cols[0]; 28 | current.size = parseInt(cols[4], 10); 29 | }); 30 | 31 | json = getChildren(json)[0]; 32 | json.name = 'root'; 33 | 34 | return json; 35 | }; 36 | 37 | /** 38 | * Convert the output of wc to JSON format 39 | * 40 | * > git ls-files | xargs wc -l 41 | */ 42 | var convertFromWcToJSON = function(data) { 43 | var lines = data.split("\n"); 44 | 45 | var json = {}; 46 | var filename, size, cols, elements, current; 47 | lines.forEach(function(line) { 48 | cols = line.trim().split(' '); 49 | size = parseInt(cols[0], 10); 50 | if (!size) return; 51 | filename = cols[1]; 52 | if (filename === "total") return; 53 | if (!filename) return; 54 | elements = filename.split(/[\/\\]/); 55 | current = json; 56 | elements.forEach(function(element) { 57 | if (!current[element]) { 58 | current[element] = {}; 59 | } 60 | current = current[element]; 61 | }); 62 | current.size = size; 63 | }); 64 | 65 | json.children = getChildren(json); 66 | json.name = 'root'; 67 | 68 | return json; 69 | }; 70 | 71 | /** 72 | * Convert a simple json object into another specifying children as an array 73 | * Works recursively 74 | * 75 | * example input: 76 | * { a: { b: { c: { size: 12 }, d: { size: 34 } }, e: { size: 56 } } } 77 | * example output 78 | * { name: a, children: [ 79 | * { name: b, children: [ 80 | * { name: c, size: 12 }, 81 | * { name: d, size: 34 } 82 | * ] }, 83 | * { name: e, size: 56 } 84 | * ] } } 85 | */ 86 | var getChildren = function(json) { 87 | var children = []; 88 | if (json.language) return children; 89 | for (var key in json) { 90 | var child = { name: key }; 91 | if (json[key].size) { 92 | // value node 93 | child.size = json[key].size; 94 | child.language = json[key].language; 95 | } else { 96 | // children node 97 | var childChildren = getChildren(json[key]); 98 | if (childChildren) child.children = childChildren; 99 | } 100 | children.push(child); 101 | delete json[key]; 102 | } 103 | return children; 104 | }; 105 | 106 | // Recursively count all elements in a tree 107 | var countElements = function(node) { 108 | var nbElements = 1; 109 | if (node.children) { 110 | nbElements += node.children.reduce(function(p, v) { return p + countElements(v); }, 0); 111 | } 112 | return nbElements; 113 | }; 114 | --------------------------------------------------------------------------------