an es2015 iteration on the block Sankey Particles III - d3v4 from @BenDilday
Forked from Sankey Particles | d3v4 & es2015+ from @micahstubbs
Forked from Sankey Particles III from @elijah_meeks
Updated to use d3 v4
border: no | |
license: Apache-2.0 |
an es2015 iteration on the block Sankey Particles III - d3v4 from @BenDilday
Forked from Sankey Particles | d3v4 & es2015+ from @micahstubbs
Forked from Sankey Particles III from @elijah_meeks
Updated to use d3 v4
{"nodes":[ | |
{"name":"Agricultural 'waste'"}, | |
{"name":"Bio-conversion"}, | |
{"name":"Liquid"}, | |
{"name":"Losses"}, | |
{"name":"Solid"}, | |
{"name":"Gas"}, | |
{"name":"Biofuel imports"}, | |
{"name":"Biomass imports"}, | |
{"name":"Coal imports"}, | |
{"name":"Coal"}, | |
{"name":"Coal reserves"}, | |
{"name":"District heating"}, | |
{"name":"Industry"}, | |
{"name":"Heating and cooling - commercial"}, | |
{"name":"Heating and cooling - homes"}, | |
{"name":"Electricity grid"}, | |
{"name":"Over generation / exports"}, | |
{"name":"H2 conversion"}, | |
{"name":"Road transport"}, | |
{"name":"Agriculture"}, | |
{"name":"Rail transport"}, | |
{"name":"Lighting & appliances - commercial"}, | |
{"name":"Lighting & appliances - homes"}, | |
{"name":"Gas imports"}, | |
{"name":"Ngas"}, | |
{"name":"Gas reserves"}, | |
{"name":"Thermal generation"}, | |
{"name":"Geothermal"}, | |
{"name":"H2"}, | |
{"name":"Hydro"}, | |
{"name":"International shipping"}, | |
{"name":"Domestic aviation"}, | |
{"name":"International aviation"}, | |
{"name":"National navigation"}, | |
{"name":"Marine algae"}, | |
{"name":"Nuclear"}, | |
{"name":"Oil imports"}, | |
{"name":"Oil"}, | |
{"name":"Oil reserves"}, | |
{"name":"Other waste"}, | |
{"name":"Pumped heat"}, | |
{"name":"Solar PV"}, | |
{"name":"Solar Thermal"}, | |
{"name":"Solar"}, | |
{"name":"Tidal"}, | |
{"name":"UK land based bioenergy"}, | |
{"name":"Wave"}, | |
{"name":"Wind"} | |
], | |
"links":[ | |
{"source":0,"target":1,"value":124.729}, | |
{"source":1,"target":2,"value":0.597}, | |
{"source":1,"target":3,"value":26.862}, | |
{"source":1,"target":4,"value":280.322}, | |
{"source":1,"target":5,"value":81.144}, | |
{"source":6,"target":2,"value":35}, | |
{"source":7,"target":4,"value":35}, | |
{"source":8,"target":9,"value":11.606}, | |
{"source":10,"target":9,"value":63.965}, | |
{"source":9,"target":4,"value":75.571}, | |
{"source":11,"target":12,"value":10.639}, | |
{"source":11,"target":13,"value":22.505}, | |
{"source":11,"target":14,"value":46.184}, | |
{"source":15,"target":16,"value":104.453}, | |
{"source":15,"target":14,"value":113.726}, | |
{"source":15,"target":17,"value":27.14}, | |
{"source":15,"target":12,"value":342.165}, | |
{"source":15,"target":18,"value":37.797}, | |
{"source":15,"target":19,"value":4.412}, | |
{"source":15,"target":13,"value":40.858}, | |
{"source":15,"target":3,"value":56.691}, | |
{"source":15,"target":20,"value":7.863}, | |
{"source":15,"target":21,"value":90.008}, | |
{"source":15,"target":22,"value":93.494}, | |
{"source":23,"target":24,"value":40.719}, | |
{"source":25,"target":24,"value":82.233}, | |
{"source":5,"target":13,"value":0.129}, | |
{"source":5,"target":3,"value":1.401}, | |
{"source":5,"target":26,"value":151.891}, | |
{"source":5,"target":19,"value":2.096}, | |
{"source":5,"target":12,"value":48.58}, | |
{"source":27,"target":15,"value":7.013}, | |
{"source":17,"target":28,"value":20.897}, | |
{"source":17,"target":3,"value":6.242}, | |
{"source":28,"target":18,"value":20.897}, | |
{"source":29,"target":15,"value":6.995}, | |
{"source":2,"target":12,"value":121.066}, | |
{"source":2,"target":30,"value":128.69}, | |
{"source":2,"target":18,"value":135.835}, | |
{"source":2,"target":31,"value":14.458}, | |
{"source":2,"target":32,"value":206.267}, | |
{"source":2,"target":19,"value":3.64}, | |
{"source":2,"target":33,"value":33.218}, | |
{"source":2,"target":20,"value":4.413}, | |
{"source":34,"target":1,"value":4.375}, | |
{"source":24,"target":5,"value":122.952}, | |
{"source":35,"target":26,"value":839.978}, | |
{"source":36,"target":37,"value":504.287}, | |
{"source":38,"target":37,"value":107.703}, | |
{"source":37,"target":2,"value":611.99}, | |
{"source":39,"target":4,"value":56.587}, | |
{"source":39,"target":1,"value":77.81}, | |
{"source":40,"target":14,"value":193.026}, | |
{"source":40,"target":13,"value":70.672}, | |
{"source":41,"target":15,"value":59.901}, | |
{"source":42,"target":14,"value":19.263}, | |
{"source":43,"target":42,"value":19.263}, | |
{"source":43,"target":41,"value":59.901}, | |
{"source":4,"target":19,"value":0.882}, | |
{"source":4,"target":26,"value":400.12}, | |
{"source":4,"target":12,"value":46.477}, | |
{"source":26,"target":15,"value":525.531}, | |
{"source":26,"target":3,"value":787.129}, | |
{"source":26,"target":11,"value":79.329}, | |
{"source":44,"target":15,"value":9.452}, | |
{"source":45,"target":1,"value":182.01}, | |
{"source":46,"target":15,"value":19.013}, | |
{"source":47,"target":15,"value":289.366} | |
]} |
<!DOCTYPE html> | |
<html lang='en'> | |
<head> | |
<meta charset='utf-8' /> | |
<title>Sankey Particles</title> | |
<style> | |
.node rect { | |
cursor: move; | |
fill-opacity: .9; | |
shape-rendering: crispEdges; | |
} | |
.node text { | |
pointer-events: none; | |
text-shadow: 0 1px 0 #fff; | |
} | |
.link { | |
fill: none; | |
stroke: #000; | |
stroke-opacity: .15; | |
} | |
.link:hover { | |
stroke-opacity: .25; | |
} | |
svg { | |
position: absolute; | |
} | |
canvas { | |
position: absolute; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="my-canvas"></div> | |
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/4.8.0/d3.js' charset='utf-8' type='text/javascript'></script> | |
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3-sankey/0.4.2/d3-sankey.js' charset='utf-8' type='text/javascript'></script> | |
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3-timer/1.0.5/d3-timer.js' charset='utf-8' type='text/javascript'></script> | |
<script src='vis.js'></script> | |
</body> | |
</html> |
/* global d3 */ | |
const margin = { top: 1, right: 1, bottom: 6, left: 1 }, | |
width = 1440 - margin.left - margin.right, | |
height = 800 - margin.top - margin.bottom, | |
formatNumber = d3.format(',.0f'), | |
format = d => `${formatNumber(d)} TWh`, | |
color = d3.scaleOrdinal(d3.schemeCategory20); | |
const canvas = d3.select('#my-canvas').append('canvas') | |
.attr('width', width + margin.left + margin.right) | |
.attr('height', height + margin.top + margin.bottom) | |
.append('g') | |
.attr('transform', `translate(${margin.left},${margin.top})`); | |
const svg = d3.select('#my-canvas').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})`); | |
const sankey = d3.sankey() | |
.nodeWidth(15) | |
.nodePadding(10) | |
.size([width, height]); | |
const path = sankey.link(); | |
const url = | |
'https://gist.githubusercontent.com/jasonhodges/' | |
+ '5e661917fcbe74df4276c9d291f6258c/raw/ca91cebb02800a83d4a712b2f36508008350547f/energy.json'; | |
d3.json(url, (energy) => { | |
sankey | |
.nodes(energy.nodes) | |
.links(energy.links) | |
.layout(32); | |
const link = svg.append('g').selectAll('.link') | |
.data(energy.links) | |
.enter().append('path') | |
.attr('class', 'link') | |
.attr('d', path) | |
.style('stroke-width', d => Math.max(1, d.dy)) | |
.sort((a, b) => b.dy - a.dy) | |
.on('click', d => createParticles(energy.links)); | |
link.append('title') | |
.text(d => `${d.source.name} → ${d.target.name}\n${format(d.value)}`); | |
const node = svg.append('g').selectAll('.node') | |
.data(energy.nodes) | |
.enter().append('g') | |
.attr('class', 'node') | |
.attr('transform', d => `translate(${d.x},${d.y})`) | |
.call(d3.drag() | |
.subject(d => d) | |
.on('start', function () { this.parentNode.appendChild(this); }) | |
.on('drag', dragmove)); | |
node.append('rect') | |
.attr('height', d => d.dy) | |
.attr('width', sankey.nodeWidth()) | |
.style('fill', (d) => { | |
d.color = color(d.name.replace(/ .*/, '')); | |
return d.color; | |
}) | |
.style('stroke', 'none') | |
.append('title') | |
.text(d => `${d.name}\n${format(d.value)}`); | |
function dragmove(d) { | |
d3.select(this).attr('transform', 'translate(' | |
+ (d.x = Math.max(0, Math.min(width - d.dx, d3.event.x))) | |
+ ',' + (d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))) + ')'); | |
sankey.relayout(); | |
link.attr('d', path); | |
} | |
function createParticles(l) { | |
const linkExtent = d3.extent(l, d => d.value); | |
const frequencyScale = d3.scaleLinear().domain(linkExtent).range([0.05, 1]); | |
l.forEach((link) => { | |
link.freq = frequencyScale(link.value); | |
link.particleSize = 2; | |
link.particleColor = d3.scaleLinear().domain([0, 1]) | |
.range([link.source.color, link.target.color]); | |
}); | |
const t = d3.timer(tick, 1000); | |
let particles = []; | |
function tick(elapsed) { | |
particles = particles.filter(d => d.current < d.path.getTotalLength()); | |
d3.selectAll('path.link') | |
.each( | |
function (d) { | |
for (let x = 0; x < 2; x += 1) { | |
const offset = (Math.random() - 0.5) * (d.dy - 4); | |
if (Math.random() < d.freq) { | |
const length = this.getTotalLength(); | |
particles.push({ | |
link: d, time: elapsed, offset, path: this, length, animateTime: length, speed: 0.5 + (Math.random()) | |
}); | |
} | |
} | |
}); | |
particleEdgeCanvasPath(elapsed); | |
} | |
function particleEdgeCanvasPath(elapsed) { | |
const context = d3.select('canvas').node().getContext('2d'); | |
context.clearRect(0, 0, width + margin.left + margin.right, height + margin.top + margin.bottom); | |
context.fillStyle = 'gray'; | |
context.lineWidth = '1px'; | |
for (const x in particles) { | |
if ({}.hasOwnProperty.call(particles, x)) { | |
const currentTime = elapsed - particles[x].time; | |
particles[x].current = currentTime * 0.15 * particles[x].speed; | |
const currentPos = particles[x].path.getPointAtLength(particles[x].current); | |
context.beginPath(); | |
context.fillStyle = particles[x].link.particleColor(0); | |
context.arc(currentPos.x, currentPos.y + particles[x].offset, particles[x].link.particleSize, 0, 2 * Math.PI); | |
context.fill(); | |
} | |
} | |
} | |
} | |
}); |