Built with blockbuilder.org
forked from RobinL's block: sankey experiment 1
license: mit |
Built with blockbuilder.org
forked from RobinL's block: sankey experiment 1
<!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 }); | |
}))); |