Skip to content

Instantly share code, notes, and snippets.

@RobinL
Last active September 15, 2017 13:41
Show Gist options
  • Save RobinL/313b29c938fb96bffee2a4d56d78dc42 to your computer and use it in GitHub Desktop.
Save RobinL/313b29c938fb96bffee2a4d56d78dc42 to your computer and use it in GitHub Desktop.
sankey experiment 2
license: mit
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="sankey.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
</style>
</head>
<body>
<div id="chart"></div>
<script>
// Feel free to change or delete any of the code you see in this editor!
var margin = {top: 10, right: 10, bottom: 10, left: 10},
width = 700 - margin.left - margin.right,
height = 511 - margin.top - margin.bottom;
var nodes = [
{"name": "Source1"},
{"name": "Source2"},
{"name": "Source3"},
{"name": "Server1"},
{"name": "Server2"},
{"name": "Analyst1"},
{"name": "Analyst2"},
];
var links = [
{"source": "Source1", "target": "Server1", "value": 10},
{"source": "Source2", "target": "Server1", "value" : 10},
{"source": "Source3", "target": "Server1", "value" : 10},
{"source": "Source1", "target": "Server2", "value" : 10},
{"source": "Source2", "target": "Server2", "value" : 10},
{"source": "Server1", "target": "Analyst1", "value" : 10},
{"source": "Server2", "target": "Analyst2", "value" : 10},
];
var graph = {"nodes": nodes, "links" : links}
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var sankey = d3.sankey()
.nodeWidth(16)
.nodePadding(200)
.size([width, height])
.nodeId(function(d) {return d.name})
var link = svg.append("g")
.attr("class", "links")
.attr("fill", "none")
.attr("stroke", "#000")
.attr("stroke-opacity", 0.2)
.selectAll("path");
var node = svg.append("g")
.attr("class", "nodes")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.selectAll("g");
sankey(graph)
function horizontalSource(d) {
return [d.source.x1, d.y0];
}
function horizontalTarget(d) {
return [d.target.x0, d.y1];
}
var sankeyLinkHorizontal = function() {
return d3.linkHorizontal()
.source(horizontalSource)
.target(horizontalTarget);
};
link = link
.data(graph.links)
.enter().append("path")
.attr("d", sankeyLinkHorizontal(function(x) {return 5}))
.attr("stroke-width", function(d) { return 5 });
node = node
.data(graph.nodes)
.enter().append("g");
node.append("rect")
.attr("x", function(d) { return d.x0; })
.attr("y", function(d) { return d.y0; })
.attr("height", function(d) { return d.y1 - d.y0; })
.attr("width", function(d) { return d.x1 - d.x0; })
.attr("fill", function(d) { return "#000"})
.attr("stroke", "#000");
node.append("text")
.attr("x", function(d) { return d.x0 - 6; })
.attr("y", function(d) { return (d.y1 + d.y0) / 2; })
.attr("dy", "0.35em")
.attr("text-anchor", "end")
.text(function(d) { return d.name; })
.filter(function(d) { return d.x0 < width / 2; })
.attr("x", function(d) { return d.x1 + 6; })
.attr("text-anchor", "start");
</script>
</body>
// https://github.com/d3/d3-sankey Version 0.7.1. Copyright 2017 Mike Bostock.
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-array'), require('d3-collection'), require('d3-shape')) :
typeof define === 'function' && define.amd ? define(['exports', 'd3-array', 'd3-collection', 'd3-shape'], factory) :
(factory((global.d3 = global.d3 || {}),global.d3,global.d3,global.d3));
}(this, (function (exports,d3Array,d3Collection,d3Shape) { 'use strict';
function targetDepth(d) {
return d.target.depth;
}
function left(node) {
return node.depth;
}
function right(node, n) {
return n - 1 - node.height;
}
function justify(node, n) {
return node.sourceLinks.length ? node.depth : n - 1;
}
function center(node) {
return node.targetLinks.length ? node.depth
: node.sourceLinks.length ? d3Array.min(node.sourceLinks, targetDepth) - 1
: 0;
}
function constant(x) {
return function() {
return x;
};
}
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 nodeCenter(node) {
return (node.y0 + node.y1) / 2;
}
function weightedSource(link) {
return nodeCenter(link.source) * link.value;
}
function weightedTarget(link) {
return nodeCenter(link.target) * link.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;
}
var sankey = function() {
var x0 = 0, y0 = 0, x1 = 1, y1 = 1, // extent
dx = 24, // nodeWidth
py = 8, // nodePadding
id = defaultId,
align = justify,
nodes = defaultNodes,
links = defaultLinks,
iterations = 32;
function sankey() {
var graph = {nodes: nodes.apply(null, arguments), links: links.apply(null, arguments)};
computeNodeLinks(graph);
computeNodeValues(graph);
computeNodeDepths(graph);
computeNodeBreadths(graph, iterations);
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.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 = d3Collection.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 = 10
});
}
// 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;
for (nodes = graph.nodes, next = [], x = 0; nodes.length; ++x, nodes = next, next = []) {
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 = []) {
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 = d3Collection.nest()
.key(function(d) { return d.x0; })
.sortKeys(d3Array.ascending)
.entries(graph.nodes)
.map(function(d) { return d.values; });
//
initializeNodeBreadth();
resolveCollisions();
for (var alpha = 1, n = iterations; n > 0; --n) {
relaxRightToLeft(alpha *= 0.99);
resolveCollisions();
relaxLeftToRight(alpha);
resolveCollisions();
}
function initializeNodeBreadth() {
var ky = d3Array.min(columns, function(nodes) {
return (y1 - (nodes.length - 1) * py) / d3Array.sum(nodes, value);
});
columns.forEach(function(nodes) {
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) {
if (node.targetLinks.length) {
var dy = (d3Array.sum(node.targetLinks, weightedSource) / d3Array.sum(node.targetLinks, value) - nodeCenter(node)) * alpha;
node.y0 += dy, node.y1 += dy;
}
});
});
}
function relaxRightToLeft(alpha) {
columns.slice().reverse().forEach(function(nodes) {
nodes.forEach(function(node) {
if (node.sourceLinks.length) {
var dy = (d3Array.sum(node.sourceLinks, weightedTarget) / d3Array.sum(node.sourceLinks, value) - nodeCenter(node)) * alpha;
node.y0 += dy, node.y1 += dy;
}
});
});
}
function resolveCollisions() {
columns.forEach(function(nodes) {
var node,
dy,
y = y0,
n = nodes.length,
i;
// Push any overlapping nodes down.
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;
});
});
}
return sankey;
};
function horizontalSource(d) {
return [d.source.x1, d.y0];
}
function horizontalTarget(d) {
return [d.target.x0, d.y1];
}
var sankeyLinkHorizontal = function() {
return d3Shape.linkHorizontal()
.source(horizontalSource)
.target(horizontalTarget);
};
exports.sankey = sankey;
exports.sankeyCenter = center;
exports.sankeyLeft = left;
exports.sankeyRight = right;
exports.sankeyJustify = justify;
exports.sankeyLinkHorizontal = sankeyLinkHorizontal;
Object.defineProperty(exports, '__esModule', { value: true });
})));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment