Skip to content

Instantly share code, notes, and snippets.

@fabric-io-rodrigues
Last active July 19, 2018 11:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save fabric-io-rodrigues/a6410950d8e38e9afefc4ca33a8898fc to your computer and use it in GitHub Desktop.
Save fabric-io-rodrigues/a6410950d8e38e9afefc4ca33a8898fc to your computer and use it in GitHub Desktop.
Responsive D3 v4 Sankey Diagram
border: no
license: gpl-3.0

A demonstration, using D3v4 of Sankey responsible, colors in links (by color's nodes) and nodes font size proportional. github.com/FabricioRHS

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Responsive D3 v4 Sankey Diagram</title>
<script type="text/javascript" src="http://d3js.org/d3.v4.min.js"></script>
<script src="http://unpkg.com/d3-sankey@0.5.0/build/d3-sankey.js"></script>
<link href="style.css" rel="stylesheet" id="bootstrap-css">
<meta name="description" content="Responsive D3 v4 Sankey Diagram">
<meta name="keywords" content="Responsive,D3,D3v4,Sankey,Diagram">
<meta name="author" content="Fabricio Rodrigues">
<meta itemprop="name" content="Responsive D3 v4 Sankey Diagram">
<meta itemprop="description" content="Responsive D3 v4 Sankey Diagram">
<meta itemprop="image" content="thumbnail.png">
<meta name="og:title" content="Responsive D3 v4 Sankey Diagram">
<meta name="og:description" content="Responsive D3 v4 Sankey Diagram">
<meta name="og:image" content="thumbnail.png">
<meta name="og:site_name" content="Fabricio's bl.ocks">
<meta name="og:type" content="website">
</head>
<body>
<div id="container"></div>
<script src="sankey.fabriciorhs.js"></script>
</body>
</html>
const sankeyData = {
nodes: [
{ id: 0, name: 'Emergency Admits' },
{ id: 1, name: 'Scheduled Admits' },
{ id: 2, name: 'Telemetry' },
{ id: 3, name: 'Gen Med' },
{ id: 4, name: 'Med Psych' },
{ id: 5, name: 'Orth / ENT / Neuro' },
{ id: 6, name: 'MICU' },
{ id: 7, name: 'ICU' },
{ id: 8, name: 'Discharges Home' },
{ id: 9, name: 'Discharges Acute Care' },
{ id: 10, name: 'Discharges to Unspecified' },
{ id: 11, name: 'Discharges to Nursing Home' },
{ id: 12, name: 'Orth / ENT / Neuro' }
],
links: [
{source: 0, target: 6, value: 25},
{source: 0, target: 7, value: 15},
{source: 1, target: 6, value: 15},
{source: 1, target: 7, value: 13},
{source: 2, target: 6, value: 8},
{source: 2, target: 7, value: 3},
{source: 3, target: 6, value: 6},
{source: 4, target: 6, value: 2},
{source: 4, target: 7, value: 8},
{source: 5, target: 7, value: 11},
{source: 6, target: 8, value: 20},
{source: 6, target: 9, value: 2},
{source: 6, target: 10, value: 10},
{source: 6, target: 11, value: 13},
{source: 6, target: 12, value: 11},
{source: 7, target: 8, value: 20},
{source: 7, target: 9, value: 10},
{source: 7, target: 10, value: 10},
{source: 7, target: 11, value: 5},
{source: 7, target: 12, value: 5}]
};
function getDimensions(container, margin) {
const bbox = container.node().getBoundingClientRect();
return {
width: bbox.width - margin.left - margin.right,
height: bbox.height - margin.top - margin.bottom
};
}
function updateLinks(linkContainer, linkData) {
// Join new data with old elements
const links = linkContainer.selectAll('.link')
.data(linkData, d => `${d.source}${d.target}`);
// Remove elements not present in new data
links.exit().remove();
// Update old elements
links.attr('d', d3.sankeyLinkHorizontal());
// Enter new elements
const enteringLinks = links.enter()
.append('path')
.attr('class', 'link')
.attr('d', d3.sankeyLinkHorizontal())
.style("stroke", function(d){ return d.source.color; })
.style('stroke-width', d => Math.max(1, d.dy))
.sort((a, b) => b.dy - a.dy);
// Add the link titles
enteringLinks.append('title')
.text(d => `${d.source.name} → ${d.target.name}\n${format(d.value)}`);
}
function updateNodes(nodeContainer, nodeData) {
// Join new data with old elements
const nodes = nodeContainer.selectAll('.node')
.data(nodeData, d => d.id);
// Remove elements not present in new data
nodes.exit().remove();
// Update old elements
nodes.attr('transform', d => `translate(${d.x}, ${d.y})`);
// Enter new elements
const enteringNodes = nodes.enter().append('g')
.attr('class', 'node')
.attr('transform', d => `translate(${d.x}, ${d.y})`);
// Add the rectangles for the nodes
enteringNodes.append('rect')
.attr('height', d => d.dy)
.attr('width', sankey.nodeWidth())
.style('fill', d => {
return (d.color = color(d.name.replace(/ .*/, '')));
})
.style('stroke', d => d3.rgb(d.color).darker(2))
.append('title')
.text(d => `${d.name}\n${format(d.value)}`);
// Add node names
enteringNodes
.append('text')
.attr('x', -6)
.attr('y', d => d.dy / 2)
.attr('dy', '.35em')
.attr('text-anchor', 'end')
.text(d => d.name)
.style("fill", function(d) {
return d3.rgb(d.color).darker(2.4);
})
.style("font-size", function(d) {
return Math.floor(fontScale(d.value)) + "px";
})
.filter(d => d.x < width / 2)
.attr('x', 6 + sankey.nodeWidth())
.attr('text-anchor', 'start');
}
const container = d3.select('#container');
const margin = { top: 10, right: 10, bottom: 10, left: 10 };
const {width, height} = getDimensions(container, margin);
const formatNumber = d3.format(',.0f');
const format = d => formatNumber(d);
const color = d3.scaleOrdinal(d3.schemeCategory20);
// append the svg object to the body of the page
const svg = container
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom);
const sankeyContainer = svg.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
const {nodes, links} = sankeyData;
// Set the sankey diagram properties
const sankey = d3.sankey().nodeWidth(36).nodePadding(40).size([width, height]);
const graph = sankey.nodes(nodes).links(links)();
const fontScale = d3.scaleLinear() .range([18, 30]);
fontScale.domain(d3.extent(graph.nodes, function(d) { return d.value }));
const linkContainer = sankeyContainer.append('g');
const nodeContainer = sankeyContainer.append('g');
updateNodes(nodeContainer, graph.nodes);
updateLinks(linkContainer, graph.links);
function resize() {
const updatedDimensions = getDimensions(container, margin);
// Resize SVG
svg
.attr('width', updatedDimensions.width + margin.left + margin.right)
.attr('height', updatedDimensions.height + margin.top + margin.bottom);
const {nodes, links} = sankeyData;
const newSankey = d3.sankey()
.nodeWidth(36)
.nodePadding(40)
.size([updatedDimensions.width, updatedDimensions.height]);
const update = newSankey
.nodes(nodes)
.links(links)();
updateLinks(linkContainer, update.links);
updateNodes(nodeContainer, update.nodes);
}
d3.select(window).on('resize', resize);
body {
font-family: sans-serif;
}
#container {
height: 300px;
width: 100%;
}
.node rect {
fill-opacity: .9;
shape-rendering: crispEdges;
}
.node text {
pointer-events: none;
text-shadow: 0 1px 0 #fff;
}
.link {
fill: none;
stroke: #000;
stroke-opacity: .16;
transition-property: stroke-opacity;
transition-duration: 0.5s;
}
.link:hover {
stroke-opacity: .5;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment