|
<meta charset="utf-8"> |
|
<title>Voronoï playground : parquet deformation</title> |
|
<meta content="Using D3's Voronoï layout to simulate parquet deformation" name="description"> |
|
<style> |
|
|
|
#under-construction { |
|
display: none; |
|
position: absolute; |
|
top: 200px; |
|
left: 300px; |
|
font-size: 40px; |
|
} |
|
|
|
svg { |
|
margin: 1px; |
|
border-radius: 1000px; |
|
box-shadow: 2px 2px 6px grey; |
|
} |
|
|
|
#voronoi-container { |
|
filter: url("#blur"); |
|
} |
|
|
|
#hover-area { |
|
fill: transparent; |
|
stroke: none; |
|
cursor: crosshair; |
|
} |
|
|
|
.seed { |
|
fill: black; |
|
} |
|
|
|
.cell { |
|
fill: none; |
|
stroke: grey; |
|
} |
|
|
|
.hide { |
|
display: none; |
|
} |
|
</style> |
|
<body> |
|
<div id="under-construction"> |
|
UNDER CONSTRUCTION |
|
</div> |
|
|
|
<svg> |
|
<defs> |
|
<filter id="blur"> |
|
<feGaussianBlur result="blur1" in="SourceGraphic" stdDeviation="0.5" /> |
|
</filter> |
|
</defs> |
|
<g id="drawing-area"> |
|
<path id="wave-path"/> |
|
<g id="voronoi-container"/> |
|
<g id="seed-container"/> |
|
<circle id="hover-area"></circle> |
|
</g> |
|
</svg> |
|
|
|
<script src="https://d3js.org/d3.v4.js"></script> |
|
<script src="simplex-noise.min.js"></script> |
|
<script> |
|
var _2PI = 2*Math.PI; |
|
|
|
//begin: layout conf. |
|
var svgbw = 1, //svg border width |
|
svgm = 10, //svg margin |
|
totalWidth = 500, |
|
totalHeight = 500, |
|
width = totalWidth-(svgm+svgbw)*2, |
|
height = totalHeight-(svgm+svgbw)*2, |
|
midWidth = width/2, |
|
midHeight = height/2; |
|
//end: layout conf. |
|
|
|
//begin: voronoi conf. |
|
//we oragnize seeds on cocentric rings; |
|
//cells from the inner and outter rings will be hidden |
|
var visibleRingCount = 15, |
|
minVisibleSeedCount = 12, |
|
maxVisibleSeedCount = 48, |
|
visibleSeedCountScale = d3.scaleQuantize() |
|
.domain([0,midWidth]) |
|
.range(d3.range(minVisibleSeedCount,maxVisibleSeedCount+1)), |
|
visibleSeedCount = visibleSeedCountScale(midWidth), |
|
outerRingDistance = midWidth, |
|
hideSeeds = true, |
|
seeds = []; |
|
//end: voronoi conf. |
|
|
|
var voronoiLayout = d3.voronoi() |
|
.x(function(d) { return d.x; }) |
|
.y(function(d) { return d.y; }) |
|
.extent([[-(midWidth+10), -(midHeight+10)], [midWidth+10, midHeight+10]]); |
|
|
|
var svg, drawingArea, voronoiContainer, seedContainer; |
|
|
|
initLayout(); |
|
|
|
computeSeeds(); |
|
redrawVoronoi(); |
|
redrawSeeds(); |
|
|
|
|
|
function initLayout() { |
|
svg = d3.select("svg") |
|
.attr("width", width) |
|
.attr("height", height); |
|
|
|
drawingArea = d3.select("#drawing-area") |
|
.attr("transform", "translate("+[midWidth, midHeight]+")rotate(90)"); |
|
d3.select("#hover-area") |
|
.attr("r", midWidth) |
|
.on("touchmove mousemove", moved) |
|
.on("mouseout", exited) |
|
.on("click", clicked); |
|
|
|
voronoiContainer = d3.select("#voronoi-container"); |
|
seedContainer = d3.select("#seed-container").classed("hide", hideSeeds); |
|
} |
|
|
|
function moved() { |
|
var coords = d3.mouse(this); |
|
var mouseRadius = Math.sqrt(Math.pow(coords[0],2)+Math.pow(coords[1],2)); |
|
visibleSeedCount = visibleSeedCountScale(mouseRadius); |
|
computeSeeds(); |
|
redrawVoronoi(); |
|
redrawSeeds(); |
|
} |
|
|
|
function exited() { |
|
visibleSeedCount = visibleSeedCountScale(midWidth); |
|
redrawSeeds; |
|
} |
|
|
|
function clicked() { |
|
hideSeeds = !hideSeeds; |
|
seedContainer.classed("hide", hideSeeds); |
|
} |
|
|
|
function computeSeeds() { |
|
seeds = []; |
|
var hidden = true, |
|
currentRingDistance = outerRingDistance, |
|
ri=visibleRingCount, |
|
currentInterRing; |
|
|
|
//add outer ring, which produces hidden cells |
|
seeds = seeds.concat(ringSeeds(visibleSeedCount-ri%2, currentRingDistance, hidden)); |
|
//add interim rings, going to the center, which produces visible cells; recomputing adequate distance between rings in order to have square-like cells at each ring's bottom |
|
while(ri>0) { |
|
ri--; |
|
currentInterRing = currentRingDistance*Math.sin(_2PI/visibleSeedCount); |
|
currentRingDistance -= currentInterRing; |
|
seeds = seeds.concat(ringSeeds(visibleSeedCount-ri%2, currentRingDistance, !hidden)); |
|
} |
|
//add inner ring, which produces hidden cells |
|
ri-- |
|
currentInterRing = currentRingDistance*Math.sin(_2PI/visibleSeedCount); |
|
currentRingDistance -= currentInterRing; |
|
seeds = seeds.concat(ringSeeds(visibleSeedCount-ri%2, currentRingDistance, hidden)); |
|
} |
|
|
|
function ringSeeds(seedCount, distance, hidden) { |
|
var ringSeeds=[], |
|
interRadAngle = _2PI/seedCount, |
|
interDegAngle = 360/seedCount, |
|
radAngle, cos, sin, distance; |
|
for(var ai=0; ai<seedCount; ai++) { |
|
radAngle = interRadAngle*ai; |
|
degAngle = interDegAngle*ai; |
|
cos = Math.cos(radAngle); |
|
sin = Math.sin(radAngle); |
|
ringSeeds.push({ |
|
x: (distance*cos).toFixed(4), |
|
y: (distance*sin).toFixed(4), |
|
hidden: hidden, |
|
strokeColor: d3.hsl(Math.abs(degAngle-180), 1, 0.45) |
|
}); |
|
} |
|
return ringSeeds; |
|
} |
|
|
|
function redrawVoronoi() { |
|
//begin: draw Voronoi cells |
|
voronoiContainer.selectAll(".cell").remove(); |
|
var drawnCells = voronoiContainer.selectAll(".cell") |
|
.data(voronoiLayout.polygons(seeds)); |
|
drawnCells.enter() |
|
.append("path") |
|
.classed("cell", true) |
|
.classed("hide", function(d){ return d.data.hidden; }) |
|
.attr("d", function(d){ return d3.line()(d)+"z"; }) |
|
.style("stroke", function(d){ return d.data.strokeColor; }); |
|
//end: draw Voronoi cells |
|
} |
|
|
|
function redrawSeeds() { |
|
//begin: draw Voronoi cells |
|
seedContainer.selectAll(".seed").remove(); |
|
var drawnSeeds = seedContainer.selectAll(".seed") |
|
.data(seeds); |
|
drawnSeeds.enter() |
|
.append("circle") |
|
.classed("seed", true) |
|
.attr("cx", function(d){ return d.x; }) |
|
.attr("cy", function(d){ return d.y; }) |
|
.attr("r", 1); |
|
//end: draw Voronoi cells |
|
} |
|
</script> |
|
</body> |