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(); | |
| } | |
| } | |
| } | |
| } | |
| }); |