UNPKG

d3

Version:

A small, free JavaScript library for manipulating documents based on data.

1,533 lines (1,347 loc) 37.4 kB
(function(){d3.layout = {}; d3.layout.chord = function() { var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords; function relayout() { var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j; chords = []; groups = []; // Compute the sum. k = 0, i = -1; while (++i < n) { x = 0, j = -1; while (++j < n) { x += matrix[i][j]; } groupSums.push(x); subgroupIndex.push(d3.range(n)); k += x; } // Sort groups… if (sortGroups) { groupIndex.sort(function(a, b) { return sortGroups(groupSums[a], groupSums[b]); }); } // Sort subgroups… if (sortSubgroups) { subgroupIndex.forEach(function(d, i) { d.sort(function(a, b) { return sortSubgroups(matrix[i][a], matrix[i][b]); }); }); } // Convert the sum to scaling factor for [0, 2pi]. // TODO Allow start and end angle to be specified. // TODO Allow padding to be specified as percentage? k = (2 * Math.PI - padding * n) / k; // Compute the start and end angle for each group and subgroup. x = 0, i = -1; while (++i < n) { x0 = x, j = -1; while (++j < n) { var di = groupIndex[i], dj = subgroupIndex[i][j], v = matrix[di][dj]; subgroups[di + "-" + dj] = { index: di, subindex: dj, startAngle: x, endAngle: x += v * k, value: v }; } groups.push({ index: di, startAngle: x0, endAngle: x, value: (x - x0) / k }); x += padding; } // Generate chords for each (non-empty) subgroup-subgroup link. i = -1; while (++i < n) { j = i - 1; while (++j < n) { var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i]; if (source.value || target.value) { chords.push({ source: source, target: target }) } } } if (sortChords) resort(); } function resort() { chords.sort(function(a, b) { a = Math.min(a.source.value, a.target.value); b = Math.min(b.source.value, b.target.value); return sortChords(a, b); }); } chord.matrix = function(x) { if (!arguments.length) return matrix; n = (matrix = x) && matrix.length; chords = groups = null; return chord; }; chord.padding = function(x) { if (!arguments.length) return padding; padding = x; chords = groups = null; return chord; }; chord.sortGroups = function(x) { if (!arguments.length) return sortGroups; sortGroups = x; chords = groups = null; return chord; }; chord.sortSubgroups = function(x) { if (!arguments.length) return sortSubgroups; sortSubgroups = x; chords = null; return chord; }; chord.sortChords = function(x) { if (!arguments.length) return sortChords; sortChords = x; if (chords) resort(); return chord; }; chord.chords = function() { if (!chords) relayout(); return chords; }; chord.groups = function() { if (!groups) relayout(); return groups; }; return chord; }; // A rudimentary force layout using Gauss-Seidel. d3.layout.force = function() { var force = {}, event = d3.dispatch("tick"), size = [1, 1], alpha, drag = .9, distance = 30, charge = -60, gravity = .001, theta = .8, interval, nodes, links, distances; function accumulate(quad) { var cx = 0, cy = 0; quad.count = 0; if (!quad.leaf) { quad.nodes.forEach(function(c) { accumulate(c); quad.count += c.count; cx += c.count * c.cx; cy += c.count * c.cy; }); } if (quad.point) { quad.count++; cx += quad.point.x; cy += quad.point.y; } quad.cx = cx / quad.count; quad.cy = cy / quad.count; } function repulse(node, kc) { return function(quad, x1, y1, x2, y2) { if (quad.point != node) { var dx = (quad.cx - node.x) || Math.random(), dy = (quad.cy - node.y) || Math.random(), dn = 1 / Math.sqrt(dx * dx + dy * dy); /* Barnes-Hut criterion. */ if ((x2 - x1) * dn < theta) { var k = kc * quad.count * dn * dn; node.fx += dx * k; node.fy += dy * k; return true; } if (quad.point) { var k = kc * dn * dn; node.fx += dx * k; node.fy += dy * k; } } }; } function tick() { var n = nodes.length, m = links.length, q = d3.geom.quadtree(nodes), i, // current index o, // current object s, // current source t, // current target l, // current distance x, // x-distance y; // y-distance // reset forces i = -1; while (++i < n) { (o = nodes[i]).fx = o.fy = 0; } // gauss-seidel relaxation for links for (i = 0; i < m; ++i) { o = links[i]; s = o.source; t = o.target; x = t.x - s.x; y = t.y - s.y; if (l = Math.sqrt(x * x + y * y)) { l = alpha * (l - distance) / l; x *= l; y *= l; t.x -= x; t.y -= y; s.x += x; s.y += y; } } // compute quadtree center of mass accumulate(q); // apply gravity forces var kg = alpha * gravity; x = size[0] / 2; y = size[1] / 2; i = -1; while (++i < n) { o = nodes[i]; s = x - o.x; t = y - o.y; l = kg * Math.sqrt(s * s + t * t); s *= l; t *= l; o.fx += s; o.fy += t; } // apply charge forces var kc = alpha * charge; i = -1; while (++i < n) { q.visit(repulse(nodes[i], kc)); } // position verlet integration i = -1; while (++i < n) { o = nodes[i]; if (o.fixed) { o.x = o.px; o.y = o.py; } else { x = o.px - (o.px = o.x); y = o.py - (o.py = o.y); o.x += o.fx - x * drag; o.y += o.fy - y * drag; } } event.tick.dispatch({type: "tick"}); // simulated annealing, basically return (alpha *= .99) < .005; } force.on = function(type, listener) { event[type].add(listener); return force; }; force.nodes = function(x) { if (!arguments.length) return nodes; nodes = x; return force; }; force.links = function(x) { if (!arguments.length) return links; links = x; return force; }; force.size = function(x) { if (!arguments.length) return size; size = x; return force; }; force.distance = function(x) { if (!arguments.length) return distance; distance = x; return force; }; force.drag = function(x) { if (!arguments.length) return drag; drag = x; return force; }; force.charge = function(x) { if (!arguments.length) return charge; charge = x; return force; }; force.gravity = function(x) { if (!arguments.length) return gravity; gravity = x; return force; }; force.theta = function(x) { if (!arguments.length) return theta; theta = x; return force; }; force.start = function() { var i, n = nodes.length, m = links.length, w = size[0], h = size[1], o; // TODO initialize positions of new nodes using constraints (links) for (i = 0; i < n; ++i) { o = nodes[i]; if (isNaN(o.x)) o.x = Math.random() * w; if (isNaN(o.y)) o.y = Math.random() * h; if (isNaN(o.px)) o.px = o.x; if (isNaN(o.py)) o.py = o.y; } for (i = 0; i < m; ++i) { o = links[i]; if (typeof o.source == "number") o.source = nodes[o.source]; if (typeof o.target == "number") o.target = nodes[o.target]; } return force.resume(); }; force.resume = function() { alpha = .1; d3.timer(tick); return force; }; force.stop = function() { alpha = 0; return force; }; // use `node.call(force.drag)` to make nodes draggable force.drag = function() { this .on("mouseover", d3_layout_forceDragOver) .on("mouseout", d3_layout_forceDragOut) .on("mousedown", d3_layout_forceDragDown); d3.select(window) .on("mousemove", dragmove) .on("mouseup", dragup); return force; }; function dragmove() { if (!d3_layout_forceDragNode) return; var m = d3.svg.mouse(d3_layout_forceDragElement); d3_layout_forceDragNode.px = m[0]; d3_layout_forceDragNode.py = m[1]; force.resume(); // restart annealing } function dragup() { if (!d3_layout_forceDragNode) return; dragmove(); d3_layout_forceDragNode.fixed = false; d3_layout_forceDragNode = d3_layout_forceDragElement = null; } return force; }; var d3_layout_forceDragNode, d3_layout_forceDragElement; function d3_layout_forceDragOver(d) { d.fixed = true; } function d3_layout_forceDragOut(d) { if (d !== d3_layout_forceDragNode) { d.fixed = false; } } function d3_layout_forceDragDown(d) { (d3_layout_forceDragNode = d).fixed = true; d3_layout_forceDragElement = this; d3.event.stopPropagation(); d3.event.preventDefault(); } d3.layout.partition = function() { var hierarchy = d3.layout.hierarchy(), size = [1, 1]; // width, height function position(node, x, dx, dy) { var children = node.children; node.x = x; node.y = node.depth * dy; node.dx = dx; node.dy = dy; if (children) { var i = -1, n = children.length, c, d; dx /= node.value; while (++i < n) { position(c = children[i], x, d = c.value * dx, dy); x += d; } } } function depth(node) { var children = node.children, d = 0; if (children) { var i = -1, n = children.length; while (++i < n) d = Math.max(d, depth(children[i])); } return 1 + d; } function partition(d, i) { var nodes = hierarchy.call(this, d, i); position(nodes[0], 0, size[0], size[1] / depth(nodes[0])); return nodes; } partition.sort = d3.rebind(partition, hierarchy.sort); partition.children = d3.rebind(partition, hierarchy.children); partition.value = d3.rebind(partition, hierarchy.value); partition.size = function(x) { if (!arguments.length) return size; size = x; return partition; }; return partition; }; d3.layout.pie = function() { var value = Number, sort = null, startAngle = 0, endAngle = 2 * Math.PI; function pie(data, i) { // Compute the start angle. var a = +(typeof startAngle == "function" ? startAngle.apply(this, arguments) : startAngle); // Compute the angular range (end - start). var k = (typeof endAngle == "function" ? endAngle.apply(this, arguments) : endAngle) - startAngle; // Optionally sort the data. var index = d3.range(data.length); if (sort != null) index.sort(function(i, j) { return sort(data[i], data[j]); }); // Compute the numeric values for each data element. var values = data.map(value); // Convert k into a scale factor from value to angle, using the sum. k /= values.reduce(function(p, d) { return p + d; }, 0); // Compute the arcs! var arcs = index.map(function(i) { return { value: d = values[i], startAngle: a, endAngle: a += d * k }; }); // Return the arcs in the original data's order. return data.map(function(d, i) { return arcs[index[i]]; }); } /** * Specifies the value function *x*, which returns a nonnegative numeric value * for each datum. The default value function is `Number`. The value function * is passed two arguments: the current datum and the current index. */ pie.value = function(x) { if (!arguments.length) return value; value = x; return pie; }; /** * Specifies a sort comparison operator *x*. The comparator is passed two data * elements from the data array, a and b; it returns a negative value if a is * less than b, a positive value if a is greater than b, and zero if a equals * b. */ pie.sort = function(x) { if (!arguments.length) return sort; sort = x; return pie; }; /** * Specifies the overall start angle of the pie chart. Defaults to 0. The * start angle can be specified either as a constant or as a function; in the * case of a function, it is evaluated once per array (as opposed to per * element). */ pie.startAngle = function(x) { if (!arguments.length) return startAngle; startAngle = x; return pie; }; /** * Specifies the overall end angle of the pie chart. Defaults to 2π. The * end angle can be specified either as a constant or as a function; in the * case of a function, it is evaluated once per array (as opposed to per * element). */ pie.endAngle = function(x) { if (!arguments.length) return endAngle; endAngle = x; return pie; }; return pie; }; // data is two-dimensional array of x,y; we populate y0 // TODO perhaps make the `x`, `y` and `y0` structure customizable d3.layout.stack = function() { var order = "default", offset = "zero"; function stack(data) { var n = data.length, m = data[0].length, i, j, y0; // compute the order of series var index = d3_layout_stackOrders[order](data); // set y0 on the baseline d3_layout_stackOffsets[offset](data, index); // propagate offset to other series for (j = 0; j < m; ++j) { for (i = 1, y0 = data[index[0]][j].y0; i < n; ++i) { data[index[i]][j].y0 = y0 += data[index[i - 1]][j].y; } } return data; } stack.order = function(x) { if (!arguments.length) return order; order = x; return stack; }; stack.offset = function(x) { if (!arguments.length) return offset; offset = x; return stack; }; return stack; } var d3_layout_stackOrders = { "inside-out": function(data) { var n = data.length, i, j, max = data.map(d3_layout_stackMaxIndex), sums = data.map(d3_layout_stackReduceSum), index = d3.range(n).sort(function(a, b) { return max[a] - max[b]; }), top = 0, bottom = 0, tops = [], bottoms = []; for (i = 0; i < n; i++) { j = index[i]; if (top < bottom) { top += sums[j]; tops.push(j); } else { bottom += sums[j]; bottoms.push(j); } } return bottoms.reverse().concat(tops); }, "reverse": function(data) { return d3.range(data.length).reverse(); }, "default": function(data) { return d3.range(data.length); } }; var d3_layout_stackOffsets = { "silhouette": function(data, index) { var n = data.length, m = data[0].length, sums = [], max = 0, i, j, o; for (j = 0; j < m; ++j) { for (i = 0, o = 0; i < n; i++) o += data[i][j].y; if (o > max) max = o; sums.push(o); } for (j = 0, i = index[0]; j < m; ++j) { data[i][j].y0 = (max - sums[j]) / 2; } }, "wiggle": function(data, index) { var n = data.length, x = data[0], m = x.length, max = 0, i, j, k, ii, ik, i0 = index[0], s1, s2, s3, dx, o, o0; data[i0][0].y0 = o = o0 = 0; for (j = 1; j < m; ++j) { for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j].y; for (i = 0, s2 = 0, dx = x[j].x - x[j - 1].x; i < n; ++i) { for (k = 0, ii = index[i], s3 = (data[ii][j].y - data[ii][j - 1].y) / (2 * dx); k < i; ++k) { s3 += (data[ik = index[k]][j].y - data[ik][j - 1].y) / dx; } s2 += s3 * data[ii][j].y; } data[i0][j].y0 = o -= s1 ? s2 / s1 * dx : 0; if (o < o0) o0 = o; } for (j = 0; j < m; ++j) data[i0][j].y0 -= o0; }, "zero": function(data, index) { var j = 0, m = data[0].length, i0 = index[0]; for (; j < m; ++j) data[i0][j].y0 = 0; } }; function d3_layout_stackReduceSum(d) { return d.reduce(d3_layout_stackSum, 0); } function d3_layout_stackMaxIndex(array) { var i = 1, j = 0, v = array[0].y, k, n = array.length; for (; i < n; ++i) { if ((k = array[i].y) > v) { j = i; v = k; } } return j; } function d3_layout_stackSum(p, d) { return p + d.y; } d3.layout.hierarchy = function() { var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue; // Recursively compute the node depth and value. // Also converts the data representation into a standard hierarchy structure. function recurse(data, depth, nodes) { var datas = children.call(hierarchy, data, depth), node = {depth: depth, data: data}; nodes.push(node); if (datas) { var i = -1, n = datas.length, c = node.children = [], v = 0, j = depth + 1; while (++i < n) { d = recurse(datas[i], j, nodes); d.parent = node; c.push(d); v += d.value; } if (sort) c.sort(sort); if (value) node.value = v; } else if (value) { node.value = value.call(hierarchy, data, depth); } return node; } // Recursively re-evaluates the node value. function revalue(node, depth) { var children = node.children, v = 0; if (children) { var i = -1, n = children.length, j = depth + 1; while (++i < n) v += revalue(children[i], j); } else if (value) { v = value.call(hierarchy, node.data, depth); } if (value) node.value = v; return v; } function hierarchy(d) { var nodes = []; recurse(d, 0, nodes); return nodes; } hierarchy.sort = function(x) { if (!arguments.length) return sort; sort = x; return hierarchy; }; hierarchy.children = function(x) { if (!arguments.length) return children; children = x; return hierarchy; }; hierarchy.value = function(x) { if (!arguments.length) return value; value = x; return hierarchy; }; // Re-evaluates the `value` property for the specified hierarchy. hierarchy.revalue = function(root) { revalue(root, 0); return root; }; return hierarchy; } function d3_layout_hierarchyChildren(d) { return d.children; } function d3_layout_hierarchyValue(d) { return d.value; } function d3_layout_hierarchySort(a, b) { return b.value - a.value; } d3.layout.pack = function() { var hierarchy = d3.layout.hierarchy(), size = [1, 1]; function pack(d, i) { var nodes = hierarchy.call(this, d, i), root = nodes[0]; // Recursively compute the layout. root.x = 0; root.y = 0; d3_layout_packTree(root); // Scale the layout to fit the requested size. var w = size[0], h = size[1], k = 1 / Math.max(2 * root.r / w, 2 * root.r / h); d3_layout_packTransform(root, w / 2, h / 2, k); return nodes; } pack.sort = d3.rebind(pack, hierarchy.sort); pack.children = d3.rebind(pack, hierarchy.children); pack.value = d3.rebind(pack, hierarchy.value); pack.size = function(x) { if (!arguments.length) return size; size = x; return pack; }; return pack.sort(d3_layout_packSort); }; function d3_layout_packSort(a, b) { return a.value - b.value; } function d3_layout_packInsert(a, b) { var c = a._pack_next; a._pack_next = b; b._pack_prev = a; b._pack_next = c; c._pack_prev = b; } function d3_layout_packSplice(a, b) { a._pack_next = b; b._pack_prev = a; } function d3_layout_packIntersects(a, b) { var dx = b.x - a.x, dy = b.y - a.y, dr = a.r + b.r; return (dr * dr - dx * dx - dy * dy) > .001; // within epsilon } function d3_layout_packCircle(nodes) { var xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, n = nodes.length, a, b, c, j, k; function bound(node) { xMin = Math.min(node.x - node.r, xMin); xMax = Math.max(node.x + node.r, xMax); yMin = Math.min(node.y - node.r, yMin); yMax = Math.max(node.y + node.r, yMax); } // Create node links. nodes.forEach(d3_layout_packLink); // Create first node. a = nodes[0]; a.x = -a.r; a.y = 0; bound(a); // Create second node. if (n > 1) { b = nodes[1]; b.x = b.r; b.y = 0; bound(b); // Create third node and build chain. if (n > 2) { c = nodes[2]; d3_layout_packPlace(a, b, c); bound(c); d3_layout_packInsert(a, c); a._pack_prev = c; d3_layout_packInsert(c, b); b = a._pack_next; // Now iterate through the rest. for (var i = 3; i < n; i++) { d3_layout_packPlace(a, b, c = nodes[i]); // Search for the closest intersection. var isect = 0, s1 = 1, s2 = 1; for (j = b._pack_next; j != b; j = j._pack_next, s1++) { if (d3_layout_packIntersects(j, c)) { isect = 1; break; } } if (isect == 1) { for (k = a._pack_prev; k != j._pack_prev; k = k._pack_prev, s2++) { if (d3_layout_packIntersects(k, c)) { if (s2 < s1) { isect = -1; j = k; } break; } } } // Update node chain. if (isect == 0) { d3_layout_packInsert(a, c); b = c; bound(c); } else if (isect > 0) { d3_layout_packSplice(a, j); b = j; i--; } else { // isect < 0 d3_layout_packSplice(j, b); a = j; i--; } } } } // Re-center the circles and return the encompassing radius. var cx = (xMin + xMax) / 2, cy = (yMin + yMax) / 2, cr = 0; for (var i = 0; i < n; i++) { var node = nodes[i]; node.x -= cx; node.y -= cy; cr = Math.max(cr, node.r + Math.sqrt(node.x * node.x + node.y * node.y)); } // Remove node links. nodes.forEach(d3_layout_packUnlink); return cr; } function d3_layout_packLink(node) { node._pack_next = node._pack_prev = node; } function d3_layout_packUnlink(node) { delete node._pack_next; delete node._pack_prev; } function d3_layout_packTree(node) { var children = node.children; if (children) { children.forEach(d3_layout_packTree); node.r = d3_layout_packCircle(children); } else { node.r = Math.sqrt(node.value); } } function d3_layout_packTransform(node, x, y, k) { var children = node.children; node.x = (x += k * node.x); node.y = (y += k * node.y); node.r *= k; if (children) { var i = -1, n = children.length; while (++i < n) d3_layout_packTransform(children[i], x, y, k); } } function d3_layout_packPlace(a, b, c) { var da = b.r + c.r, db = a.r + c.r, dx = b.x - a.x, dy = b.y - a.y, dc = Math.sqrt(dx * dx + dy * dy), cos = (db * db + dc * dc - da * da) / (2 * db * dc), theta = Math.acos(cos), x = cos * db, h = Math.sin(theta) * db; dx /= dc; dy /= dc; c.x = a.x + x * dx + h * dy; c.y = a.y + x * dy - h * dx; } // Implements a hierarchical layout using the cluster (or dendogram) algorithm. d3.layout.cluster = function() { var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [1, 1]; // width, height function cluster(d, i) { var nodes = hierarchy.call(this, d, i), root = nodes[0], previousNode, x = 0, kx, ky; // First walk, computing the initial x & y values. d3_layout_treeVisitAfter(root, function(node) { if (node.children) { node.x = d3_layout_clusterX(node.children); node.y = d3_layout_clusterY(node.children); } else { node.x = previousNode ? x += separation(node, previousNode) : 0; node.y = 0; previousNode = node; } }); // Compute the left-most, right-most, and depth-most nodes for extents. var left = d3_layout_clusterLeft(root), right = d3_layout_clusterRight(root), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2; // Second walk, normalizing x & y to the desired size. d3_layout_treeVisitAfter(root, function(node) { node.x = (node.x - x0) / (x1 - x0) * size[0]; node.y = (1 - node.y / root.y) * size[1]; }); return nodes; } cluster.sort = d3.rebind(cluster, hierarchy.sort); cluster.children = d3.rebind(cluster, hierarchy.children); cluster.links = d3_layout_treeLinks; cluster.separation = function(x) { if (!arguments.length) return separation; separation = x; return cluster; }; cluster.size = function(x) { if (!arguments.length) return size; size = x; return cluster; }; return cluster; }; function d3_layout_clusterY(children) { return 1 + d3.max(children, function(child) { return child.y; }); } function d3_layout_clusterX(children) { return children.reduce(function(x, child) { return x + child.x; }, 0) / children.length; } function d3_layout_clusterLeft(node) { var children = node.children; return children ? d3_layout_clusterLeft(children[0]) : node; } function d3_layout_clusterRight(node) { var children = node.children; return children ? d3_layout_clusterRight(children[children.length - 1]) : node; } // Node-link tree diagram using the Reingold-Tilford "tidy" algorithm d3.layout.tree = function() { var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [1, 1]; // width, height function tree(d, i) { var nodes = hierarchy.call(this, d, i), root = nodes[0]; function firstWalk(node, previousSibling) { var children = node.children, layout = node._tree; if (children) { var n = children.length, firstChild = children[0], previousChild, ancestor = firstChild, child, i = -1; while (++i < n) { child = children[i]; firstWalk(child, previousChild); ancestor = apportion(child, previousChild, ancestor); previousChild = child; } d3_layout_treeShift(node); var midpoint = .5 * (firstChild._tree.prelim + child._tree.prelim); if (previousSibling) { layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); layout.mod = layout.prelim - midpoint; } else { layout.prelim = midpoint; } } else { if (previousSibling) { layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); } } } function secondWalk(node, x) { node.x = node._tree.prelim + x; var children = node.children; if (children) { var i = -1, n = children.length; x += node._tree.mod; while (++i < n) { secondWalk(children[i], x); } } } function apportion(node, previousSibling, ancestor) { if (previousSibling) { var vip = node, vop = node, vim = previousSibling, vom = node.parent.children[0], sip = vip._tree.mod, sop = vop._tree.mod, sim = vim._tree.mod, som = vom._tree.mod, shift; while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) { vom = d3_layout_treeLeft(vom); vop = d3_layout_treeRight(vop); vop._tree.ancestor = node; shift = vim._tree.prelim + sim - vip._tree.prelim - sip + separation(vim, vip); if (shift > 0) { d3_layout_treeMove(d3_layout_treeAncestor(vim, node, ancestor), node, shift); sip += shift; sop += shift; } sim += vim._tree.mod; sip += vip._tree.mod; som += vom._tree.mod; sop += vop._tree.mod; } if (vim && !d3_layout_treeRight(vop)) { vop._tree.thread = vim; vop._tree.mod += sim - sop; } if (vip && !d3_layout_treeLeft(vom)) { vom._tree.thread = vip; vom._tree.mod += sip - som; ancestor = node; } } return ancestor; } // Initialize temporary layout variables. d3_layout_treeVisitAfter(root, function(node, previousSibling) { node._tree = { ancestor: node, prelim: 0, mod: 0, change: 0, shift: 0, number: previousSibling ? previousSibling._tree.number + 1 : 0 }; }); // Compute the layout using Buchheim et al.'s algorithm. firstWalk(root); secondWalk(root, -root._tree.prelim); // Compute the left-most, right-most, and depth-most nodes for extents. var left = d3_layout_treeSearch(root, d3_layout_treeLeftmost), right = d3_layout_treeSearch(root, d3_layout_treeRightmost), deep = d3_layout_treeSearch(root, d3_layout_treeDeepest), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2, y1 = deep.depth; // Clear temporary layout variables; transform x and y. d3_layout_treeVisitAfter(root, function(node) { node.x = (node.x - x0) / (x1 - x0) * size[0]; node.y = node.depth / y1 * size[1]; delete node._tree; }); return nodes; } tree.sort = d3.rebind(tree, hierarchy.sort); tree.children = d3.rebind(tree, hierarchy.children); tree.links = d3_layout_treeLinks; tree.separation = function(x) { if (!arguments.length) return separation; separation = x; return tree; }; tree.size = function(x) { if (!arguments.length) return size; size = x; return tree; }; return tree; }; // Returns an array source+target objects for the specified nodes. function d3_layout_treeLinks(nodes) { return d3.merge(nodes.map(function(parent) { return (parent.children || []).map(function(child) { return {source: parent, target: child}; }); })); } function d3_layout_treeSeparation(a, b) { return a.parent == b.parent ? 1 : 2; } // function d3_layout_treeSeparationRadial(a, b) { // return (a.parent == b.parent ? 1 : 2) / a.depth; // } function d3_layout_treeLeft(node) { return node.children ? node.children[0] : node._tree.thread; } function d3_layout_treeRight(node) { return node.children ? node.children[node.children.length - 1] : node._tree.thread; } function d3_layout_treeSearch(node, compare) { var children = node.children; if (children) { var child, n = children.length, i = -1; while (++i < n) { if (compare(child = d3_layout_treeSearch(children[i], compare), node) > 0) { node = child; } } } return node; } function d3_layout_treeRightmost(a, b) { return a.x - b.x; } function d3_layout_treeLeftmost(a, b) { return b.x - a.x; } function d3_layout_treeDeepest(a, b) { return a.depth - b.depth; } function d3_layout_treeVisitAfter(node, callback) { function visit(node, previousSibling) { var children = node.children; if (children) { var child, previousChild = null, i = -1, n = children.length; while (++i < n) { child = children[i]; visit(child, previousChild); previousChild = child; } } callback(node, previousSibling); } visit(node, null); } function d3_layout_treeShift(node) { var shift = 0, change = 0, children = node.children, i = children.length, child; while (--i >= 0) { child = children[i]._tree; child.prelim += shift; child.mod += shift; shift += child.shift + (change += child.change); } } function d3_layout_treeMove(ancestor, node, shift) { ancestor = ancestor._tree; node = node._tree; var change = shift / (node.number - ancestor.number); ancestor.change += change; node.change -= change; node.shift += shift; node.prelim += shift; node.mod += shift; } function d3_layout_treeAncestor(vim, node, ancestor) { return vim._tree.ancestor.parent == node.parent ? vim._tree.ancestor : ancestor; } // Squarified Treemaps by Mark Bruls, Kees Huizing, and Jarke J. van Wijk d3.layout.treemap = function() { var hierarchy = d3.layout.hierarchy(), round = Math.round, size = [1, 1], // width, height sticky = false, stickies; // Recursively compute the node area based on value & scale. function scale(node, k) { var children = node.children; node.area = node.value * k; if (children) { var i = -1, n = children.length; while (++i < n) scale(children[i], k); } } // Recursively arranges the specified node's children into squarified rows. function squarify(node) { if (!node.children) return; var rect = {x: node.x, y: node.y, dx: node.dx, dy: node.dy}, row = [], children = node.children.slice(), // copy-on-write child, best = Infinity, // the best row score so far score, // the current row score u = Math.min(rect.dx, rect.dy), // initial orientation n; row.area = 0; while ((n = children.length) > 0) { row.push(child = children[n - 1]); row.area += child.area; if ((score = worst(row, u)) <= best) { // continue with this orientation children.pop(); best = score; } else { // abort, and try a different orientation row.area -= row.pop().area; position(row, u, rect, false); u = Math.min(rect.dx, rect.dy); row.length = row.area = 0; best = Infinity; } } if (row.length) { position(row, u, rect, true); row.length = row.area = 0; } node.children.forEach(squarify); } // Recursively resizes the specified node's children into existing rows. // Preserves the existing layout! function stickify(node) { if (!node.children) return; var rect = {x: node.x, y: node.y, dx: node.dx, dy: node.dy}, children = node.children.slice(), // copy-on-write child, row = []; row.area = 0; while (child = children.pop()) { row.push(child); row.area += child.area; if (child.z != null) { position(row, child.z ? rect.dx : rect.dy, rect, !children.length); row.length = row.area = 0; } } node.children.forEach(stickify); } // Computes the score for the specified row, as the worst aspect ratio. function worst(row, u) { var s = row.area, r, rmax = 0, rmin = Infinity, i = -1, n = row.length; while (++i < n) { r = row[i].area; if (r < rmin) rmin = r; if (r > rmax) rmax = r; } s *= s; u *= u; return Math.max((u * rmax) / s, s / (u * rmin)); } // Positions the specified row of nodes. Modifies `rect`. function position(row, u, rect, flush) { var i = -1, n = row.length, x = rect.x, y = rect.y, v = u ? round(row.area / u) : 0, o; if (u == rect.dx) { // horizontal subdivision if (flush || v > rect.dy) v = rect.dy; // over+underflow while (++i < n) { o = row[i]; o.x = x; o.y = y; o.dy = v; x += o.dx = round(o.area / v); } o.z = true; o.dx += rect.x + rect.dx - x; // rounding error rect.y += v; rect.dy -= v; } else { // vertical subdivision if (flush || v > rect.dx) v = rect.dx; // over+underflow while (++i < n) { o = row[i]; o.x = x; o.y = y; o.dx = v; y += o.dy = round(o.area / v); } o.z = false; o.dy += rect.y + rect.dy - y; // rounding error rect.x += v; rect.dx -= v; } } function treemap(d) { var nodes = stickies || hierarchy(d), root = nodes[0]; root.x = 0; root.y = 0; root.dx = size[0]; root.dy = size[1]; if (stickies) hierarchy.revalue(root); scale(root, size[0] * size[1] / root.value); (stickies ? stickify : squarify)(root); if (sticky) stickies = nodes; return nodes; } treemap.sort = d3.rebind(treemap, hierarchy.sort); treemap.children = d3.rebind(treemap, hierarchy.children); treemap.value = d3.rebind(treemap, hierarchy.value); treemap.size = function(x) { if (!arguments.length) return size; size = x; return treemap; }; treemap.round = function(x) { if (!arguments.length) return round != Number; round = x ? Math.round : Number; return treemap; }; treemap.sticky = function(x) { if (!arguments.length) return sticky; sticky = x; stickies = null; return treemap; }; return treemap; }; })()