layout = {
const inflow = {}, outflow = {};
fruitsFlowData.links.forEach(link => {
outflow[link.source] = (outflow[link.source] || 0) + link.value;
inflow[link.target] = (inflow[link.target] || 0) + link.value;
});
fruitsFlowData.nodes.forEach(node => {
const input = inflow[node.id] || 0;
const output = outflow[node.id] || 0;
node.flow = Math.max(input, output);
});
const maxLevel = d3.max(fruitsFlowData.nodes, n => n.level);
const scaleX = d3.scaleLinear()
.domain([0, maxLevel + 1])
.range([0, width - layoutProp.nodeWidth]);
const levelFlow = {}, levelNodes = {};
fruitsFlowData.nodes.forEach(n => {
levelFlow[n.level] = (levelFlow[n.level] || 0) + n.flow;
levelNodes[n.level] = (levelNodes[n.level] || 0) + 1;
});
const maxFlowPerLevel = d3.max(Object.values(levelFlow));
const maxNodesPerLevel = d3.max(Object.values(levelNodes));
const scaleFlow = d3.scaleLinear()
.domain([0, maxFlowPerLevel])
.range([0, layoutProp.height - (maxNodesPerLevel - 1) * layoutProp.nodePadding]);
const levels = {};
fruitsFlowData.nodes.forEach(n => {
if (!levels[n.level]) levels[n.level] = [];
levels[n.level].push(n);
});
for (const level in levels) {
let y = 0;
for (const node of levels[level]) {
node.x = scaleX(node.level);
node.y = y;
node.height = scaleFlow(node.flow);
y += node.height + layoutProp.nodePadding;
}
}
const nodeMap = {};
fruitsFlowData.nodes.forEach(n => nodeMap[n.id] = n);
fruitsFlowData.links.forEach(link => {
link.sourceNode = nodeMap[link.source];
link.targetNode = nodeMap[link.target];
link.thickness = scaleFlow(link.value);
});
const bySource = {}, byTarget = {};
fruitsFlowData.links.forEach(link => {
(bySource[link.source] ||= []).push(link);
(byTarget[link.target] ||= []).push(link);
});
for (const id in bySource) {
let offset = 0;
for (const link of bySource[id].sort((a, b) => d3.ascending(a.fruit, b.fruit))) {
link.y0 = nodeMap[id].y + offset;
offset += link.thickness;
}
}
for (const id in byTarget) {
let offset = 0;
for (const link of byTarget[id].sort((a, b) => d3.ascending(a.fruit, b.fruit))) {
link.y1 = nodeMap[id].y + offset;
offset += link.thickness;
}
}
return { ...fruitsFlowData };
}