1887 lines
47 KiB
JavaScript
1887 lines
47 KiB
JavaScript
(function(){d3.layout = {};
|
|
// Implements hierarchical edge bundling using Holten's algorithm. For each
|
|
// input link, a path is computed that travels through the tree, up the parent
|
|
// hierarchy to the least common ancestor, and then back down to the destination
|
|
// node. Each path is simply an array of nodes.
|
|
d3.layout.bundle = function() {
|
|
return function(links) {
|
|
var paths = [],
|
|
i = -1,
|
|
n = links.length;
|
|
while (++i < n) paths.push(d3_layout_bundlePath(links[i]));
|
|
return paths;
|
|
};
|
|
};
|
|
|
|
function d3_layout_bundlePath(link) {
|
|
var start = link.source,
|
|
end = link.target,
|
|
lca = d3_layout_bundleLeastCommonAncestor(start, end),
|
|
points = [start];
|
|
while (start !== lca) {
|
|
start = start.parent;
|
|
points.push(start);
|
|
}
|
|
var k = points.length;
|
|
while (end !== lca) {
|
|
points.splice(k, 0, end);
|
|
end = end.parent;
|
|
}
|
|
return points;
|
|
}
|
|
|
|
function d3_layout_bundleAncestors(node) {
|
|
var ancestors = [],
|
|
parent = node.parent;
|
|
while (parent != null) {
|
|
ancestors.push(node);
|
|
node = parent;
|
|
parent = parent.parent;
|
|
}
|
|
ancestors.push(node);
|
|
return ancestors;
|
|
}
|
|
|
|
function d3_layout_bundleLeastCommonAncestor(a, b) {
|
|
if (a === b) return a;
|
|
var aNodes = d3_layout_bundleAncestors(a),
|
|
bNodes = d3_layout_bundleAncestors(b),
|
|
aNode = aNodes.pop(),
|
|
bNode = bNodes.pop(),
|
|
sharedNode = null;
|
|
while (aNode === bNode) {
|
|
sharedNode = aNode;
|
|
aNode = aNodes.pop();
|
|
bNode = bNodes.pop();
|
|
}
|
|
return sharedNode;
|
|
}
|
|
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.
|
|
// Note: Opera has a bug reordering object literal properties!
|
|
x = 0, i = -1; while (++i < n) {
|
|
x0 = x, j = -1; while (++j < n) {
|
|
var di = groupIndex[i],
|
|
dj = subgroupIndex[di][j],
|
|
v = matrix[di][dj],
|
|
a0 = x,
|
|
a1 = x += v * k;
|
|
subgroups[di + "-" + dj] = {
|
|
index: di,
|
|
subindex: dj,
|
|
startAngle: a0,
|
|
endAngle: a1,
|
|
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.value < target.value
|
|
? {source: target, target: source}
|
|
: {source: source, target: target});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sortChords) resort();
|
|
}
|
|
|
|
function resort() {
|
|
chords.sort(function(a, b) {
|
|
return sortChords(
|
|
(a.source.value + a.target.value) / 2,
|
|
(b.source.value + b.target.value) / 2);
|
|
});
|
|
}
|
|
|
|
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],
|
|
drag,
|
|
alpha,
|
|
friction = .9,
|
|
linkDistance = d3_layout_forceLinkDistance,
|
|
linkStrength = d3_layout_forceLinkStrength,
|
|
charge = -30,
|
|
gravity = .1,
|
|
theta = .8,
|
|
interval,
|
|
nodes = [],
|
|
links = [],
|
|
distances,
|
|
strengths,
|
|
charges;
|
|
|
|
function repulse(node) {
|
|
return function(quad, x1, y1, x2, y2) {
|
|
if (quad.point !== node) {
|
|
var dx = quad.cx - node.x,
|
|
dy = quad.cy - node.y,
|
|
dn = 1 / Math.sqrt(dx * dx + dy * dy);
|
|
|
|
/* Barnes-Hut criterion. */
|
|
if ((x2 - x1) * dn < theta) {
|
|
var k = quad.charge * dn * dn;
|
|
node.px -= dx * k;
|
|
node.py -= dy * k;
|
|
return true;
|
|
}
|
|
|
|
if (quad.point && isFinite(dn)) {
|
|
var k = quad.pointCharge * dn * dn;
|
|
node.px -= dx * k;
|
|
node.py -= dy * k;
|
|
}
|
|
}
|
|
return !quad.charge;
|
|
};
|
|
}
|
|
|
|
function tick() {
|
|
var n = nodes.length,
|
|
m = links.length,
|
|
q,
|
|
i, // current index
|
|
o, // current object
|
|
s, // current source
|
|
t, // current target
|
|
l, // current distance
|
|
k, // current force
|
|
x, // x-distance
|
|
y; // y-distance
|
|
|
|
// 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 = (x * x + y * y)) {
|
|
l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l;
|
|
x *= l;
|
|
y *= l;
|
|
t.x -= x * (k = s.weight / (t.weight + s.weight));
|
|
t.y -= y * k;
|
|
s.x += x * (k = 1 - k);
|
|
s.y += y * k;
|
|
}
|
|
}
|
|
|
|
// apply gravity forces
|
|
if (k = alpha * gravity) {
|
|
x = size[0] / 2;
|
|
y = size[1] / 2;
|
|
i = -1; if (k) while (++i < n) {
|
|
o = nodes[i];
|
|
o.x += (x - o.x) * k;
|
|
o.y += (y - o.y) * k;
|
|
}
|
|
}
|
|
|
|
// compute quadtree center of mass and apply charge forces
|
|
if (charge) {
|
|
d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges);
|
|
i = -1; while (++i < n) {
|
|
if (!(o = nodes[i]).fixed) {
|
|
q.visit(repulse(o));
|
|
}
|
|
}
|
|
}
|
|
|
|
// position verlet integration
|
|
i = -1; while (++i < n) {
|
|
o = nodes[i];
|
|
if (o.fixed) {
|
|
o.x = o.px;
|
|
o.y = o.py;
|
|
} else {
|
|
o.x -= (o.px - (o.px = o.x)) * friction;
|
|
o.y -= (o.py - (o.py = o.y)) * friction;
|
|
if (o.x < 0) o.x = 10;
|
|
if (o.x > size[0]) o.x = size[0] - 10;
|
|
if (o.y < 0) o.y = 10;
|
|
if (o.y > size[1]) o.y = size[1] - 10;
|
|
|
|
}
|
|
}
|
|
|
|
event.tick({type: "tick", alpha: alpha});
|
|
|
|
// simulated annealing, basically
|
|
return (alpha *= .99) < .0005;
|
|
}
|
|
|
|
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.linkDistance = function(x) {
|
|
if (!arguments.length) return linkDistance;
|
|
linkDistance = d3.functor(x);
|
|
return force;
|
|
};
|
|
|
|
// For backwards-compatibility.
|
|
force.distance = force.linkDistance;
|
|
|
|
force.linkStrength = function(x) {
|
|
if (!arguments.length) return linkStrength;
|
|
linkStrength = d3.functor(x);
|
|
return force;
|
|
};
|
|
|
|
force.friction = function(x) {
|
|
if (!arguments.length) return friction;
|
|
friction = x;
|
|
return force;
|
|
};
|
|
|
|
force.charge = function(x) {
|
|
if (!arguments.length) return charge;
|
|
charge = typeof x === "function" ? x : +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,
|
|
j,
|
|
n = nodes.length,
|
|
m = links.length,
|
|
w = size[0],
|
|
h = size[1],
|
|
neighbors,
|
|
o;
|
|
|
|
for (i = 0; i < n; ++i) {
|
|
(o = nodes[i]).index = i;
|
|
o.weight = 0;
|
|
}
|
|
|
|
distances = [];
|
|
strengths = [];
|
|
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];
|
|
distances[i] = linkDistance.call(this, o, i);
|
|
strengths[i] = linkStrength.call(this, o, i);
|
|
++o.source.weight;
|
|
++o.target.weight;
|
|
}
|
|
|
|
for (i = 0; i < n; ++i) {
|
|
o = nodes[i];
|
|
if (isNaN(o.x)) o.x = position("x", w);
|
|
if (isNaN(o.y)) o.y = position("y", h);
|
|
if (isNaN(o.px)) o.px = o.x;
|
|
if (isNaN(o.py)) o.py = o.y;
|
|
}
|
|
|
|
charges = [];
|
|
if (typeof charge === "function") {
|
|
for (i = 0; i < n; ++i) {
|
|
charges[i] = +charge.call(this, nodes[i], i);
|
|
}
|
|
} else {
|
|
for (i = 0; i < n; ++i) {
|
|
charges[i] = charge;
|
|
}
|
|
}
|
|
|
|
// initialize node position based on first neighbor
|
|
function position(dimension, size) {
|
|
var neighbors = neighbor(i),
|
|
j = -1,
|
|
m = neighbors.length,
|
|
x;
|
|
while (++j < m) if (!isNaN(x = neighbors[j][dimension])) return x;
|
|
return Math.random() * size;
|
|
}
|
|
|
|
// initialize neighbors lazily
|
|
function neighbor() {
|
|
if (!neighbors) {
|
|
neighbors = [];
|
|
for (j = 0; j < n; ++j) {
|
|
neighbors[j] = [];
|
|
}
|
|
for (j = 0; j < m; ++j) {
|
|
var o = links[j];
|
|
neighbors[o.source.index].push(o.target);
|
|
neighbors[o.target.index].push(o.source);
|
|
}
|
|
}
|
|
return neighbors[i];
|
|
}
|
|
|
|
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() {
|
|
if (!drag) drag = d3.behavior.drag()
|
|
.origin(Object)
|
|
.on("dragstart", dragstart)
|
|
.on("drag", d3_layout_forceDrag)
|
|
.on("dragend", d3_layout_forceDragEnd);
|
|
|
|
this.on("mouseover.force", d3_layout_forceDragOver)
|
|
.on("mouseout.force", d3_layout_forceDragOut)
|
|
.call(drag);
|
|
};
|
|
|
|
function dragstart(d) {
|
|
d3_layout_forceDragOver(d3_layout_forceDragNode = d);
|
|
d3_layout_forceDragForce = force;
|
|
}
|
|
|
|
return d3.rebind(force, event, "on");
|
|
};
|
|
|
|
var d3_layout_forceDragForce,
|
|
d3_layout_forceDragNode;
|
|
|
|
function d3_layout_forceDragOver(d) {
|
|
d.fixed |= 2;
|
|
}
|
|
|
|
function d3_layout_forceDragOut(d) {
|
|
if (d !== d3_layout_forceDragNode) d.fixed &= 1;
|
|
}
|
|
|
|
function d3_layout_forceDragEnd() {
|
|
d3_layout_forceDrag();
|
|
d3_layout_forceDragNode.fixed &= 1;
|
|
d3_layout_forceDragForce = d3_layout_forceDragNode = null;
|
|
}
|
|
|
|
function d3_layout_forceDrag() {
|
|
d3_layout_forceDragNode.px = d3.event.x;
|
|
d3_layout_forceDragNode.py = d3.event.y;
|
|
d3_layout_forceDragForce.resume(); // restart annealing
|
|
}
|
|
|
|
function d3_layout_forceAccumulate(quad, alpha, charges) {
|
|
var cx = 0,
|
|
cy = 0;
|
|
quad.charge = 0;
|
|
if (!quad.leaf) {
|
|
var nodes = quad.nodes,
|
|
n = nodes.length,
|
|
i = -1,
|
|
c;
|
|
while (++i < n) {
|
|
c = nodes[i];
|
|
if (c == null) continue;
|
|
d3_layout_forceAccumulate(c, alpha, charges);
|
|
quad.charge += c.charge;
|
|
cx += c.charge * c.cx;
|
|
cy += c.charge * c.cy;
|
|
}
|
|
}
|
|
if (quad.point) {
|
|
// jitter internal nodes that are coincident
|
|
if (!quad.leaf) {
|
|
quad.point.x += Math.random() - .5;
|
|
quad.point.y += Math.random() - .5;
|
|
}
|
|
var k = alpha * charges[quad.point.index];
|
|
quad.charge += quad.pointCharge = k;
|
|
cx += k * quad.point.x;
|
|
cy += k * quad.point.y;
|
|
}
|
|
quad.cx = cx / quad.charge;
|
|
quad.cy = cy / quad.charge;
|
|
}
|
|
|
|
function d3_layout_forceLinkDistance(link) {
|
|
return 20;
|
|
}
|
|
|
|
function d3_layout_forceLinkStrength(link) {
|
|
return 1;
|
|
}
|
|
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 && (n = children.length)) {
|
|
var i = -1,
|
|
n,
|
|
c,
|
|
d;
|
|
dx = node.value ? dx / node.value : 0;
|
|
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 && (n = children.length)) {
|
|
var i = -1,
|
|
n;
|
|
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.size = function(x) {
|
|
if (!arguments.length) return size;
|
|
size = x;
|
|
return partition;
|
|
};
|
|
|
|
return d3_layout_hierarchyRebind(partition, hierarchy);
|
|
};
|
|
d3.layout.pie = function() {
|
|
var value = Number,
|
|
sort = d3_layout_pieSortByValue,
|
|
startAngle = 0,
|
|
endAngle = 2 * Math.PI;
|
|
|
|
function pie(data, i) {
|
|
|
|
// Compute the numeric values for each data element.
|
|
var values = data.map(function(d, i) { return +value.call(pie, d, i); });
|
|
|
|
// Compute the start angle.
|
|
var a = +(typeof startAngle === "function"
|
|
? startAngle.apply(this, arguments)
|
|
: startAngle);
|
|
|
|
// Compute the angular scale factor: from value to radians.
|
|
var k = ((typeof endAngle === "function"
|
|
? endAngle.apply(this, arguments)
|
|
: endAngle) - startAngle)
|
|
/ d3.sum(values);
|
|
|
|
// Optionally sort the data.
|
|
var index = d3.range(data.length);
|
|
if (sort != null) index.sort(sort === d3_layout_pieSortByValue
|
|
? function(i, j) { return values[j] - values[i]; }
|
|
: function(i, j) { return sort(data[i], data[j]); });
|
|
|
|
// Compute the arcs!
|
|
// They are stored in the original data's order.
|
|
var arcs = [];
|
|
index.forEach(function(i) {
|
|
arcs[i] = {
|
|
data: data[i],
|
|
value: d = values[i],
|
|
startAngle: a,
|
|
endAngle: a += d * k
|
|
};
|
|
});
|
|
return arcs;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
};
|
|
|
|
var d3_layout_pieSortByValue = {};
|
|
// data is two-dimensional array of x,y; we populate y0
|
|
d3.layout.stack = function() {
|
|
var values = Object,
|
|
order = d3_layout_stackOrders["default"],
|
|
offset = d3_layout_stackOffsets["zero"],
|
|
out = d3_layout_stackOut,
|
|
x = d3_layout_stackX,
|
|
y = d3_layout_stackY;
|
|
|
|
function stack(data, index) {
|
|
|
|
// Convert series to canonical two-dimensional representation.
|
|
var series = data.map(function(d, i) {
|
|
return values.call(stack, d, i);
|
|
});
|
|
|
|
// Convert each series to canonical [[x,y]] representation.
|
|
var points = series.map(function(d, i) {
|
|
return d.map(function(v, i) {
|
|
return [x.call(stack, v, i), y.call(stack, v, i)];
|
|
});
|
|
});
|
|
|
|
// Compute the order of series, and permute them.
|
|
var orders = order.call(stack, points, index);
|
|
series = d3.permute(series, orders);
|
|
points = d3.permute(points, orders);
|
|
|
|
// Compute the baseline…
|
|
var offsets = offset.call(stack, points, index);
|
|
|
|
// And propagate it to other series.
|
|
var n = series.length,
|
|
m = series[0].length,
|
|
i,
|
|
j,
|
|
o;
|
|
for (j = 0; j < m; ++j) {
|
|
out.call(stack, series[0][j], o = offsets[j], points[0][j][1]);
|
|
for (i = 1; i < n; ++i) {
|
|
out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]);
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
stack.values = function(x) {
|
|
if (!arguments.length) return values;
|
|
values = x;
|
|
return stack;
|
|
};
|
|
|
|
stack.order = function(x) {
|
|
if (!arguments.length) return order;
|
|
order = typeof x === "function" ? x : d3_layout_stackOrders[x];
|
|
return stack;
|
|
};
|
|
|
|
stack.offset = function(x) {
|
|
if (!arguments.length) return offset;
|
|
offset = typeof x === "function" ? x : d3_layout_stackOffsets[x];
|
|
return stack;
|
|
};
|
|
|
|
stack.x = function(z) {
|
|
if (!arguments.length) return x;
|
|
x = z;
|
|
return stack;
|
|
};
|
|
|
|
stack.y = function(z) {
|
|
if (!arguments.length) return y;
|
|
y = z;
|
|
return stack;
|
|
};
|
|
|
|
stack.out = function(z) {
|
|
if (!arguments.length) return out;
|
|
out = z;
|
|
return stack;
|
|
};
|
|
|
|
return stack;
|
|
}
|
|
|
|
function d3_layout_stackX(d) {
|
|
return d.x;
|
|
}
|
|
|
|
function d3_layout_stackY(d) {
|
|
return d.y;
|
|
}
|
|
|
|
function d3_layout_stackOut(d, y0, y) {
|
|
d.y0 = y0;
|
|
d.y = y;
|
|
}
|
|
|
|
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) {
|
|
var n = data.length,
|
|
m = data[0].length,
|
|
sums = [],
|
|
max = 0,
|
|
i,
|
|
j,
|
|
o,
|
|
y0 = [];
|
|
for (j = 0; j < m; ++j) {
|
|
for (i = 0, o = 0; i < n; i++) o += data[i][j][1];
|
|
if (o > max) max = o;
|
|
sums.push(o);
|
|
}
|
|
for (j = 0; j < m; ++j) {
|
|
y0[j] = (max - sums[j]) / 2;
|
|
}
|
|
return y0;
|
|
},
|
|
|
|
"wiggle": function(data) {
|
|
var n = data.length,
|
|
x = data[0],
|
|
m = x.length,
|
|
max = 0,
|
|
i,
|
|
j,
|
|
k,
|
|
s1,
|
|
s2,
|
|
s3,
|
|
dx,
|
|
o,
|
|
o0,
|
|
y0 = [];
|
|
y0[0] = o = o0 = 0;
|
|
for (j = 1; j < m; ++j) {
|
|
for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1];
|
|
for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) {
|
|
for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) {
|
|
s3 += (data[k][j][1] - data[k][j - 1][1]) / dx;
|
|
}
|
|
s2 += s3 * data[i][j][1];
|
|
}
|
|
y0[j] = o -= s1 ? s2 / s1 * dx : 0;
|
|
if (o < o0) o0 = o;
|
|
}
|
|
for (j = 0; j < m; ++j) y0[j] -= o0;
|
|
return y0;
|
|
},
|
|
|
|
"expand": function(data) {
|
|
var n = data.length,
|
|
m = data[0].length,
|
|
k = 1 / n,
|
|
i,
|
|
j,
|
|
o,
|
|
y0 = [];
|
|
for (j = 0; j < m; ++j) {
|
|
for (i = 0, o = 0; i < n; i++) o += data[i][j][1];
|
|
if (o) for (i = 0; i < n; i++) data[i][j][1] /= o;
|
|
else for (i = 0; i < n; i++) data[i][j][1] = k;
|
|
}
|
|
for (j = 0; j < m; ++j) y0[j] = 0;
|
|
return y0;
|
|
},
|
|
|
|
"zero": function(data) {
|
|
var j = -1,
|
|
m = data[0].length,
|
|
y0 = [];
|
|
while (++j < m) y0[j] = 0;
|
|
return y0;
|
|
}
|
|
|
|
};
|
|
|
|
function d3_layout_stackMaxIndex(array) {
|
|
var i = 1,
|
|
j = 0,
|
|
v = array[0][1],
|
|
k,
|
|
n = array.length;
|
|
for (; i < n; ++i) {
|
|
if ((k = array[i][1]) > v) {
|
|
j = i;
|
|
v = k;
|
|
}
|
|
}
|
|
return j;
|
|
}
|
|
|
|
function d3_layout_stackReduceSum(d) {
|
|
return d.reduce(d3_layout_stackSum, 0);
|
|
}
|
|
|
|
function d3_layout_stackSum(p, d) {
|
|
return p + d[1];
|
|
}
|
|
d3.layout.histogram = function() {
|
|
var frequency = true,
|
|
valuer = Number,
|
|
ranger = d3_layout_histogramRange,
|
|
binner = d3_layout_histogramBinSturges;
|
|
|
|
function histogram(data, i) {
|
|
var bins = [],
|
|
values = data.map(valuer, this),
|
|
range = ranger.call(this, values, i),
|
|
thresholds = binner.call(this, range, values, i),
|
|
bin,
|
|
i = -1,
|
|
n = values.length,
|
|
m = thresholds.length - 1,
|
|
k = frequency ? 1 : 1 / n,
|
|
x;
|
|
|
|
// Initialize the bins.
|
|
while (++i < m) {
|
|
bin = bins[i] = [];
|
|
bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]);
|
|
bin.y = 0;
|
|
}
|
|
|
|
// Fill the bins, ignoring values outside the range.
|
|
i = -1; while(++i < n) {
|
|
x = values[i];
|
|
if ((x >= range[0]) && (x <= range[1])) {
|
|
bin = bins[d3.bisect(thresholds, x, 1, m) - 1];
|
|
bin.y += k;
|
|
bin.push(data[i]);
|
|
}
|
|
}
|
|
|
|
return bins;
|
|
}
|
|
|
|
// Specifies how to extract a value from the associated data. The default
|
|
// value function is `Number`, which is equivalent to the identity function.
|
|
histogram.value = function(x) {
|
|
if (!arguments.length) return valuer;
|
|
valuer = x;
|
|
return histogram;
|
|
};
|
|
|
|
// Specifies the range of the histogram. Values outside the specified range
|
|
// will be ignored. The argument `x` may be specified either as a two-element
|
|
// array representing the minimum and maximum value of the range, or as a
|
|
// function that returns the range given the array of values and the current
|
|
// index `i`. The default range is the extent (minimum and maximum) of the
|
|
// values.
|
|
histogram.range = function(x) {
|
|
if (!arguments.length) return ranger;
|
|
ranger = d3.functor(x);
|
|
return histogram;
|
|
};
|
|
|
|
// Specifies how to bin values in the histogram. The argument `x` may be
|
|
// specified as a number, in which case the range of values will be split
|
|
// uniformly into the given number of bins. Or, `x` may be an array of
|
|
// threshold values, defining the bins; the specified array must contain the
|
|
// rightmost (upper) value, thus specifying n + 1 values for n bins. Or, `x`
|
|
// may be a function which is evaluated, being passed the range, the array of
|
|
// values, and the current index `i`, returning an array of thresholds. The
|
|
// default bin function will divide the values into uniform bins using
|
|
// Sturges' formula.
|
|
histogram.bins = function(x) {
|
|
if (!arguments.length) return binner;
|
|
binner = typeof x === "number"
|
|
? function(range) { return d3_layout_histogramBinFixed(range, x); }
|
|
: d3.functor(x);
|
|
return histogram;
|
|
};
|
|
|
|
// Specifies whether the histogram's `y` value is a count (frequency) or a
|
|
// probability (density). The default value is true.
|
|
histogram.frequency = function(x) {
|
|
if (!arguments.length) return frequency;
|
|
frequency = !!x;
|
|
return histogram;
|
|
};
|
|
|
|
return histogram;
|
|
};
|
|
|
|
function d3_layout_histogramBinSturges(range, values) {
|
|
return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1));
|
|
}
|
|
|
|
function d3_layout_histogramBinFixed(range, n) {
|
|
var x = -1,
|
|
b = +range[0],
|
|
m = (range[1] - b) / n,
|
|
f = [];
|
|
while (++x <= n) f[x] = m * x + b;
|
|
return f;
|
|
}
|
|
|
|
function d3_layout_histogramRange(values) {
|
|
return [d3.min(values), d3.max(values)];
|
|
}
|
|
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 childs = children.call(hierarchy, data, depth),
|
|
node = d3_layout_hierarchyInline ? data : {data: data};
|
|
node.depth = depth;
|
|
nodes.push(node);
|
|
if (childs && (n = childs.length)) {
|
|
var i = -1,
|
|
n,
|
|
c = node.children = [],
|
|
v = 0,
|
|
j = depth + 1;
|
|
while (++i < n) {
|
|
d = recurse(childs[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) || 0;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
// Recursively re-evaluates the node value.
|
|
function revalue(node, depth) {
|
|
var children = node.children,
|
|
v = 0;
|
|
if (children && (n = children.length)) {
|
|
var i = -1,
|
|
n,
|
|
j = depth + 1;
|
|
while (++i < n) v += revalue(children[i], j);
|
|
} else if (value) {
|
|
v = +value.call(hierarchy, d3_layout_hierarchyInline ? node : node.data, depth) || 0;
|
|
}
|
|
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;
|
|
};
|
|
|
|
// A method assignment helper for hierarchy subclasses.
|
|
function d3_layout_hierarchyRebind(object, hierarchy) {
|
|
d3.rebind(object, hierarchy, "sort", "children", "value");
|
|
|
|
// Add an alias for links, for convenience.
|
|
object.links = d3_layout_hierarchyLinks;
|
|
|
|
// If the new API is used, enabling inlining.
|
|
object.nodes = function(d) {
|
|
d3_layout_hierarchyInline = true;
|
|
return (object.nodes = object)(d);
|
|
};
|
|
|
|
return object;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Returns an array source+target objects for the specified nodes.
|
|
function d3_layout_hierarchyLinks(nodes) {
|
|
return d3.merge(nodes.map(function(parent) {
|
|
return (parent.children || []).map(function(child) {
|
|
return {source: parent, target: child};
|
|
});
|
|
}));
|
|
}
|
|
|
|
// For backwards-compatibility, don't enable inlining by default.
|
|
var d3_layout_hierarchyInline = false;
|
|
d3.layout.pack = function() {
|
|
var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort),
|
|
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.size = function(x) {
|
|
if (!arguments.length) return size;
|
|
size = x;
|
|
return pack;
|
|
};
|
|
|
|
return d3_layout_hierarchyRebind(pack, hierarchy);
|
|
};
|
|
|
|
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)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update node chain.
|
|
if (isect) {
|
|
if (s1 < s2 || (s1 == s2 && b.r < a.r)) d3_layout_packSplice(a, b = j);
|
|
else d3_layout_packSplice(a = k, b);
|
|
i--;
|
|
} else {
|
|
d3_layout_packInsert(a, c);
|
|
b = c;
|
|
bound(c);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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.length) {
|
|
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 db = a.r + c.r,
|
|
dx = b.x - a.x,
|
|
dy = b.y - a.y;
|
|
if (db && (dx || dy)) {
|
|
var da = b.r + c.r,
|
|
dc = Math.sqrt(dx * dx + dy * dy),
|
|
cos = Math.max(-1, Math.min(1, (db * db + dc * dc - da * da) / (2 * db * dc))),
|
|
theta = Math.acos(cos),
|
|
x = cos * (db /= dc),
|
|
y = Math.sin(theta) * db;
|
|
c.x = a.x + x * dx + y * dy;
|
|
c.y = a.y + x * dy - y * dx;
|
|
} else {
|
|
c.x = a.x + db;
|
|
c.y = a.y;
|
|
}
|
|
}
|
|
// 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) {
|
|
var children = node.children;
|
|
if (children && children.length) {
|
|
node.x = d3_layout_clusterX(children);
|
|
node.y = d3_layout_clusterY(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 - (root.y ? node.y / root.y : 1)) * size[1];
|
|
});
|
|
|
|
return nodes;
|
|
}
|
|
|
|
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 d3_layout_hierarchyRebind(cluster, hierarchy);
|
|
};
|
|
|
|
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 && children.length ? d3_layout_clusterLeft(children[0]) : node;
|
|
}
|
|
|
|
function d3_layout_clusterRight(node) {
|
|
var children = node.children, n;
|
|
return children && (n = children.length) ? d3_layout_clusterRight(children[n - 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 && (n = children.length)) {
|
|
var n,
|
|
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 && (n = children.length)) {
|
|
var i = -1,
|
|
n;
|
|
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 || 1;
|
|
|
|
// 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.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 d3_layout_hierarchyRebind(tree, hierarchy);
|
|
};
|
|
|
|
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) {
|
|
var children = node.children;
|
|
return children && children.length ? children[0] : node._tree.thread;
|
|
}
|
|
|
|
function d3_layout_treeRight(node) {
|
|
var children = node.children,
|
|
n;
|
|
return children && (n = children.length) ? children[n - 1] : node._tree.thread;
|
|
}
|
|
|
|
function d3_layout_treeSearch(node, compare) {
|
|
var children = node.children;
|
|
if (children && (n = children.length)) {
|
|
var child,
|
|
n,
|
|
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 && (n = children.length)) {
|
|
var child,
|
|
previousChild = null,
|
|
i = -1,
|
|
n;
|
|
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
|
|
// Modified to support a target aspect ratio by Jeff Heer
|
|
d3.layout.treemap = function() {
|
|
var hierarchy = d3.layout.hierarchy(),
|
|
round = Math.round,
|
|
size = [1, 1], // width, height
|
|
padding = null,
|
|
pad = d3_layout_treemapPadNull,
|
|
sticky = false,
|
|
stickies,
|
|
ratio = 0.5 * (1 + Math.sqrt(5)); // golden ratio
|
|
|
|
// Compute the area for each child based on value & scale.
|
|
function scale(children, k) {
|
|
var i = -1,
|
|
n = children.length,
|
|
child,
|
|
area;
|
|
while (++i < n) {
|
|
area = (child = children[i]).value * (k < 0 ? 0 : k);
|
|
child.area = isNaN(area) || area <= 0 ? 0 : area;
|
|
}
|
|
}
|
|
|
|
// Recursively arranges the specified node's children into squarified rows.
|
|
function squarify(node) {
|
|
var children = node.children;
|
|
if (children && children.length) {
|
|
var rect = pad(node),
|
|
row = [],
|
|
remaining = 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;
|
|
scale(remaining, rect.dx * rect.dy / node.value);
|
|
row.area = 0;
|
|
while ((n = remaining.length) > 0) {
|
|
row.push(child = remaining[n - 1]);
|
|
row.area += child.area;
|
|
if ((score = worst(row, u)) <= best) { // continue with this orientation
|
|
remaining.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;
|
|
}
|
|
children.forEach(squarify);
|
|
}
|
|
}
|
|
|
|
// Recursively resizes the specified node's children into existing rows.
|
|
// Preserves the existing layout!
|
|
function stickify(node) {
|
|
var children = node.children;
|
|
if (children && children.length) {
|
|
var rect = pad(node),
|
|
remaining = children.slice(), // copy-on-write
|
|
child,
|
|
row = [];
|
|
scale(remaining, rect.dx * rect.dy / node.value);
|
|
row.area = 0;
|
|
while (child = remaining.pop()) {
|
|
row.push(child);
|
|
row.area += child.area;
|
|
if (child.z != null) {
|
|
position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length);
|
|
row.length = row.area = 0;
|
|
}
|
|
}
|
|
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) {
|
|
if (!(r = row[i].area)) continue;
|
|
if (r < rmin) rmin = r;
|
|
if (r > rmax) rmax = r;
|
|
}
|
|
s *= s;
|
|
u *= u;
|
|
return s
|
|
? Math.max((u * rmax * ratio) / s, s / (u * rmin * ratio))
|
|
: Infinity;
|
|
}
|
|
|
|
// 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 = Math.min(rect.x + rect.dx - x, v ? round(o.area / v) : 0);
|
|
}
|
|
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 = Math.min(rect.y + rect.dy - y, v ? round(o.area / v) : 0);
|
|
}
|
|
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], root.dx * root.dy / root.value);
|
|
(stickies ? stickify : squarify)(root);
|
|
if (sticky) stickies = nodes;
|
|
return nodes;
|
|
}
|
|
|
|
treemap.size = function(x) {
|
|
if (!arguments.length) return size;
|
|
size = x;
|
|
return treemap;
|
|
};
|
|
|
|
treemap.padding = function(x) {
|
|
if (!arguments.length) return padding;
|
|
|
|
function padFunction(node) {
|
|
var p = x.call(treemap, node, node.depth);
|
|
return p == null
|
|
? d3_layout_treemapPadNull(node)
|
|
: d3_layout_treemapPad(node, typeof p === "number" ? [p, p, p, p] : p);
|
|
}
|
|
|
|
function padConstant(node) {
|
|
return d3_layout_treemapPad(node, x);
|
|
}
|
|
|
|
var type;
|
|
pad = (padding = x) == null ? d3_layout_treemapPadNull
|
|
: (type = typeof x) === "function" ? padFunction
|
|
: type === "number" ? (x = [x, x, x, x], padConstant)
|
|
: padConstant;
|
|
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;
|
|
};
|
|
|
|
treemap.ratio = function(x) {
|
|
if (!arguments.length) return ratio;
|
|
ratio = x;
|
|
return treemap;
|
|
};
|
|
|
|
return d3_layout_hierarchyRebind(treemap, hierarchy);
|
|
};
|
|
|
|
function d3_layout_treemapPadNull(node) {
|
|
return {x: node.x, y: node.y, dx: node.dx, dy: node.dy};
|
|
}
|
|
|
|
function d3_layout_treemapPad(node, padding) {
|
|
var x = node.x + padding[3],
|
|
y = node.y + padding[0],
|
|
dx = node.dx - padding[1] - padding[3],
|
|
dy = node.dy - padding[0] - padding[2];
|
|
if (dx < 0) { x += dx / 2; dx = 0; }
|
|
if (dy < 0) { y += dy / 2; dy = 0; }
|
|
return {x: x, y: y, dx: dx, dy: dy};
|
|
}
|
|
})();
|