Experimenting with the gooey effect - based on the work of Nadieh Bremer.
This is an attempt at an icon grid to circle transition.
| license: mit | 
Experimenting with the gooey effect - based on the work of Nadieh Bremer.
This is an attempt at an icon grid to circle transition.
| <!DOCTYPE html> | |
| <head> | |
| <meta charset="utf-8"> | |
| <script src="https://d3js.org/d3.v4.min.js"></script> | |
| <style> | |
| body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } | |
| .big-circle { | |
| fill: none; | |
| } | |
| text { | |
| fill: white; | |
| font-family: sans-serif; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <script> | |
| var margin = {top: 50, right: 50, bottom: 50, left: 50} | |
| var width = 960 - margin.left - margin.right, | |
| height = 500 - margin.top - margin.bottom; | |
| var svg = d3.select("body").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 config = { | |
| radius: 5, | |
| gridLength: 10, | |
| gridPadding: 10 | |
| }; | |
| var defs = svg.append('defs'); | |
| var filter = defs.append('filter').attr('id','gooey'); | |
| filter.append('feGaussianBlur') | |
| .attr('in','SourceGraphic') | |
| .attr('stdDeviation', config.radius * 1.8) | |
| .attr('result','blur'); | |
| filter.append('feColorMatrix') | |
| .attr("class", "blurValues") | |
| .attr('in','blur') | |
| .attr('mode','matrix') | |
| .attr('values', '1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 ' + config.radius +' -6') | |
| .attr('result','gooey'); | |
| filter.append("feBlend") | |
| .attr("in", "SourceGraphic") | |
| .attr("in2", "gooey") | |
| .attr("operator", "atop"); | |
| var data = d3.range(60).map(function(i) { | |
| return { | |
| r: config.radius, | |
| colour: "purple" | |
| } | |
| }); | |
| var simulation = d3.forceSimulation(data) | |
| .force("x", d3.forceX(width / 2)) | |
| .force("y", d3.forceY(height / 2)) | |
| .force("collide", d3.forceCollide(config.radius + 1.5).iterations(2)) | |
| .stop(); | |
| var circleGroup = svg.append("g") | |
| .style("filter", "url(#gooey)"); | |
| var smallCircles = circleGroup.append("g").selectAll("circle") | |
| .data(data) | |
| .enter().append("circle") | |
| .attr("class", "small-circle") | |
| .attr("r", config.radius) | |
| .attr("cx", (d, i) => d.x = (i % config.gridLength) * (config.gridPadding + config.radius * 2)) | |
| .attr("cy", (d, i) => d.y = Math.floor(i / config.gridLength) * (config.gridPadding + config.radius * 2)) | |
| .attr("fill", d => d.colour); | |
| var bigCircle = circleGroup.append("g") | |
| .append("circle") | |
| .attr("class", "big-circle") | |
| .attr("cx", width / 2) | |
| .attr("cy", height / 2) | |
| .attr("r", 0) | |
| .style("fill", "purple"); | |
| function clusterDots() { | |
| // Interpolate between gooey filter and no gooey filter | |
| transitionGoo(3000); | |
| for (var i = 0; i < 120; ++i) simulation.tick(); | |
| d3.selectAll(".small-circle") | |
| .transition() | |
| .duration(1500) | |
| .delay((d,i) => calculateDistance(d, [width/2, height/2]) * 30) | |
| .attr("cx", d => d.x) | |
| .attr("cy", d => d.y) | |
| d3.select(".big-circle") | |
| .transition() | |
| .delay(700) | |
| .duration(2500) | |
| .attr("r", Math.sqrt(data.length) * 1.5 * config.radius); | |
| } | |
| function separateDots() { | |
| transitionGooBack(2000); | |
| d3.select(".big-circle") | |
| .transition() | |
| .duration(2100) | |
| .attr("r", 0); | |
| d3.selectAll(".small-circle") | |
| .transition() | |
| .duration(1500) | |
| .delay((d,i) => 1500 + (config.radius - calculateDistance(d, [width/2, height/2])) * 30) | |
| .attr("cx", (d, i) => (i % config.gridLength) * (config.gridPadding + config.radius * 2)) | |
| .attr("cy", (d, i) => Math.floor(i / config.gridLength) * (config.gridPadding + config.radius * 2)); | |
| } | |
| function loop() { | |
| setTimeout(clusterDots, 1000); | |
| setTimeout(separateDots, data.length * 150); | |
| } | |
| loop(); | |
| setInterval(loop, data.length * 200); | |
| function transitionGoo(duration) { | |
| d3.selectAll(".blurValues") | |
| .transition().duration(duration) | |
| .attrTween("values", function() { | |
| return d3.interpolateString("1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 6 -6", | |
| "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 " + config.radius * 8 + " -6"); | |
| }); | |
| } | |
| function transitionGooBack(duration) { | |
| d3.selectAll(".blurValues") | |
| .transition().duration(duration) | |
| .attrTween("values", function() { | |
| return d3.interpolateString("1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 " + config.radius * 8 + " -6", "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 6 -6"); | |
| }); | |
| } | |
| function calculateDistance(d, point) { | |
| return Math.sqrt(Math.pow(point[0] - d.x, 2) + Math.pow(point[1] - d.y, 2)) | |
| } | |
| </script> | |
| </body> |