block by nbremer 3da658e9a21cd3c71d0819f9698f3bfa

Gooey effect - Hexagon

Full Screen

The hexagon with gooey circles moving throughout it that I made for the intro slide to the Gooey section of my OpenVis presentation.

This example also appears on my blog on More fun data visualizations with the gooey effect

You can find the other examples from the blog here

Older examples with the Gooey effect

Built with blockbuilder.org

index.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		
		<!-- D3.js -->
		<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.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 //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("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");

			//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(#gooeyCodeFilter)");

			//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: //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("linear")
					.attr("cy", newy)
					.attr("cx", newx)
					.each("end", move);

			}//function move

		</script>

	</body>
</html>