|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<meta charset="utf-8" /> |
|
<title>Hexagonal SVG gradient experiment</title> |
|
<style> |
|
body { |
|
/*background-color: #262626;*/ |
|
text-align: center; |
|
} |
|
</style> |
|
|
|
<script src="https://unpkg.com/d3@5/dist/d3.js"></script> |
|
</head> |
|
<body> |
|
<div id="hexmap"></div> |
|
<script> |
|
|
|
/////////////////////////////////////////////////////////////////////////// |
|
//////////////////// Set up and initiate svg containers /////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
|
|
|
var hexRadius = 23, |
|
SQRT3 = Math.sqrt(3), |
|
hexWidth = SQRT3 * hexRadius, |
|
hexHeight = 2 * hexRadius; |
|
|
|
var margin = { |
|
top: hexHeight / 2 + 5, |
|
right: 0, |
|
bottom: 0, |
|
left: hexWidth + 5 |
|
}; |
|
var width = 960 - margin.left - margin.right - 10; |
|
var height = 960 - margin.top - margin.bottom - 20; |
|
|
|
//SVG container |
|
var svg = d3.select('#hexmap') |
|
.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 defs = svg.append("defs"); |
|
|
|
var colors = ["#EFB605", "#E9A501", "#E48405", "#E34914", "#DE0D2B", "#CF003E", "#B90050", "#A30F65", "#8E297E", "#724097", "#4F54A8", "#296DA4", "#0C8B8C", "#0DA471", "#39B15E", "#7EB852"]; |
|
|
|
var gradRotations = [0, 60, 120]; |
|
|
|
var animationID, |
|
animationLoop = 1, |
|
extraStep = 0, |
|
alpha = 1, |
|
reduceAlpha = false, |
|
keepGrowing = true; |
|
|
|
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////// Calculate hexagon variables /////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
|
|
|
var SQRT3 = Math.sqrt(3), |
|
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"; |
|
|
|
var mapColumns = Math.floor(width/(SQRT3 * hexRadius)) + 1, |
|
mapRows = Math.floor(height/(2 * hexRadius * 0.75)); |
|
|
|
/////////////////////////////////////////////////////////////////////////// |
|
//////////////// Calculate hexagon centers and put into array ///////////// |
|
/////////////////////////////////////////////////////////////////////////// |
|
|
|
var points = []; |
|
var pointsByIndex = []; |
|
var counter = 0; |
|
for (var i = 0; i < mapRows; i++) { |
|
for (var j = 0; j < mapColumns; j++) { |
|
var a; |
|
var b = (3 * i) * hexRadius / 2; |
|
if (i % 2 === 0) { |
|
a = Math.sqrt(3) * j * hexRadius; |
|
} else { |
|
a = Math.sqrt(3) * (j - 0.5) * hexRadius; |
|
}//else |
|
points.push({ |
|
x: a, y: b, rowIndex: i, colIndex: j, fill: false |
|
}); |
|
pointsByIndex[i + "-" + j] = counter; |
|
counter += 1; |
|
}//for j |
|
}//for i |
|
|
|
/////////////////////////////////////////////////////////////////////////// |
|
///////////////////////////// Place hexagons ////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
|
|
|
svg.append("g") |
|
.selectAll(".hexagon") |
|
.data(points) |
|
.enter().append("path") |
|
.attr("class", "hexagon") |
|
.attr("d", function (d) { return "M" + d.x + "," + d.y + hexagonPath; }) |
|
.style("stroke", "none") |
|
.style("fill", "none") |
|
.style("stroke-opacity", 0) |
|
.style("fill-opacity", 0); |
|
|
|
/////////////////////////////////////////////////////////////////////////// |
|
///////////////////////////// Fill hexagons /////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
|
|
|
//Start it off in the near middle |
|
var centerRow = Math.round(mapRows/2), |
|
centerCol = Math.round(mapColumns/2), |
|
filledHexs = []; |
|
filledHexs[centerRow+"-"+centerCol] = {x: centerRow, y: centerCol}; |
|
createGradient(filledHexs[centerRow+"-"+centerCol]); |
|
fillHex(filledHexs[centerRow+"-"+centerCol]); |
|
|
|
function animate() { |
|
|
|
//Loop over all already filled hexagons |
|
for(var hex in filledHexs) { |
|
//Find its neighbours and append them to the already existing ones |
|
//make sure to have only the unique one |
|
var newNeighbours = findNeighbours(filledHexs[hex]); |
|
newNeighbours.forEach(function(d) { |
|
//With a probability add the hexagon to the filled set |
|
if(Math.random() < 0.05) { |
|
if(d.x >= 0 && d.x < mapRows && d.y >= 0 && d.y < mapColumns) { |
|
if(filledHexs[d.x+"-"+d.y] === undefined) { |
|
filledHexs[d.x+"-"+d.y] = d; |
|
//Create a gradient for the hexagon |
|
createGradient(d); |
|
fillHex(d); |
|
}//if |
|
|
|
//If the growth is almost at the outside, let the alpha shrink |
|
if( d.x < mapRows*0.25 || d.x > mapRows*0.75 || d.y < mapColumns*0.25 || d.y > mapColumns*0.75 ) { |
|
reduceAlpha = true; |
|
}//if |
|
|
|
} else { |
|
//Stop growing once one hexagon neighbour would lie outside the box |
|
keepGrowing = false; |
|
}//else |
|
}//if |
|
}); |
|
}//for hex |
|
|
|
//Exponential decay of alpha |
|
if(reduceAlpha) { |
|
extraStep = extraStep + 0.075; |
|
alpha = Math.min(1, (1 - Math.exp(-5+extraStep)) ); |
|
} |
|
|
|
//Set the found neighbours to have a fill |
|
for(var item in filledHexs) { |
|
points[pointsByIndex[item]].fill = true; |
|
}//for item |
|
|
|
if(keepGrowing) { |
|
animationID = requestAnimationFrame(animate); |
|
}//if |
|
|
|
}//animate |
|
|
|
//Start the run |
|
requestAnimationFrame(animate); |
|
|
|
/////////////////////////////////////////////////////////////////////////// |
|
////////////////////////// Helper functions /////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
|
|
|
//Create a linear gradient for the hexagon d |
|
function createGradient(d) { |
|
|
|
//Create a gradient |
|
var linearGradient = defs.append("linearGradient") |
|
.attr("id", "gradient-"+d.x+"-"+d.y) |
|
.attr("x1", "0%").attr("y1", "0%") |
|
.attr("x2", "0%").attr("y2", "100%") |
|
.attr("gradientTransform", "rotate("+ (gradRotations[Math.round(Math.random()*(gradRotations.length-1))]) +" "+ 0.5 +" "+ 0.5 +")"); |
|
|
|
linearGradient.append("stop") |
|
.attr("offset", "0%") |
|
.attr("stop-color", colors[Math.round(Math.random()*(colors.length-1))] ) |
|
.attr("stop-opacity", alpha ); |
|
|
|
linearGradient.append("stop") |
|
.attr("offset", "100%") |
|
.attr("stop-color", colors[Math.round(Math.random()*(colors.length-1))]) |
|
.attr("stop-opacity", alpha ); |
|
|
|
}//createGradient |
|
|
|
function fillHex(d) { |
|
//Fill the chosen hexagon with its color |
|
d3.selectAll(".hexagon") |
|
.filter(function(h,i) { return h.rowIndex === d.x && h.colIndex === d.y; }) |
|
.style("fill", "url(#gradient-"+d.x+"-"+d.y+")" ) |
|
.style("stroke", "url(#gradient-"+d.x+"-"+d.y+")" ) |
|
.transition().duration(500) |
|
.style("fill-opacity", 1) |
|
.style("stroke-opacity", alpha > 0.9 ? 1 : alpha*0.35 ); |
|
}//fillHex |
|
|
|
//Find the row and column locations of the 6 neighbours around a hexagon |
|
function findNeighbours(currentHex) { |
|
var i = currentHex.y, |
|
j = currentHex.x; |
|
//http://stackoverflow.com/questions/6661169/finding-adjacent-neighbors-on-a-hexagonal-grid |
|
if(j % 2 === 0) { |
|
//For a cell (X,Y) where Y is even, the neighbors are |
|
var neighbours = [ |
|
{y: i, x: (j-1)}, |
|
{y: (i+1), x: (j-1)}, |
|
{y: (i-1), x: j}, |
|
{y: (i+1), x: j}, |
|
{y: i, x: (j+1)}, |
|
{y: (i+1), x: (j+1)} ]; |
|
} else { |
|
//For a cell (y,x) where x is odd, the neighbors are |
|
var neighbours = [ |
|
{y: (i-1), x: (j-1)}, |
|
{y: i, x: (j-1)}, |
|
{y: (i-1),x: j}, |
|
{y: (i+1), x: j}, |
|
{y: (i-1), x: (j+1)}, |
|
{y: i, x: (j+1)} ]; |
|
}//else |
|
return neighbours; |
|
}//findNeighbours |
|
|
|
//http://stackoverflow.com/questions/3689903/how-to-create-a-2d-array-of-zeroes-in-javascript |
|
function zeros(dimensions) { |
|
var array = []; |
|
for (var i = 0; i < dimensions[0]; ++i) { |
|
array.push(dimensions.length == 1 ? 0 : zeros(dimensions.slice(1))); |
|
} |
|
return array; |
|
// zeros([5, 3]); |
|
// [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]] |
|
}//zeros |
|
|
|
</script> |
|
</body> |
|
</html> |