In this example I try to showcase how the Gooey effect can be used with data visualizations. There are 3 different states that this visual loops through which lasts 15 seconds in total. It appears on my blog on More fun data visualizations with the gooey effect.
First we start with a center circle. From this circle 150 other circles start flying out with a gooey effect applied. These 150 circles represent the 150 largest cities in the world according to this page on Wikipedia. The circles are also sized to the population.
When the transition of the circles flying to their endpoint is finished, the gooey effect has been slowly turned down so that you cannot see its effects anymore. The opacity of the circles is turned down a bit for a nicer result.
The second step, which is another idea for the gooey effect, shows the circles coming together in a force layout where the circles are grouped according to their countries. Instead of showing the circles all separately, the gooey effect is slowly turned on again so that the circles mush together into 1 blob that represents the country. I do want to note here that the surface area is no longer exactly representative of the underlying circles, since the space between the circles will be filled with color as well.
In the third and final step, the circles move back into their center arrangement and the loop starts anew
You can find the other examples from the blog here
Older examples with the Gooey effect
Built with blockbuilder.org
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- Google fonts -->
<link href='https://fonts.googleapis.com/css?family=Open+Sans:300' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Poiret+One' rel='stylesheet' type='text/css'>
<style>
body {
font-family: 'Open Sans', sans-serif;
text-align: center;
font-weight: 300;
}
.title {
font-size: 30px;
color: #4F4F4F;
}
.subtitle {
font-size: 14px;
color: #AAAAAA;
padding-bottom: 15px;
}
.geo-path {
stroke: white;
stroke-width: 0.5px;
stroke-opacity: 1;
fill: #C0AA91;
fill-opacity: 1;
}
.cities, .cityCover{
fill: #1A818C;
}
.label {
font-size: 18px;
text-anchor: middle;
fill: #707070;
font-family: 'Poiret One', cursive;
font-weight: 400;
}
</style>
<!-- D3.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
</head>
<body>
<div class="title">The 150 largest Cities in the World</div>
<div class="subtitle">sized to population</div>
<div id="chart"></div>
<script src="countriesMap.js"></script>
<script src="populations.js"></script>
<script language="javascript" type="text/javascript">
///////////////////////////////////////////////////////////////////////////
//////////////////// Set up and initiate svg containers ///////////////////
///////////////////////////////////////////////////////////////////////////
var margin = {
top: 100,
right: 0,
bottom: 0,
left: 0
};
var width = 960 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
//SVG container
var svg = d3.select('#chart')
.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) + ")");
///////////////////////////////////////////////////////////////////////////
///////////////////////////// Create filter ///////////////////////////////
///////////////////////////////////////////////////////////////////////////
//SVG filter for the gooey effect
//Code taken from //tympanus.net/codrops/2015/03/10/creative-gooey-effects/
var defs = svg.append("defs");
var filter = defs.append("filter").attr("id","gooeyCodeFilter");
filter.append("feGaussianBlur")
.attr("in","SourceGraphic")
.attr("stdDeviation","10")
//to fix safari: //stackoverflow.com/questions/24295043/svg-gaussian-blur-in-safari-unexpectedly-lightens-image
.attr("color-interpolation-filters","sRGB")
.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 18 -5")
.attr("result","gooey");
filter.append("feBlend")
.attr("in","SourceGraphic")
.attr("in2","gooey")
.attr("operator","atop");
///////////////////////////////////////////////////////////////////////////
//////////////////////////// Set-up Map /////////////////////////////////
///////////////////////////////////////////////////////////////////////////
//Variables for the map
var projection = d3.geo.mercator()
.scale(170)
.translate([480,230]);
var path = d3.geo.path()
.projection(projection);
var map = svg.append("g")
.attr("class", "map");
//Initiate the world map
map.selectAll(".geo-path")
.data(countriesMap[0].features)
.enter().append("path")
.attr("class", function(d) { return "geo-path" + " " + d.id; })
.attr("id", function(d) { return d.properties.name; })
.attr("d", path)
.style("stroke-opacity", 1)
.style("fill-opacity", 0.5);
///////////////////////////////////////////////////////////////////////////
//////////////////////////////// Cities ///////////////////////////////////
///////////////////////////////////////////////////////////////////////////
//Radius scale
var rScale = d3.scale.sqrt()
.range([0,14])
.domain([0, d3.max(populations, function(d) { return d.population; })]);
//Put the city locations into the data itself
populations.forEach(function(d,i) {
d.radius = rScale(d.population);
d.x = projection([d.longitude, d.latitude])[0];
d.y = projection([d.longitude, d.latitude])[1];
});
//Wrapper for the cities
var cityWrapper = svg.append("g")
.attr("class", "cityWrapper")
.style("filter", "url(#gooeyCodeFilter)");
//Place the city circles
var cities = cityWrapper.selectAll(".cities")
.data(populations)
.enter().append("circle")
.attr("class", "cities")
.attr("r", function(d) { return d.radius ;})
.attr("cx", projection([0,0])[0])
.attr("cy", projection([0,0])[1])
.style("opacity", 1);
var coverCirleRadius = 40;
//Circle over all others
cityWrapper.append("circle")
.attr("class", "cityCover")
.attr("r", coverCirleRadius)
.attr("cx", projection([0,0])[0])
.attr("cy", projection([0,0])[1]);
///////////////////////////////////////////////////////////////////////////
/////////////////////////// Country Labels ////////////////////////////////
///////////////////////////////////////////////////////////////////////////
//Calculate the centers for each country
var centers = getCenters("country", [width, height/0.8]);
centers.forEach(function(d) {
d.y = d.y - 100;
d.x = d.x + 0;
});//centers forEach
//Wrapper for the country labels
var labelWrapper = svg.append("g")
.attr("class", "labelWrapper");
//Append the country labels
labelWrapper.selectAll(".label")
.data(centers)
.enter().append("text")
.attr("class", "label")
.style("opacity", 0)
.attr("transform", function (d) { return "translate(" + (d.x) + ", " + (d.y - 60) + ")"; })
.text(function (d) { return d.name });
///////////////////////////////////////////////////////////////////////////
/////////////////////////// Set-up the force //////////////////////////////
///////////////////////////////////////////////////////////////////////////
var force = d3.layout.force()
.gravity(.02)
.charge(0)
.on("tick", tick(centers, "country"));
var padding = 0;
var maxRadius = d3.max(populations, function(d) { return d.radius; });
///////////////////////////////////////////////////////////////////////////
///////////////////////////// Do the loop /////////////////////////////////
///////////////////////////////////////////////////////////////////////////
loop();
setInterval(loop, 15000);
function loop() {
placeCities();
setTimeout(clusterCountry, 7000);
setTimeout(backToCenter, 12000);
}//loop
///////////////////////////////////////////////////////////////////////////
/////////////////////////// Animation steps ///////////////////////////////
///////////////////////////////////////////////////////////////////////////
//Move the cities from the center to their actual locations
function placeCities () {
//Stop the force layout (in case you move backward)
force.stop();
//Make the cover circle shrink
d3.selectAll(".cityCover")
.transition().duration(5000)
.attr("r", 0);
//Put the cities in their geo location
d3.selectAll(".cities")
.transition("move").duration(2000)
.delay(function(d,i) { return i*20; })
.attr("r", function(d) {
return d.radius = rScale(d.population);
})
.attr("cx", function(d) {
return d.x = projection([d.longitude, d.latitude])[0];
})
.attr("cy", function(d) {
return d.y = projection([d.longitude, d.latitude])[1];
});
//Around the end of the transition above make the circles see-through a bit
d3.selectAll(".cities")
.transition("dim").duration(2000).delay(4000)
.style("opacity", 0.8);
//"Remove" gooey filter from cities during the transition
//So at the end they do not appear to melt together anymore
d3.selectAll(".blurValues")
.transition().duration(4000)
.attrTween("values", function() {
return d3.interpolateString("1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -5",
"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 6 -5");
});
}//placeCities
//Cluster all the cities based on the country
function clusterCountry() {
///Start force again
force.start();
//Dim the map
d3.selectAll(".geo-path")
.transition().duration(1000)
.style("fill-opacity", 0);
//Show the labels
d3.selectAll(".label")
.transition().duration(500)
.style("opacity", 1);
d3.selectAll(".cities")
.transition().duration(1000)
.style("opacity", 1);
//Reset gooey filter values back to a visible "gooey" effect
d3.selectAll(".blurValues")
.transition().duration(2000)
.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 -5",
"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 35 -6");
});
}//clusterCountry
//Move the circles back to the center location again
function backToCenter () {
//Stop the force layout
force.stop();
//Hide labels
d3.selectAll(".label")
.transition().duration(500)
.style("opacity", 0);
//Show map
d3.selectAll(".geo-path")
.transition().duration(1000)
.style("fill-opacity", 0.5);
//Make the cover cirlce to its true size again
d3.selectAll(".cityCover")
.transition().duration(3000).delay(500)
.attr("r", coverCirleRadius);
//Move the cities to the 0,0 coordinate
d3.selectAll(".cities")
.transition()
.duration(2000).delay(function(d,i) { return i*10; })
.attr("cx", projection([0, 0])[0])
.attr("cy", projection([0, 0])[1])
.style("opacity", 1);
d3.selectAll(".blurValues")
.transition().duration(1000).delay(1000)
.attrTween("values", function() {
return d3.interpolateString("1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 35 -6",
"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -5");
});
}//backToCenter
///////////////////////////////////////////////////////////////////////////
/////////////////////////// Helper functions //////////////////////////////
///////////////////////////////////////////////////////////////////////////
//Radial layout
function getCenters (vname, size) {
var centers = [],
mapping,
flags = [];
for( var i = 0; i < populations.length; i++) {
if( flags[populations[i][vname]]) continue;
flags[populations[i][vname]] = true;
centers.push({name: populations[i][vname], value: 1});
}//for i
centers.sort(function(a, b){ return d3.ascending(a.name, b.name); });
mapping = d3.layout.pack()
.sort(function(d) { return d[vname]; })
.size(size);
mapping.nodes({children: centers});
return centers;
}//getCenters
//Radial lay-out
function tick (centers, varname) {
var foci = {};
for (var i = 0; i < centers.length; i++) {
foci[centers[i].name] = centers[i];
}
return function (e) {
for (var i = 0; i < populations.length; i++) {
var o = populations[i];
var f = foci[o[varname]];
o.y += (f.y - o.y) * e.alpha;
o.x += (f.x - o.x) * e.alpha;
}//for
d3.selectAll(".cities")
.each(collide(.5))
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; });
}//function
}//tick
function collide(alpha) {
var quadtree = d3.geom.quadtree(populations);
return function (d) {
var r = d.radius + maxRadius + padding,
nx1 = d.x - r,
nx2 = d.x + r,
ny1 = d.y - r,
ny2 = d.y + r;
quadtree.visit(function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== d)) {
var x = d.x - quad.point.x,
y = d.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = d.radius + quad.point.radius + padding;
if (l < r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
};
}//collide
</script>
</body>
</html>