Commit 513e0580 authored by Matija Obreza's avatar Matija Obreza
Browse files

Use origin, sink and code names instead of indices

parent 2b164824
...@@ -28,7 +28,7 @@ const format = (d) => { ...@@ -28,7 +28,7 @@ const format = (d) => {
return `${f(d)}`; return `${f(d)}`;
} }
const sankeyx = ({ nodes, transfers }) => { const sankeyx = ({ origin, sink, transfers }) => {
const sankey = d3.sankey() const sankey = d3.sankey()
.nodeWidth(15) .nodeWidth(15)
.nodePadding(10) .nodePadding(10)
...@@ -36,7 +36,7 @@ const sankeyx = ({ nodes, transfers }) => { ...@@ -36,7 +36,7 @@ const sankeyx = ({ nodes, transfers }) => {
[1, 1], [1, 1],
[width - 1, height - 5] [width - 1, height - 5]
]); ]);
return sankey({ nodes, transfers }); return sankey({ origin, sink, transfers });
} }
const chart = (data) => { const chart = (data) => {
...@@ -56,7 +56,7 @@ const chart = (data) => { ...@@ -56,7 +56,7 @@ const chart = (data) => {
} = sankeyx(data); } = sankeyx(data);
svg.append("g") svg.append("g")
.attr("stroke", "#000") .attr("stroke", "#909090")
.selectAll("rect") .selectAll("rect")
.data(nodes) .data(nodes)
.join("rect") .join("rect")
...@@ -142,25 +142,22 @@ const chart = (data) => { ...@@ -142,25 +142,22 @@ const chart = (data) => {
return svg.node(); return svg.node();
} }
// TODO Consider using codes in target array
// TODO and then specify origin and end node names as MEX002 and NOR051
const data = { const data = {
nodes: [ origin: "MEX002",
{ name: "MEX002" }, sink: "NOR051",
{ name: "COL003" },
{ name: "USA996" },
{ name: "SOM000" },
{ name: "NOR051" }
],
transfers: [ transfers: [
{ value: 50, target: [ 4 ] }, { value: 50, target: [ "POR001", "NOR051" ] },
{ value: 30, target: [ 1, 3, 4 ]}, { value: 30, target: [ "COL003", "SOM001", "POR001" ]},
{ value: 40, target: [ 1, 4 ]}, { value: 40, target: [ "COL003", "NOR052" ]},
{ value: 100, target: [ 1, 2, 3, 4 ]}, { value: 100, target: [ "COL003", "USA996", "SOM000", "POR001" ]},
{ value: 30, target: [ 2, 4 ]}, { value: 30, target: [ "USA996", "NOR052" ]},
{ value: 25, target: [ 3 ] }, { value: 25, target: [ "SOM000" ] },
{ value: 50, target: [ 1, 2, 4 ]}, { value: 50, target: [ "COL003", "USA996", "NOR051" ]},
{ value: 40, target: [ 2 ] }, { value: 40, target: [ "USA996" ] },
{ value: 10, target: [ 3, 4 ]}, { value: 10, target: [ "SOM000", "NOR051" ]},
{ value: 50, target: [ ] }, { value: 500, target: [ ] },
] ]
}; };
......
...@@ -36,7 +36,7 @@ All rights reserved. ...@@ -36,7 +36,7 @@ All rights reserved.
import {ascending, min, sum} from "d3-array"; import {ascending, min, sum} from "d3-array";
import {map, nest} from "d3-collection"; import {map, nest} from "d3-collection";
import {justify} from "d3-sankey/src/align"; import {left, justify} from "d3-sankey/src/align";
import constant from "d3-sankey/src/constant"; import constant from "d3-sankey/src/constant";
import { notDeepEqual } from "assert"; import { notDeepEqual } from "assert";
...@@ -72,32 +72,46 @@ function defaultId(d) { ...@@ -72,32 +72,46 @@ function defaultId(d) {
return d.index; return d.index;
} }
function defaultNodes(graph) { function defaultName(d) {
return graph.nodes; return d.name;
} }
function defaultTransfers(graph) { function defaultTransfers(graph) {
return graph.transfers; return graph.transfers;
} }
function defaultOrigin(graph) {
return graph.origin;
}
function defaultSink(graph) {
return graph.sink;
}
function find(nodeById, id) { function find(nodeById, id) {
var node = nodeById.get(id); var node = nodeById.get(id);
if (!node) throw new Error("missing: " + id); // if (!node) throw new Error("missing: " + id);
return node; return node;
} }
function customJustify(node, n) {
return node.depth;
}
export default function() { export default function() {
var x0 = 0, y0 = 0, x1 = 1, y1 = 1, // extent var x0 = 0, y0 = 0, x1 = 1, y1 = 1, // extent
dx = 24, // nodeWidth dx = 24, // nodeWidth
py = 8, // nodePadding py = 8, // nodePadding
id = defaultId, id = defaultId,
align = justify, name = defaultName,
nodes = defaultNodes, align = left, // customJustify, //justify,
transfers = defaultTransfers, transfers = defaultTransfers,
origin = defaultOrigin,
sink = defaultSink,
iterations = 32; iterations = 32;
function sankey() { function sankey() {
const graph = {nodes: nodes.apply(null, arguments), transfers: transfers.apply(null, arguments)}; const graph = { origin: origin.apply(null, arguments), sink: sink.apply(null, arguments), transfers: transfers.apply(null, arguments)};
console.log(`Processing graph`, graph); console.log(`Processing graph`, graph);
computeNodeLinks(graph); computeNodeLinks(graph);
computeNodeValues(graph); computeNodeValues(graph);
...@@ -148,32 +162,58 @@ export default function() { ...@@ -148,32 +162,58 @@ export default function() {
return arguments.length ? (iterations = +_, sankey) : iterations; return arguments.length ? (iterations = +_, sankey) : iterations;
}; };
function addNode(name, depth) {
const node = {
name: name,
depth: depth,
sourceLinks: [],
targetLinks: [],
};
console.log(`Adding node ${name} depth=${depth}`, node);
return node;
}
// Populate the sourceLinks and targetLinks for each node. // Populate the sourceLinks and targetLinks for each node.
// Also, if the source and target are not objects, assume they are indices. // Also, if the source and target are not objects, assume they are indices.
function computeNodeLinks(graph) { function computeNodeLinks(graph) {
graph.nodes.forEach(function(node, i) { graph.nodes = [];
node.index = i;
node.sourceLinks = []; const sourceNode = addNode(graph.origin, 0)
node.targetLinks = []; const intermediateNode = addNode(``, 1);
intermediateNode.intermediate = true;
const endNode = addNode(graph.sink, 2);
graph.nodes.push(sourceNode);
graph.nodes.push(intermediateNode);
graph.nodes.push(endNode);
var nodeByName = map(graph.nodes, name);
graph.transfers.forEach((transfer, i) => {
transfer.target.forEach((target) => {
var targetNode = find(nodeByName, target);
if (targetNode === undefined) {
graph.nodes.push(addNode(target, 1));
nodeByName = map(graph.nodes, name);
} else {
console.log(`Found existing target node ${target}`, targetNode);
}
})
}); });
const endNodeIndex = graph.nodes.length - 1;
var nodeById = map(graph.nodes, id);
const endNode = find(nodeById, endNodeIndex);
console.log(`End node is`, endNode);
graph.links = []; graph.links = [];
graph.transfers.forEach((transfer, i) => { graph.transfers.forEach((transfer, i) => {
console.log(`Transfer ${i}`, transfer); console.log(`Transfer ${i}`, transfer);
const source = find(nodeById, 0); // console.log(`Has end node?`, transfer.target.find((target) => target === endNode.name));
const includesEndNode = transfer.target.find((target) => target === endNodeIndex) >= 0; const includesEndNode = transfer.target.find((target) => target === endNode.name) !== undefined;
console.log(`Includes end node? ${includesEndNode}`); console.log(`Includes end node? ${includesEndNode}`);
transfer.target.forEach((target, j) => { transfer.target.forEach((target, j) => {
console.log(`T ${i},${j} -> ${target}`); console.log(`T ${i},${j} -> ${target}`);
if (typeof target !== "object") target = find(nodeById, target); if (typeof target !== "object") target = find(nodeByName, target);
if (target !== endNode) { if (target !== endNode) {
const link = { value: transfer.value, source, target, cluster: i }; const link = { value: transfer.value, source: sourceNode, target, cluster: i };
// console.log(`Registering transfer`, link); // console.log(`Registering transfer`, link);
source.sourceLinks.push(link); sourceNode.sourceLinks.push(link);
target.targetLinks.push(link); target.targetLinks.push(link);
graph.links.push(link); graph.links.push(link);
if (includesEndNode && transfer.target.length > 1) { if (includesEndNode && transfer.target.length > 1) {
...@@ -183,14 +223,22 @@ export default function() { ...@@ -183,14 +223,22 @@ export default function() {
graph.links.push(link); graph.links.push(link);
} }
} else if (target === endNode && transfer.target.length === 1) { } else if (target === endNode && transfer.target.length === 1) {
const link = { value: transfer.value, source, target, cluster: i }; const link1 = { value: transfer.value, source: sourceNode, target: intermediateNode, cluster: i };
// console.log(`Registering transfer`, link); const link2 = { value: transfer.value, source: intermediateNode, target: endNode, cluster: i };
source.sourceLinks.push(link); console.log(`Adding direct origin -> sink link`, link1, link2);
target.targetLinks.push(link); sourceNode.sourceLinks.push(link1);
graph.links.push(link); intermediateNode.targetLinks.push(link1);
intermediateNode.value = transfer.value;
intermediateNode.sourceLinks.push(link2);
target.targetLinks.push(link2);
graph.links.push(link1);
graph.links.push(link2);
} }
}); });
}); });
graph.nodes = graph.nodes.filter((node) => node.sourceLinks.length > 0 || node.targetLinks.length > 0);
console.log(`Processed graph`, graph); console.log(`Processed graph`, graph);
// graph.links.forEach(function(link, i) { // graph.links.forEach(function(link, i) {
// link.index = i; // link.index = i;
...@@ -204,20 +252,21 @@ export default function() { ...@@ -204,20 +252,21 @@ export default function() {
// Compute the value (size) of each node by summing the associated links. // Compute the value (size) of each node by summing the associated links.
function computeNodeValues(graph) { function computeNodeValues(graph) {
var nodeById = map(graph.nodes, id); var nodeByName = map(graph.nodes, name);
const sourceNode = find(nodeById, 0); const sourceNode = find(nodeByName, graph.origin);
sourceNode.value = sum(graph.transfers, value); sourceNode.value = sum(graph.transfers, value);
console.log(`Source node value: ${sourceNode.value}`); console.log(`Source node value: ${sourceNode.value}`);
const endNodeIndex = graph.nodes.length - 1;
const endNode = find(nodeById, endNodeIndex); const endNode = find(nodeByName, graph.sink);
endNode.value = sum(graph.transfers.filter((transfer) => transfer.target.find((target) => target === endNodeIndex) >= 0 ), value); if (endNode) {
console.log(`End node[${endNodeIndex}] value: ${endNode.value}`); endNode.value = sum(graph.transfers.filter((transfer) => transfer.target.find((target) => target === endNode.name)), value);
console.log(`End node["${endNode.name}"] value: ${endNode.value}`);
}
graph.transfers.forEach((transfer) => { graph.transfers.forEach((transfer) => {
transfer.target.forEach((target, j) => { transfer.target.forEach((target, j) => {
if (target !== endNodeIndex) { if (!endNode || target !== endNode.name) {
if (typeof target !== "object") target = find(nodeById, target); if (typeof target !== "object") target = find(nodeByName, target);
console.log(`Updating node[${j}].value`); console.log(`Updating node[${j}].value`);
target.value = sum(target.targetLinks, value); target.value = sum(target.targetLinks, value);
} }
...@@ -232,17 +281,9 @@ export default function() { ...@@ -232,17 +281,9 @@ export default function() {
function computeNodeDepths(graph) { function computeNodeDepths(graph) {
var nodes, next, x; var nodes, next, x;
var nodeById = map(graph.nodes, id);
const sourceNode = find(nodeById, 0);
const endNodeIndex = graph.nodes.length - 1;
const endNode = find(nodeById, endNodeIndex);
graph.nodes.forEach((node, i, l) => {
node.depth = i === 0 ? 0 : i === l.length - 1 ? 2 : 1;
});
for (nodes = graph.nodes, next = [], x = 0; nodes.length; ++x, nodes = next, next = []) { for (nodes = graph.nodes, next = [], x = 0; nodes.length; ++x, nodes = next, next = []) {
nodes.forEach(function(node) { nodes.forEach(function(node) {
console.log(`Node ${node.name} x=${x}`);
node.height = x; node.height = x;
node.targetLinks.forEach((link) => { node.targetLinks.forEach((link) => {
if (next.indexOf(link.source) < 0) { if (next.indexOf(link.source) < 0) {
...@@ -252,9 +293,19 @@ export default function() { ...@@ -252,9 +293,19 @@ export default function() {
}); });
} }
graph.nodes.forEach((node) => {
console.log(`Node ${node.name} h=${node.height} d=${node.depth}`);
// node.height = 2 - node.depth;
});
var kx = (x1 - x0 - dx) / (x - 1); var kx = (x1 - x0 - dx) / (x - 1);
graph.nodes.forEach(function(node) { graph.nodes.forEach(function(node) {
console.log(`Node ${node.name} x=${x} kx=${kx} ${node.depth}`);
node.x1 = (node.x0 = x0 + Math.max(0, Math.min(x - 1, Math.floor(align.call(null, node, x)))) * kx) + dx; node.x1 = (node.x0 = x0 + Math.max(0, Math.min(x - 1, Math.floor(align.call(null, node, x)))) * kx) + dx;
if (node.intermediate) {
// console.log(`Intermediate node x0=${node.x0} x1=${node.x1} diff=${node.x1 - node.x0}`);
node.x1 = node.x0;
}
}); });
} }
...@@ -265,7 +316,7 @@ export default function() { ...@@ -265,7 +316,7 @@ export default function() {
.entries(graph.nodes) .entries(graph.nodes)
.map(function(d) { return d.values; }); .map(function(d) { return d.values; });
// console.log(`Columns:`, columns); console.log(`Columns:`, columns);
initializeNodeBreadth(); initializeNodeBreadth();
resolveCollisions(); resolveCollisions();
...@@ -364,10 +415,15 @@ export default function() { ...@@ -364,10 +415,15 @@ export default function() {
}); });
const sourcePositions = {}; const sourcePositions = {};
const endPositions = {}; const endPositions = {};
var nodeByName = map(graph.nodes, name);
const sourceNode = find(nodeByName, graph.origin);
const endNode = find(nodeByName, graph.sink);
graph.nodes.forEach((node, i) => { graph.nodes.forEach((node, i) => {
var y0 = node.y0, y1 = y0; var y0 = node.y0, y1 = y0;
node.sourceLinks.forEach((link) => { node.sourceLinks.forEach((link) => {
if (i === 0) { if (node === sourceNode) {
const clusterPos = sourcePositions[link.cluster]; const clusterPos = sourcePositions[link.cluster];
// console.log(`Doing source link.y0 cluster=${link.cluster} pos=${clusterPos} on`, link); // console.log(`Doing source link.y0 cluster=${link.cluster} pos=${clusterPos} on`, link);
if (clusterPos === undefined) { if (clusterPos === undefined) {
...@@ -381,7 +437,7 @@ export default function() { ...@@ -381,7 +437,7 @@ export default function() {
} }
}); });
node.targetLinks.forEach(function(link) { node.targetLinks.forEach(function(link) {
if (i === graph.nodes.length - 1) { if (node === endNode) {
const clusterPos = endPositions[link.cluster]; const clusterPos = endPositions[link.cluster];
// console.log(`Doing end link.y0 cluster=${link.cluster} pos=${clusterPos} on`, link); // console.log(`Doing end link.y0 cluster=${link.cluster} pos=${clusterPos} on`, link);
if (clusterPos === undefined) { if (clusterPos === undefined) {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment