Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@nbremer
Last active June 18, 2016 20:24
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nbremer/bd3dd129b4a812828397a85db6ba36b7 to your computer and use it in GitHub Desktop.
Save nbremer/bd3dd129b4a812828397a85db6ba36b7 to your computer and use it in GitHub Desktop.
Color blending - Hexagon

This is the first example of my blog on Beautiful color blending effects with SVGs & D3

The hexagon introduction to the Color blending section of my OpenVis 2016 talk "SVGs beyond mere shapes". In this example the circles are given the mix-blend-mode of screen to get nice effects when the circles overlap each other.

Another color blending example can be found here

Built with blockbuilder.org

<!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";
///////////////////////////////////////////////////////////////////////////
////////////////////// Place circles inside hexagon ///////////////////////
///////////////////////////////////////////////////////////////////////////
//Create a clip path that is the same as the top hexagon
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("path")
.attr("d", "M" + (width/2) + "," + (height/2) + hexagonPath);
//First append a group for the clip path, then a new group that can be transformed
var circleWrapperOuter = svg.append("g")
.attr("clip-path", "url(#clip)")
.style("clip-path", "url(#clip)"); //make it work in safari
var circleWrapperInner = circleWrapperOuter.append("g")
.attr("transform", "translate(" + (width/2) + "," + (height/2) + ")")
.style("isolation", "isolate");
var colors = ["#2c7bb6", "#00a6ca","#00ccbc","#90eb9d","#ffff8c","#f9d057","#f29e2e","#e76818","#d7191c"];
//Create dataset with random initial positions
randStart = [];
for(var i = 0; i < 6*colors.length; i++) {
randStart.push({
rHex: Math.random() * hexWidth,
theta: Math.random() * 2 * Math.PI,
r: 4 + Math.random() * 25
});
}//for i
//Background rectangle
circleWrapperInner.append("rect")
.attr("x", -hexWidth/2)
.attr("y", -hexHeight/2)
.attr("width", hexWidth)
.attr("height", hexHeight)
.style("fill", "#262626");
var circle = circleWrapperInner.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", function(d,i) {
return colors[i%colors.length];
})
.style("opacity", 1)
.style("mix-blend-mode", "screen")
.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", "#00a6ca")
.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")),
mode = d3.select(this).style("mix-blend-mode"),
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)
.style("mix-blend-mode", "screen")
.each("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