A demonstration of d3-sankey using Zoom control, drag nodes (vertically), tooltip boxs, colors links (by color's nodes) and nodes values.
Thanks Mike Bostock!
border: no | |
license: gpl-3.0 |
function d3sankey() { | |
var sankey = {}, | |
nodeWidth = 20, | |
nodePadding = 8, | |
size = [1, 1], | |
nodes = [], | |
links = []; | |
sankey.nodeWidth = function(_) { | |
if (!arguments.length) return nodeWidth; | |
nodeWidth = +_; | |
return sankey; | |
}; | |
sankey.nodePadding = function(_) { | |
if (!arguments.length) return nodePadding; | |
nodePadding = +_; | |
return sankey; | |
}; | |
sankey.nodes = function(_) { | |
if (!arguments.length) return nodes; | |
nodes = _; | |
return sankey; | |
}; | |
sankey.links = function(_) { | |
if (!arguments.length) return links; | |
links = _; | |
return sankey; | |
}; | |
sankey.size = function(_) { | |
if (!arguments.length) return size; | |
size = _; | |
return sankey; | |
}; | |
sankey.layout = function(iterations) { | |
computeNodeLinks(); | |
computeNodeValues(); | |
computeNodeBreadths(); | |
computeNodeDepths(iterations); | |
computeLinkDepths(); | |
return sankey; | |
}; | |
sankey.relayout = function() { | |
computeLinkDepths(); | |
return sankey; | |
}; | |
sankey.link = function() { | |
var curvature = .5; | |
function link(d) { | |
var x0 = d.source.x + d.source.dx, | |
x1 = d.target.x, | |
xi = d3.interpolateNumber(x0, x1), | |
x2 = xi(curvature), | |
x3 = xi(1 - curvature), | |
y0 = d.source.y + d.sy + d.dy / 2, | |
y1 = d.target.y + d.ty + d.dy / 2; | |
return "M" + x0 + "," + y0 + "C" + x2 + "," + y0 + " " + x3 + "," + y1 + " " + x1 + "," + y1; | |
} | |
link.curvature = function(_) { | |
if (!arguments.length) return curvature; | |
curvature = +_; | |
return link; | |
}; | |
return link; | |
}; | |
// Populate the sourceLinks and targetLinks for each node. | |
// Also, if the source and target are not objects, assume they are indices. | |
function computeNodeLinks() { | |
nodes.forEach(function(node) { | |
node.sourceLinks = []; | |
node.targetLinks = []; | |
}); | |
links.forEach(function(link) { | |
var source = link.source, | |
target = link.target; | |
if (typeof source === "number") source = link.source = nodes[link.source]; | |
if (typeof target === "number") target = link.target = nodes[link.target]; | |
source.sourceLinks.push(link); | |
target.targetLinks.push(link); | |
}); | |
} | |
// Compute the value (size) of each node by summing the associated links. | |
function computeNodeValues() { | |
nodes.forEach(function(node) { | |
node.value = Math.max( | |
d3.sum(node.sourceLinks, value), | |
d3.sum(node.targetLinks, value)); | |
}); | |
} | |
// Iteratively assign the breadth (x-position) for each node. | |
// Nodes are assigned the maximum breadth of incoming neighbors plus one; | |
// nodes with no incoming links are assigned breadth zero, while | |
// nodes with no outgoing links are assigned the maximum breadth. | |
function computeNodeBreadths() { | |
var remainingNodes = nodes, | |
nextNodes, | |
x = 0; | |
while (remainingNodes.length) { | |
nextNodes = []; | |
remainingNodes.forEach(function(node) { | |
node.x = x; | |
node.dx = nodeWidth; | |
node.sourceLinks.forEach(function(link) { | |
nextNodes.push(link.target); | |
}); | |
}); | |
remainingNodes = nextNodes; | |
++x; | |
} | |
// | |
moveSinksRight(x); | |
scaleNodeBreadths((size[0] - nodeWidth) / (x - 1)); | |
} | |
function moveSourcesRight() { | |
nodes.forEach(function(node) { | |
if (!node.targetLinks.length) { | |
node.x = d3.min(node.sourceLinks, function(d) { | |
return d.target.x; | |
}) - 1; | |
} | |
}); | |
} | |
function moveSinksRight(x) { | |
nodes.forEach(function(node) { | |
if (!node.sourceLinks.length) { | |
node.x = x - 1; | |
} | |
}); | |
} | |
function scaleNodeBreadths(kx) { | |
nodes.forEach(function(node) { | |
node.x *= kx; | |
}); | |
} | |
function computeNodeDepths(iterations) { | |
var nodesByBreadth = d3.nest() | |
.key(function(d) { | |
return d.x; | |
}) | |
.sortKeys(d3.ascending) | |
.entries(nodes) | |
.map(function(d) { | |
return d.values; | |
}); | |
// | |
initializeNodeDepth(); | |
resolveCollisions(); | |
for (var alpha = 1; iterations > 0; --iterations) { | |
relaxRightToLeft(alpha *= .99); | |
resolveCollisions(); | |
relaxLeftToRight(alpha); | |
resolveCollisions(); | |
} | |
function initializeNodeDepth() { | |
var ky = d3.min(nodesByBreadth, function(nodes) { | |
return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes, value); | |
}); | |
nodesByBreadth.forEach(function(nodes) { | |
nodes.forEach(function(node, i) { | |
node.y = i; | |
node.dy = node.value * ky; | |
}); | |
}); | |
links.forEach(function(link) { | |
link.dy = link.value * ky; | |
}); | |
} | |
function relaxLeftToRight(alpha) { | |
nodesByBreadth.forEach(function(nodes, breadth) { | |
nodes.forEach(function(node) { | |
if (node.targetLinks.length) { | |
var y = d3.sum(node.targetLinks, weightedSource) / d3.sum(node.targetLinks, value); | |
node.y += (y - center(node)) * alpha; | |
} | |
}); | |
}); | |
function weightedSource(link) { | |
return center(link.source) * link.value; | |
} | |
} | |
function relaxRightToLeft(alpha) { | |
nodesByBreadth.slice().reverse().forEach(function(nodes) { | |
nodes.forEach(function(node) { | |
if (node.sourceLinks.length) { | |
var y = d3.sum(node.sourceLinks, weightedTarget) / d3.sum(node.sourceLinks, value); | |
node.y += (y - center(node)) * alpha; | |
} | |
}); | |
}); | |
function weightedTarget(link) { | |
return center(link.target) * link.value; | |
} | |
} | |
function resolveCollisions() { | |
nodesByBreadth.forEach(function(nodes) { | |
var node, | |
dy, | |
y0 = 0, | |
n = nodes.length, | |
i; | |
// Push any overlapping nodes down. | |
nodes.sort(ascendingDepth); | |
for (i = 0; i < n; ++i) { | |
node = nodes[i]; | |
dy = y0 - node.y; | |
if (dy > 0) node.y += dy; | |
y0 = node.y + node.dy + nodePadding; | |
} | |
// If the bottommost node goes outside the bounds, push it back up. | |
dy = y0 - nodePadding - size[1]; | |
if (dy > 0) { | |
y0 = node.y -= dy; | |
// Push any overlapping nodes back up. | |
for (i = n - 2; i >= 0; --i) { | |
node = nodes[i]; | |
dy = node.y + node.dy + nodePadding - y0; | |
if (dy > 0) node.y -= dy; | |
y0 = node.y; | |
} | |
} | |
}); | |
} | |
function ascendingDepth(a, b) { | |
return a.y - b.y; | |
} | |
} | |
function computeLinkDepths() { | |
nodes.forEach(function(node) { | |
node.sourceLinks.sort(ascendingTargetDepth); | |
node.targetLinks.sort(ascendingSourceDepth); | |
}); | |
nodes.forEach(function(node) { | |
var sy = 0, | |
ty = 0; | |
node.sourceLinks.forEach(function(link) { | |
link.sy = sy; | |
sy += link.dy; | |
}); | |
node.targetLinks.forEach(function(link) { | |
link.ty = ty; | |
ty += link.dy; | |
}); | |
}); | |
function ascendingSourceDepth(a, b) { | |
return a.source.y - b.source.y; | |
} | |
function ascendingTargetDepth(a, b) { | |
return a.target.y - b.target.y; | |
} | |
} | |
function center(node) { | |
return node.y + node.dy / 2; | |
} | |
function value(link) { | |
return link.value; | |
} | |
return sankey; | |
}; |
<!DOCTYPE html> | |
<html> | |
<meta charset="utf-8"/> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script> | |
<link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css" rel="stylesheet" id="bootstrap-css"> | |
<script src="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/js/bootstrap.min.js"></script> | |
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" id="bootstrap-css"> | |
<script src="http://d3js.org/d3.v3.js"></script> | |
<script type="text/javascript" src="https://cdn.rawgit.com/Caged/d3-tip/896d387c653b4d73cea9cdd0740aa8794754417a/index.js"></script> | |
<!-- Get inicial sample from http://jsfiddle.net/QwMPS/ --> | |
<script src="d3sankey.js"></script> | |
<style type="text/css"> | |
svg text { | |
font-size: 14px; | |
stroke-width: 0; | |
fill: black; | |
} | |
html, body { | |
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Open Sans","Helvetica Neue",sans-serif; | |
justify-content: center; | |
align-items: center; | |
font-size: 12px; | |
overflow: hidden; | |
padding: 0; | |
margin: 0; | |
width: 100%; | |
height: 100%; | |
} | |
.sankeybox { | |
display: flex; | |
flex-direction: column; | |
height: 100%; | |
position: relative; | |
} | |
.sankey .node text { | |
pointer-events: none; | |
} | |
.sankey .link { | |
fill: none; | |
stroke: #000; | |
stroke-opacity: .16; | |
transition-property: stroke-opacity; | |
transition-duration: 0.5s; | |
} | |
.sankey .link:hover { | |
stroke-opacity: .5; | |
} | |
h1, h2, h3 { | |
line-height: 12px !important; | |
} | |
.d3-tip h1 { | |
font-size: 14px; | |
padding: 0; | |
margin-bottom: 5px; | |
width: 100%; | |
} | |
.d3-tip h2 { | |
font-weight: bold; | |
font-size: 12px; | |
padding-right: inherit; | |
padding-left: inherit; | |
padding-top: 2px; | |
padding-bottom: 2px; | |
margin: 0px; | |
} | |
.d3-tip h3 { | |
font-weight: normal; | |
font-size: 8px; | |
margin: 0; | |
padding: 0; | |
} | |
.d3-tip table { | |
font-weight: normal; | |
font-size: 12px; | |
padding: none; | |
margin: 0; | |
width: 100%; | |
border: none; | |
border-collapse: collapse; | |
} | |
.d3-tip td { | |
padding-top: 2px; | |
padding-bottom: 2px; | |
} | |
.d3-tip .col-left { | |
padding-right: 8px; | |
} | |
.d3-tip .table-wrapper { | |
margin: 0; | |
padding: inherit; | |
border: none; | |
} | |
.d3-tip { | |
line-height: 1; | |
font-weight: normal; | |
padding: 4px; | |
background: white; | |
color: black; | |
border-radius: 2px; | |
pointer-events: none; | |
background: white; | |
box-shadow: 1px 1px 4px grey; | |
} | |
</style> | |
<style> | |
.node rect { | |
cursor: move; | |
fill-opacity: .9; | |
shape-rendering: crispEdges; | |
} | |
.node text { | |
pointer-events: none; | |
text-shadow: 0px 0px 13px #fff; | |
} | |
.node:hover { | |
stroke-opacity: .2; | |
} | |
.link { | |
fill: none; | |
stroke: #000; | |
stroke-opacity: .16; | |
transition-property: stroke-opacity; | |
transition-duration: 0.5s; | |
} | |
.link:hover { | |
stroke-opacity: .5; | |
} | |
</style> | |
<style> | |
.d3-zoom-controls { | |
position: absolute; | |
left: 15px; | |
top: 10px; | |
} | |
.d3-zoom-controls .btn-circle { | |
width: 30px; | |
height: 30px; | |
align-items: center; | |
padding: 0px 0; | |
font-size: 18px; | |
line-height: 2.00; | |
display: grid; | |
border-radius: 30px; | |
background: rgba(255, 255, 255, 0.7); | |
color: gray; | |
margin-top: 10px; | |
} | |
</style> | |
<body> | |
<div id="sankey" class="sankeybox"> | |
<div class="d3-zoom-controls"> | |
<a data-zoom="+0.5" class="btn btn-circle"><span class="fa fa-plus"></span></a> | |
<a data-zoom="-0.5" class="btn btn-circle"><span class="fa fa-minus"></span></a> | |
<a data-zoom="0" class="btn btn-circle"><span class="fa fa-crosshairs"></span></a> | |
</div> | |
</div> | |
<script> | |
var zoom = {}; | |
var drag = {}; | |
var svg = {}; | |
//Zoom function: | |
//Programmatic Pan+Zoom III (Mike Bostock’s Block 7ec977c95910dd026812) | |
//https://bl.ocks.org/mbostock/7ec977c95910dd026812 | |
d3.selectAll("a[data-zoom]").on("click", clicked); | |
function clicked() { | |
var valueZoom = this.getAttribute("data-zoom"); | |
if (valueZoom != 0) | |
{ | |
svg.call(zoom); | |
// Record the coordinates (in data space) of the center (in screen space). | |
var center0 = zoom.center(), translate0 = zoom.translate(), coordinates0 = coordinates(center0); | |
zoom.scale(zoom.scale() * Math.pow(2, +valueZoom)); | |
// Translate back to the center. | |
var center1 = point(coordinates0); | |
zoom.translate([translate0[0] + center0[0] - center1[0], translate0[1] + center0[1] - center1[1]]); | |
svg.transition().duration(750).call(zoom.event); | |
} else { | |
fitZoom(); | |
} | |
} | |
function fitZoom() | |
{ | |
svg.transition().duration(500).call(zoom.translate([20,10]).scale(1).event); | |
} | |
function coordinates(point) { | |
var scale = zoom.scale(), translate = zoom.translate(); | |
return [(point[0] - translate[0]) / scale, (point[1] - translate[1]) / scale]; | |
} | |
function point(coordinates) { | |
var scale = zoom.scale(), translate = zoom.translate(); | |
return [coordinates[0] * scale + translate[0], coordinates[1] * scale + translate[1]]; | |
} | |
function showSankey(energy) | |
{ | |
$('.d3-tip-nodes').remove(); //clear olds tips | |
var chartBox = d3.select("#sankey").node().getBoundingClientRect(); | |
var margin = {top: 10, right: 10, bottom: 10, left: 20}, | |
width = chartBox.width - chartBox.left - margin.left - margin.right, | |
height = chartBox.height - chartBox.top - margin.top - margin.bottom; | |
var linkTooltipOffset = 72, | |
nodeTooltipOffset = 130; | |
//Tooltip function: | |
//D3 sankey diagram with view options (Austin Czarnecki’s Block cc6371af0b726e61b9ab) | |
//https://bl.ocks.org/austinczarnecki/cc6371af0b726e61b9ab | |
var tipLinks = d3.tip() | |
.attr('class', 'd3-tip') | |
.offset([-10,0]); | |
var tipNodes = d3.tip() | |
.attr('class', 'd3-tip d3-tip-nodes') | |
.offset([-10, 0]); | |
function formatAmount(val) { | |
//return val.toLocaleString("en-US", {style: 'currency', currency: "USD"}).replace(/\.[0-9]+/, ""); | |
return parseFloat(val).toFixed(1) + " $"; | |
}; | |
// "➡" | |
tipLinks.html(function(d) { | |
var title, candidate; | |
if (energy.links.indexOf(d.source.name) > -1) { | |
candidate = d.source.name; | |
title = d.target.name; | |
} else { | |
candidate = d.target.name; | |
title = d.source.name; | |
} | |
var html = '<div class="table-wrapper">'+ | |
'<h1>'+title+'</h1>'+ | |
'<table>'+ | |
'<tr>'+ | |
'<td class="col-left">'+candidate+'</td>'+ | |
'<td align="right">'+formatAmount(d.value)+'</td>'+ | |
'</tr>'+ | |
'</table>'+ | |
'</div>'; | |
return html; | |
}); | |
tipNodes.html(function(d) { | |
var object = d3.entries(d), | |
nodeName = object[1].value, | |
linksTo = object[3].value, | |
linksFrom = object[4].value, | |
html; | |
html = '<div class="table-wrapper">'+ | |
'<h1>'+nodeName+'</h1>'+ | |
'<table>'; | |
if (linksFrom.length > 0 & linksTo.length > 0) { | |
html+= '<tr><td><h2>Input:</h2></td><td></td></tr>' | |
} | |
for (i = 0; i < linksFrom.length; ++i) { | |
html += '<tr>'+ | |
'<td class="col-left">'+linksFrom[i].source.name+'</td>'+ | |
'<td align="right">'+formatAmount(linksFrom[i].value)+'</td>'+ | |
'</tr>'; | |
} | |
if (linksFrom.length > 0 & linksTo.length > 0) { | |
html+= '<tr><td><h2>Output:</h2></td><td></td></tr>' | |
} | |
for (i = 0; i < linksTo.length; ++i) { | |
html += '<tr>'+ | |
'<td class="col-left">'+linksTo[i].target.name+'</td>'+ | |
'<td align="right">'+formatAmount(linksTo[i].value)+'</td>'+ | |
'</tr>'; | |
} | |
html += '</table></div>'; | |
return html; | |
}); | |
function zoomed() { | |
svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); | |
} | |
function dragstarted(d) { | |
d3.select(this).classed("dragging", true); | |
} | |
function dragged(d) { | |
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y); | |
} | |
function dragended(d) { | |
d3.select(this).classed("dragging", false); | |
} | |
drag = d3.behavior.drag() | |
.origin(function(d) { return d; }) | |
.on("dragstart", dragstarted) | |
.on("drag", dragged) | |
.on("dragend", dragended); | |
zoom = d3.behavior.zoom() | |
.scaleExtent([0, 5]) | |
.center([width / 2, height / 2]) | |
.on("zoom", zoomed); | |
var formatNumber = d3.format(",.0f"), | |
format = function(d) { return formatNumber(d) + " $"; }, | |
color = d3.scale.category20(); | |
svg = d3.select("#sankey").append("svg") | |
.attr("width", width + chartBox.left + margin.left + margin.right) | |
.attr("height", height + chartBox.top + margin.top + margin.bottom) | |
.call(zoom) | |
.call(tipLinks) | |
.call(tipNodes) | |
.append("g") | |
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
var sankey = d3sankey() | |
.nodeWidth(40) | |
.nodePadding(15) | |
.size([width, height]); | |
var path = sankey.link(); | |
sankey | |
.nodes(energy.nodes) | |
.links(energy.links) | |
.layout(32); | |
var fontScale = d3.scale.linear().domain(d3.extent(energy.nodes, function(d) { return d.value })).range([18, 30]); | |
var link = svg.append("g").selectAll(".link") | |
.data(energy.links) | |
.enter().append("path") | |
.attr("class", "link") | |
.attr("d", path) | |
.style("stroke", function(d){ return d.source.color; }) | |
.style("stroke-width", function(d) { return Math.max(1, d.dy); }) | |
.sort(function(a, b) { return b.dy - a.dy; }) | |
.on('mousemove', function(event) { | |
tipLinks | |
.style("top", (d3.event.pageY - linkTooltipOffset) + "px") | |
.style("left", function () { | |
var left = (Math.max(d3.event.pageX - linkTooltipOffset, 10)); | |
left = Math.min(left, window.innerWidth - $('.d3-tip').width() - 20) | |
return left + "px"; }) | |
}) | |
.on('mouseover', tipLinks.show) | |
.on('mouseout', tipLinks.hide); | |
var node = svg.append("g").selectAll(".node") | |
.data(energy.nodes) | |
.enter().append("g") | |
.attr("class", "node") | |
.attr("transform", function(d) { | |
return "translate(" + d.x + "," + d.y + ")"; | |
}) | |
.on('mousemove', function(event) { | |
tipNodes | |
.style("top", (d3.event.pageY - $('.d3-tip-nodes').height() - 20) + "px") | |
.style("left", function () { | |
var left = (Math.max(d3.event.pageX - nodeTooltipOffset, 10)); | |
left = Math.min(left, window.innerWidth - $('.d3-tip').width() - 20) | |
return left + "px"; }) | |
}) | |
.on('mouseover', tipNodes.show) | |
.on('mouseout', tipNodes.hide) | |
.call(d3.behavior.drag() | |
.origin(function(d) { return d; }) | |
.on("dragstart", function() { | |
d3.event.sourceEvent.stopPropagation(); //Disable drag sankey on node select | |
this.parentNode.appendChild(this); }) | |
.on("drag", dragmove)); | |
node.append("rect") | |
.attr("height", function(d) { return d.dy; }) | |
.attr("width", sankey.nodeWidth()) | |
.style("fill", function(d) { | |
if (d.color == undefined) | |
return d.color = color(d.name.replace(/ .*/, "")); //get new color if node.color is null | |
return d.color; | |
}) | |
.style("stroke", function(d) { return d3.rgb(d.color).darker(2); }); | |
node.append("text") | |
.attr("class","nodeValue") | |
.text(function(d) { return d.name + "\n" + format(d.value); }); | |
node.selectAll("text.nodeValue") | |
.attr("x", sankey.nodeWidth() / 2) | |
.attr("y", function (d) { return (d.dy / 2) }) | |
.text(function (d) { return formatNumber(d.value); }) | |
.attr("dy", 5) | |
.attr("text-anchor", "middle"); | |
node.append("text") | |
.attr("class","nodeLabel") | |
.style("fill", function(d) { | |
return d3.rgb(d.color).darker(2.4); | |
}) | |
.style("font-size", function(d) { | |
return fontScale(d.value) + "px"; | |
}); | |
node.selectAll("text.nodeLabel") | |
.attr("x", -6) | |
.attr("y", function(d) { return d.dy / 2; }) | |
.attr("dy", ".35em") | |
.attr("text-anchor", "end") | |
.attr("transform", null) | |
.text(function(d) { return d.name; }) | |
.filter(function(d) { return d.x < width / 2; }) | |
.attr("x", 6 + sankey.nodeWidth()) | |
.attr("text-anchor", "start"); | |
function dragmove(d) { | |
d3.select(this) | |
.attr("transform", "translate(" + d.x + "," + | |
(d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))) + ")"); | |
sankey.relayout(); | |
link.attr("d", path); | |
}; | |
fitZoom(); | |
} | |
function getData() { | |
return { | |
"nodes": [{ | |
"node": 0, | |
"name": "node0", | |
color: "#1f77b4" | |
}, { | |
"node": 1, | |
"name": "node1", | |
color:"#aec7e8" | |
}, { | |
"node": 2, | |
"name": "node2", | |
color: "#ff7f0e" | |
}, { | |
"node": 3, | |
"name": "node3", | |
color: "#ffbb78" | |
}, { | |
"node": 4, | |
"name": "node4", | |
color: "#2ca02c" | |
}, { | |
"node": 5, | |
"name": "node5", | |
color: "#98df8a" | |
}, { | |
"node": 6, | |
"name": "node6", | |
color: "#d62728" | |
}, { | |
"node": 7, | |
"name": "node7", | |
color: "#ff9896" | |
}], | |
"links": [{ | |
"source": 0, | |
"target": 2, | |
"value": 25 | |
}, { | |
"source": 1, | |
"target": 2, | |
"value": 5 | |
}, { | |
"source": 1, | |
"target": 3, | |
"value": 20 | |
}, { | |
"source": 2, | |
"target": 4, | |
"value": 29 | |
}, { | |
"source": 2, | |
"target": 5, | |
"value": 1 | |
}, { | |
"source": 3, | |
"target": 4, | |
"value": 10 | |
}, { | |
"source": 3, | |
"target": 5, | |
"value": 2 | |
}, { | |
"source": 3, | |
"target": 6, | |
"value": 8 | |
}, { | |
"source": 4, | |
"target": 7, | |
"value": 39 | |
}, { | |
"source": 5, | |
"target": 7, | |
"value": 3 | |
}, { | |
"source": 6, | |
"target": 7, | |
"value": 8 | |
}]}; | |
}; | |
showSankey(getData()); | |
</script> | |
</body> | |
</html> |
Cool Stuff! Could you please change the license to Apache 2.0 so that your work is widely used.
A demonstration of d3-sankey using Zoom control, drag nodes (vertically), tooltip boxs, colors links (by color's nodes) and nodes values.
Thanks Mike Bostock!