PortaleOrdiniGruppo/PortalStudio/d3-sankey.js
2025-03-24 15:28:26 +01:00

336 lines
9.3 KiB
JavaScript

d3.sankey = function Sankey() {
var x0 = 0, y0 = 0, x1 = 1, y1 = 1, // extent
dx = 24, // nodeWidth
py = 8, // nodePadding
id = defaultId,
align = d3.sankeyJustify,
sort,
nodes = defaultNodes,
links = defaultLinks,
iterations = 6;
function sankey() {
var graph = {nodes: nodes.apply(null, arguments), links: links.apply(null, arguments)};
computeNodeLinks(graph);
computeNodeValues(graph);
computeNodeDepths(graph);
computeNodeBreadths(graph);
computeLinkBreadths(graph);
return graph;
}
sankey.update = function(graph) {
computeLinkBreadths(graph);
return graph;
};
sankey.nodeId = function(_) {
return arguments.length ? (id = typeof _ === "function" ? _ : constant(_), sankey) : id;
};
sankey.nodeAlign = function(_) {
return arguments.length ? (align = typeof _ === "function" ? _ : constant(_), sankey) : align;
};
sankey.nodeSort = function(_) {
return arguments.length ? (sort = _, sankey) : sort;
};
sankey.nodeWidth = function(_) {
return arguments.length ? (dx = +_, sankey) : dx;
};
sankey.nodePadding = function(_) {
return arguments.length ? (py = +_, sankey) : py;
};
sankey.nodes = function(_) {
return arguments.length ? (nodes = typeof _ === "function" ? _ : constant(_), sankey) : nodes;
};
sankey.links = function(_) {
return arguments.length ? (links = typeof _ === "function" ? _ : constant(_), sankey) : links;
};
sankey.size = function(_) {
return arguments.length ? (x0 = y0 = 0, x1 = +_[0], y1 = +_[1], sankey) : [x1 - x0, y1 - y0];
};
sankey.extent = function(_) {
return arguments.length ? (x0 = +_[0][0], x1 = +_[1][0], y0 = +_[0][1], y1 = +_[1][1], sankey) : [[x0, y0], [x1, y1]];
};
sankey.iterations = function(_) {
return arguments.length ? (iterations = +_, sankey) : iterations;
};
// Populate the sourceLinks and targetLinks for each node.
// Also, if the source and target are not objects, assume they are indices.
function computeNodeLinks(graph) {
graph.nodes.forEach(function(node, i) {
node.index = i;
node.sourceLinks = [];
node.targetLinks = [];
});
var nodeById = d3.map(graph.nodes, id);
graph.links.forEach(function(link, i) {
link.index = i;
var source = link.source, target = link.target;
if (typeof source !== "object") source = link.source = find(nodeById, source);
if (typeof target !== "object") target = link.target = find(nodeById, target);
source.sourceLinks.push(link);
target.targetLinks.push(link);
});
}
// Compute the value (size) of each node by summing the associated links.
function computeNodeValues(graph) {
graph.nodes.forEach(function(node) {
node.value = Math.max(
d3.sum(node.sourceLinks, value),
d3.sum(node.targetLinks, value)
);
});
}
// Iteratively assign the depth (x-position) for each node.
// Nodes are assigned the maximum depth of incoming neighbors plus one;
// nodes with no incoming links are assigned depth zero, while
// nodes with no outgoing links are assigned the maximum depth.
function computeNodeDepths(graph) {
var nodes, next, x, n = graph.nodes.length;
for (nodes = graph.nodes, next = [], x = 0; nodes.length; ++x, nodes = next, next = []) {
if (x > n) throw new Error("circular link");
nodes.forEach(function(node) {
node.depth = x;
node.sourceLinks.forEach(function(link) {
if (next.indexOf(link.target) < 0) {
next.push(link.target);
}
});
});
}
for (nodes = graph.nodes, next = [], x = 0; nodes.length; ++x, nodes = next, next = []) {
if (x > n) throw new Error("circular link");
nodes.forEach(function(node) {
node.height = x;
node.targetLinks.forEach(function(link) {
if (next.indexOf(link.source) < 0) {
next.push(link.source);
}
});
});
}
var kx = (x1 - x0 - dx) / (x - 1);
graph.nodes.forEach(function(node) {
node.x1 = (node.x0 = x0 + Math.max(0, Math.min(x - 1, Math.floor(align.call(null, node, x)))) * kx) + dx;
});
}
function computeNodeBreadths(graph) {
var columns = d3.nest()
.key(function(d) { return d.x0; })
.sortKeys(d3.ascending)
.entries(graph.nodes)
.map(function(d) { return d.values; });
//
initializeNodeBreadth();
resolveCollisions();
for (var alpha = 0.9, n = iterations; n > 0; --n, alpha *= 0.9) {
relaxRightToLeft(alpha);
resolveCollisions();
relaxLeftToRight(alpha);
resolveCollisions();
}
function initializeNodeBreadth() {
var ky = d3.min(columns, function(nodes) {
return (y1 - y0 - (nodes.length - 1) * py) / d3.sum(nodes, value);
});
columns.forEach(function(nodes) {
if (sort != null) nodes.sort(sort);
nodes.forEach(function(node, i) {
node.y1 = (node.y0 = i) + node.value * ky;
});
});
graph.links.forEach(function(link) {
link.width = link.value * ky;
});
}
function relaxLeftToRight(alpha) {
columns.forEach(function(nodes) {
nodes.forEach(function(node) {
let y = node.y0;
for (const {target, width, value} of node.sourceLinks.sort(ascendingTargetBreadth)) {
if (value > 0) {
let dy = 0;
for (const {source, width} of target.targetLinks) {
if (source === node) break;
dy += width + py / 2;
}
dy = (y - dy - target.y0) * alpha * (value / Math.min(node.value, target.value));
target.y0 += dy;
target.y1 += dy;
}
y += width + py / 2;
}
});
});
}
function relaxRightToLeft(alpha) {
columns.slice().reverse().forEach(function(nodes) {
nodes.forEach(function(node) {
let y = node.y0;
for (const {source, width, value} of node.targetLinks.sort(ascendingSourceBreadth)) {
if (value > 0) {
let dy = 0;
for (const {target, width} of source.sourceLinks) {
if (target === node) break;
dy += width + py / 2;
}
dy = (y - dy - source.y0) * alpha * (value / Math.min(node.value, source.value));
source.y0 += dy;
source.y1 += dy;
}
y += width + py / 2;
}
});
});
}
function resolveCollisions() {
columns.forEach(function(nodes) {
var node,
dy,
y = y0,
n = nodes.length,
i;
// Push any overlapping nodes down.
if (sort === undefined) nodes.sort(ascendingBreadth);
for (i = 0; i < n; ++i) {
node = nodes[i];
dy = y - node.y0;
if (dy > 0) node.y0 += dy, node.y1 += dy;
y = node.y1 + py;
}
// If the bottommost node goes outside the bounds, push it back up.
dy = y - py - y1;
if (dy > 0) {
y = (node.y0 -= dy), node.y1 -= dy;
// Push any overlapping nodes back up.
for (i = n - 2; i >= 0; --i) {
node = nodes[i];
dy = node.y1 + py - y;
if (dy > 0) node.y0 -= dy, node.y1 -= dy;
y = node.y0;
}
}
});
}
}
function computeLinkBreadths(graph) {
graph.nodes.forEach(function(node) {
node.sourceLinks.sort(ascendingTargetBreadth);
node.targetLinks.sort(ascendingSourceBreadth);
});
graph.nodes.forEach(function(node) {
var y0 = node.y0, y1 = y0;
node.sourceLinks.forEach(function(link) {
link.y0 = y0 + link.width / 2, y0 += link.width;
});
node.targetLinks.forEach(function(link) {
link.y1 = y1 + link.width / 2, y1 += link.width;
});
});
}
function ascendingSourceBreadth(a, b) {
return ascendingBreadth(a.source, b.source) || a.index - b.index;
}
function ascendingTargetBreadth(a, b) {
return ascendingBreadth(a.target, b.target) || a.index - b.index;
}
function ascendingBreadth(a, b) {
return a.y0 - b.y0;
}
function value(d) {
return d.value;
}
function defaultId(d) {
return d.index;
}
function defaultNodes(graph) {
return graph.nodes;
}
function defaultLinks(graph) {
return graph.links;
}
function find(nodeById, id) {
var node = nodeById.get(id);
if (!node) throw new Error("missing: " + id);
return node;
}
function constant(x) {
return function() {
return x;
};
}
return sankey;
}
d3.sankeyLeft = function (node) {
return node.depth;
}
d3.sankeyRight = function (node, n) {
return n - 1 - node.height;
}
d3.sankeyJustify = function (node, n) {
return node.sourceLinks.length ? node.depth : n - 1;
}
d3.sankeyCenter = function (node) {
function targetDepth(d) {
return d.target.depth;
}
return node.targetLinks.length ? node.depth
: node.sourceLinks.length ? d3.min(node.sourceLinks, targetDepth) - 1
: 0;
}
d3.sankeyLinkHorizontal = function () {
function horizontalSource(d) {
return [d.source.x1, d.y0];
}
function horizontalTarget(d) {
return [d.target.x0, d.y1];
}
return d3.linkHorizontal()
.source(horizontalSource)
.target(horizontalTarget);
}