@antv/g2
Version:
the Grammar of Graphics in Javascript
392 lines • 13.6 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.Sankey = void 0;
const d3_array_1 = require("d3-array");
const align_1 = require("./align");
const constant_1 = require("./constant");
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) {
const node = nodeById.get(id);
if (!node)
throw new Error('missing: ' + id);
return node;
}
function computeLinkBreadths({ nodes }) {
for (const node of nodes) {
let y0 = node.y0;
let y1 = y0;
for (const link of node.sourceLinks) {
link.y0 = y0 + link.width / 2;
y0 += link.width;
}
for (const link of node.targetLinks) {
link.y1 = y1 + link.width / 2;
y1 += link.width;
}
}
}
function Sankey() {
let x0 = 0, y0 = 0, x1 = 1, y1 = 1; // extent
let dx = 24; // nodeWidth
let dy = 8, py; // nodePadding
let id = defaultId;
let align = align_1.justify;
let depth;
let sort;
let linkSort;
let nodes = defaultNodes;
let links = defaultLinks;
let iterations = 6;
function sankey(arg) {
const graph = {
nodes: nodes(arg),
links: links(arg),
};
computeNodeLinks(graph);
computeNodeValues(graph);
computeNodeDepths(graph);
computeNodeHeights(graph);
computeNodeBreadths(graph);
computeLinkBreadths(graph);
return graph;
}
sankey.update = function (graph) {
computeLinkBreadths(graph);
return graph;
};
sankey.nodeId = function (_) {
return arguments.length
? ((id = typeof _ === 'function' ? _ : (0, constant_1.constant)(_)), sankey)
: id;
};
sankey.nodeAlign = function (_) {
return arguments.length
? ((align = typeof _ === 'function' ? _ : (0, constant_1.constant)(_)), sankey)
: align;
};
sankey.nodeDepth = function (_) {
return arguments.length
? ((depth = typeof _ === 'function' ? _ : _), sankey)
: depth;
};
sankey.nodeSort = function (_) {
return arguments.length ? ((sort = _), sankey) : sort;
};
sankey.nodeWidth = function (_) {
return arguments.length ? ((dx = +_), sankey) : dx;
};
sankey.nodePadding = function (_) {
return arguments.length ? ((dy = py = +_), sankey) : dy;
};
sankey.nodes = function (_) {
return arguments.length
? ((nodes = typeof _ === 'function' ? _ : (0, constant_1.constant)(_)), sankey)
: nodes;
};
sankey.links = function (_) {
return arguments.length
? ((links = typeof _ === 'function' ? _ : (0, constant_1.constant)(_)), sankey)
: links;
};
sankey.linkSort = function (_) {
return arguments.length ? ((linkSort = _), sankey) : linkSort;
};
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;
};
function computeNodeLinks({ nodes, links }) {
nodes.forEach((node, idx) => {
node.index = idx;
node.sourceLinks = [];
node.targetLinks = [];
});
const nodeById = new Map(nodes.map((d) => [id(d), d]));
links.forEach((link, idx) => {
link.index = idx;
let { source, target } = link;
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);
});
if (linkSort != null) {
for (const { sourceLinks, targetLinks } of nodes) {
sourceLinks.sort(linkSort);
targetLinks.sort(linkSort);
}
}
}
function computeNodeValues({ nodes }) {
for (const node of nodes) {
node.value =
node.fixedValue === undefined
? Math.max((0, d3_array_1.sum)(node.sourceLinks, value), (0, d3_array_1.sum)(node.targetLinks, value))
: node.fixedValue;
}
}
function computeNodeDepths({ nodes }) {
const n = nodes.length;
let current = new Set(nodes);
let next = new Set();
let x = 0;
while (current.size) {
current.forEach((node) => {
node.depth = x;
for (const { target } of node.sourceLinks) {
next.add(target);
}
});
if (++x > n)
throw new Error('circular link');
current = next;
next = new Set();
}
// 如果配置了 depth,则设置自定义 depth
if (depth) {
const maxDepth = Math.max((0, d3_array_1.max)(nodes, (d) => d.depth) + 1, 0);
let node;
for (let i = 0; i < nodes.length; i++) {
node = nodes[i];
node.depth = depth.call(null, node, maxDepth);
}
}
}
function computeNodeHeights({ nodes }) {
const n = nodes.length;
let current = new Set(nodes);
let next = new Set();
let x = 0;
while (current.size) {
current.forEach((node) => {
node.height = x;
for (const { source } of node.targetLinks) {
next.add(source);
}
});
if (++x > n)
throw new Error('circular link');
current = next;
next = new Set();
}
}
function computeNodeLayers({ nodes }) {
const x = Math.max((0, d3_array_1.max)(nodes, (d) => d.depth) + 1, 0);
const kx = (x1 - x0 - dx) / (x - 1);
const columns = new Array(x).fill(0).map(() => []);
for (const node of nodes) {
const i = Math.max(0, Math.min(x - 1, Math.floor(align.call(null, node, x))));
node.layer = i;
node.x0 = x0 + i * kx;
node.x1 = node.x0 + dx;
if (columns[i])
columns[i].push(node);
else
columns[i] = [node];
}
if (sort)
for (const column of columns) {
column.sort(sort);
}
return columns;
}
function initializeNodeBreadths(columns) {
const ky = (0, d3_array_1.min)(columns, (c) => (y1 - y0 - (c.length - 1) * py) / (0, d3_array_1.sum)(c, value));
for (const nodes of columns) {
let y = y0;
for (const node of nodes) {
node.y0 = y;
node.y1 = y + node.value * ky;
y = node.y1 + py;
for (const link of node.sourceLinks) {
link.width = link.value * ky;
}
}
y = (y1 - y + py) / (nodes.length + 1);
for (let i = 0; i < nodes.length; ++i) {
const node = nodes[i];
node.y0 += y * (i + 1);
node.y1 += y * (i + 1);
}
reorderLinks(nodes);
}
}
function computeNodeBreadths(graph) {
const columns = computeNodeLayers(graph);
py = Math.min(dy, (y1 - y0) / ((0, d3_array_1.max)(columns, (c) => c.length) - 1));
initializeNodeBreadths(columns);
for (let i = 0; i < iterations; ++i) {
const alpha = Math.pow(0.99, i);
const beta = Math.max(1 - alpha, (i + 1) / iterations);
relaxRightToLeft(columns, alpha, beta);
relaxLeftToRight(columns, alpha, beta);
}
}
// Reposition each node based on its incoming (target) links.
function relaxLeftToRight(columns, alpha, beta) {
for (let i = 1, n = columns.length; i < n; ++i) {
const column = columns[i];
for (const target of column) {
let y = 0;
let w = 0;
for (const { source, value } of target.targetLinks) {
const v = value * (target.layer - source.layer);
y += targetTop(source, target) * v;
w += v;
}
if (!(w > 0))
continue;
const dy = (y / w - target.y0) * alpha;
target.y0 += dy;
target.y1 += dy;
reorderNodeLinks(target);
}
if (sort === undefined)
column.sort(ascendingBreadth);
if (column.length)
resolveCollisions(column, beta);
}
}
// Reposition each node based on its outgoing (source) links.
function relaxRightToLeft(columns, alpha, beta) {
for (let n = columns.length, i = n - 2; i >= 0; --i) {
const column = columns[i];
for (const source of column) {
let y = 0;
let w = 0;
for (const { target, value } of source.sourceLinks) {
const v = value * (target.layer - source.layer);
y += sourceTop(source, target) * v;
w += v;
}
if (!(w > 0))
continue;
const dy = (y / w - source.y0) * alpha;
source.y0 += dy;
source.y1 += dy;
reorderNodeLinks(source);
}
if (sort === undefined)
column.sort(ascendingBreadth);
if (column.length)
resolveCollisions(column, beta);
}
}
function resolveCollisions(nodes, alpha) {
const i = nodes.length >> 1;
const subject = nodes[i];
resolveCollisionsBottomToTop(nodes, subject.y0 - py, i - 1, alpha);
resolveCollisionsTopToBottom(nodes, subject.y1 + py, i + 1, alpha);
resolveCollisionsBottomToTop(nodes, y1, nodes.length - 1, alpha);
resolveCollisionsTopToBottom(nodes, y0, 0, alpha);
}
// Push any overlapping nodes down.
function resolveCollisionsTopToBottom(nodes, y, i, alpha) {
for (; i < nodes.length; ++i) {
const node = nodes[i];
const dy = (y - node.y0) * alpha;
if (dy > 1e-6)
(node.y0 += dy), (node.y1 += dy);
y = node.y1 + py;
}
}
// Push any overlapping nodes up.
function resolveCollisionsBottomToTop(nodes, y, i, alpha) {
for (; i >= 0; --i) {
const node = nodes[i];
const dy = (node.y1 - y) * alpha;
if (dy > 1e-6)
(node.y0 -= dy), (node.y1 -= dy);
y = node.y0 - py;
}
}
function reorderNodeLinks({ sourceLinks, targetLinks }) {
if (linkSort === undefined) {
for (const { source: { sourceLinks }, } of targetLinks) {
sourceLinks.sort(ascendingTargetBreadth);
}
for (const { target: { targetLinks }, } of sourceLinks) {
targetLinks.sort(ascendingSourceBreadth);
}
}
}
function reorderLinks(nodes) {
if (linkSort === undefined) {
for (const { sourceLinks, targetLinks } of nodes) {
sourceLinks.sort(ascendingTargetBreadth);
targetLinks.sort(ascendingSourceBreadth);
}
}
}
// Returns the target.y0 that would produce an ideal link from source to target.
function targetTop(source, target) {
let y = source.y0 - ((source.sourceLinks.length - 1) * py) / 2;
for (const { target: node, width } of source.sourceLinks) {
if (node === target)
break;
y += width + py;
}
for (const { source: node, width } of target.targetLinks) {
if (node === source)
break;
y -= width;
}
return y;
}
// Returns the source.y0 that would produce an ideal link from source to target.
function sourceTop(source, target) {
let y = target.y0 - ((target.targetLinks.length - 1) * py) / 2;
for (const { source: node, width } of target.targetLinks) {
if (node === source)
break;
y += width + py;
}
for (const { target: node, width } of source.sourceLinks) {
if (node === target)
break;
y -= width;
}
return y;
}
return sankey;
}
exports.Sankey = Sankey;
//# sourceMappingURL=sankey.js.map
;