Skip to content

Instantly share code, notes, and snippets.

@Kcnarf
Last active July 28, 2017 14:51
Show Gist options
  • Save Kcnarf/3f1740338371011b2ba78ffbc712ea07 to your computer and use it in GitHub Desktop.
Save Kcnarf/3f1740338371011b2ba78ffbc712ea07 to your computer and use it in GitHub Desktop.
Gooey egde effect
license: mit

OK, this is not an secret: this block is based on the work done by nbremer on the gooey effect (cf. the original block, and More fun data visualizations with the gooey effect).

In this block, the resulting image is produced by applying a filter on moving circles. The filter is the same as the one used to produce the gooey effect, except that it uses an extra step which detects edges (feConvolveMatrix).

Acknowledgments to:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- D3.js -->
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<style>
body {
text-align: center;
}
</style>
</head>
<body>
<div id="hexagon"></div>
<script language="javascript" type="text/javascript">
///////////////////////////////////////////////////////////////////////////
//////////////////// Set up and initiate svg containers ///////////////////
///////////////////////////////////////////////////////////////////////////
var margin = {
top: 10,
right: 0,
bottom: 10,
left: 0
};
var width = window.innerWidth - margin.left - margin.right - 10,
height = Math.min(500, window.innerHeight - margin.top - margin.bottom - 20);
//SVG container
var svg = d3.select('#hexagon')
.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 + ")");
///////////////////////////////////////////////////////////////////////////
/////////////////////// Calculate hexagon variables ///////////////////////
///////////////////////////////////////////////////////////////////////////
var SQRT3 = Math.sqrt(3),
hexRadius = Math.min(width, height)/2,
hexWidth = SQRT3 * hexRadius,
hexHeight = 2 * hexRadius;
var hexagonPoly = [[0,-1],[SQRT3/2,0.5],[0,1],[-SQRT3/2,0.5],[-SQRT3/2,-0.5],[0,-1],[SQRT3/2,-0.5]];
var hexagonPath = "m" + hexagonPoly.map(function(p){ return [p[0]*hexRadius, p[1]*hexRadius].join(','); }).join('l') + "z";
///////////////////////////////////////////////////////////////////////////
///////////////////////////// Create filter ///////////////////////////////
///////////////////////////////////////////////////////////////////////////
//SVG filter for the gooey effect
//Code taken from http://tympanus.net/codrops/2015/03/10/creative-gooey-effects/
var defs = svg.append("defs");
var filter = defs.append("filter").attr("id","gooedgeCodeFilter");
filter.append("feGaussianBlur")
.attr("in","SourceGraphic")
.attr("stdDeviation","10")
//to fix safari: http://stackoverflow.com/questions/24295043/svg-gaussian-blur-in-safari-unexpectedly-lightens-image
.attr("color-interpolation-filters","sRGB")
.attr("result","blur");
filter.append("feColorMatrix")
.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 19 -9")
.attr("result","gooey");
filter.append('feConvolveMatrix')
.attr('in','gooey')
.attr('kernelMatrix',[ 0, -1, 0,
-1, 4, -1,
0, -1, 0,].join(" "))
/*uncomment below code to have a 2 pixels (due to larger matrix) smoothed (due to .5) border, at the cost of computation performance
.attr('order', 5)
.attr('kernelMatrix',[ 0, 0, .5, 0, 0,
0, 0, .5, 0, 0,
.5, .5, 4, .5, .5,
0, 0, .5, 0, 0,
0, 0, .5, 0, 0,].join(" "))
*/
.attr('result','edges');
//Create a gradient for the fill
var colors = ["#490A3D","#BD1550","#E97F02","#F8CA00","#8A9B0F"];
defs.append("linearGradient")
.attr("id", "gradientRainbow")
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", -hexWidth/2*0.85).attr("y1", 0)
.attr("x2", hexWidth/2*0.85).attr("y2", 0)
.selectAll("stop")
.data(d3.range(colors.length))
.enter().append("stop")
.attr("offset", function(d,i) { return (i/(colors.length-1)*100) + "%"; })
.attr("stop-color", function(d) { return colors[d]; });
//Create a clip path that is the same as the top hexagon
defs.append("clipPath")
.attr("id", "clip")
.append("path")
.attr("d", "M" + (width/2) + "," + (height/2) + hexagonPath);
///////////////////////////////////////////////////////////////////////////
////////////////////// Place circles inside hexagon ///////////////////////
///////////////////////////////////////////////////////////////////////////
//First append a group for the clip path, then a new group that can be transformed
var circleWrapper = svg.append("g")
.attr("clip-path", "url(#clip")
.style("clip-path", "url(#clip)") //make it work in safari
.append("g")
.attr("transform", "translate(" + (width/2) + "," + (height/2) + ")")
.style("filter", "url(#gooedgeCodeFilter)");
//Create dataset with random initial positions
randStart = [];
for(var i = 0; i < 30; i++) {
randStart.push({
rHex: Math.random() * hexWidth,
theta: Math.random() * 2 * Math.PI,
r: 15 + Math.random() * 25
});
}//for i
var circle = circleWrapper.selectAll(".dots")
.data(randStart)
.enter().append("circle")
.attr("class", "dots")
.attr("cx", function(d) { return d.rHex * Math.cos(d.theta); })
.attr("cy", function(d) { return d.rHex * Math.sin(d.theta); })
.attr("r", 0)
.style("fill", "url(#gradientRainbow)")
.style("opacity", 1)
.each(move);
circle.transition("grow")
.duration(function(d,i) { return Math.random()*2000+500; })
.delay(function(d,i) { return Math.random()*3000;})
.attr("r", function(d,i) { return d.r; });
///////////////////////////////////////////////////////////////////////////
///////////////////////// Place Hexagon in center /////////////////////////
///////////////////////////////////////////////////////////////////////////
//Place a hexagon on the scene
svg.append("path")
.attr("class", "hexagon")
.attr("d", "M" + (width/2) + "," + (height/2) + hexagonPath)
.style("stroke", "#F2F2F2")
.style("stroke-width", "4px")
.style("fill", "none");
///////////////////////////////////////////////////////////////////////////
////////////////////// Circle movement inside hexagon /////////////////////
///////////////////////////////////////////////////////////////////////////
//General idea from Maarten Lambrecht's block: http://bl.ocks.org/maartenzam/f35baff17a0316ad4ff6
function move(d) {
var currentx = parseFloat(d3.select(this).attr("cx")),
radius = d.r;
//Randomly define which quadrant to move next
var sideX = currentx > 0 ? -1 : 1,
sideY = Math.random() > 0.5 ? 1 : -1,
randSide = Math.random();
var newx,
newy;
//Move new locations along the vertical sides in 33% of the cases
if (randSide > 0.66) {
newx = sideX * 0.5 * SQRT3 * hexRadius - sideX*radius;
newy = sideY * Math.random() * 0.5 * hexRadius - sideY*radius;
} else {
//Choose a new x location randomly,
//the y position will be calculated later to lie on the hexagon border
newx = sideX * Math.random() * 0.5 * SQRT3 * hexRadius;
//Otherwise calculate the new Y position along the hexagon border
//based on which quadrant the random x and y gave
if (sideX > 0 && sideY > 0) {
newy = hexRadius - (1/SQRT3)*newx;
} else if (sideX > 0 && sideY <= 0) {
newy = -hexRadius + (1/SQRT3)*newx;
} else if (sideX <= 0 && sideY > 0) {
newy = hexRadius + (1/SQRT3)*newx;
} else if (sideX <= 0 && sideY <= 0) {
newy = -hexRadius - (1/SQRT3)*newx;
}//else
//Take off a bit so it seems that the circles truly only touch the edge
var offSetX = radius * Math.cos( 60 * Math.PI/180),
offSetY = radius * Math.sin( 60 * Math.PI/180);
newx = newx - sideX*offSetX;
newy = newy - sideY*offSetY;
}//else
//Transition the circle to its new location
d3.select(this)
.transition("moveing")
.duration(3000 + 4000*Math.random())
.ease(d3.easeLinear)
.attr("cy", newy)
.attr("cx", newx)
.on("end", move);
}//function move
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment