Drag the circles to change the Voronoi diagram. It also updates itself with new data.
See also:
license: gpl-3.0 |
Drag the circles to change the Voronoi diagram. It also updates itself with new data.
See also:
<html> | |
<head> | |
<style> | |
body { | |
margin: 0; | |
} | |
.voronoi { | |
stroke-width: 1px; | |
stroke: #3a403d; | |
} | |
.dot { | |
fill: #fff; | |
fill-opacity: .1; | |
stroke:#3a403d; | |
cursor: move; | |
} | |
</style> | |
</head> | |
<body> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/1.2.1/chroma.min.js"></script> | |
<script> | |
var width = window.innerWidth, | |
height = window.innerHeight, | |
alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".split(""), | |
duration = 12, // milliseconds of transition duration | |
n = 5, // amount of random movement | |
r = [], | |
data = [], | |
c = chroma.scale(["#fc8d62", "#ffd92f", "#66c2a5", "#8da0cb"]).domain([0, width * height / alphabet.length * 2]); // color scale | |
for (var i = n * -1; i <= n; i++){ | |
r.push(i / 125); | |
} | |
alphabet.forEach(function(d){ | |
data.push({name: d, x: random(0, 100), y: random(0, 100)}) | |
}); | |
// scales | |
var x = d3.scaleLinear().domain([0, 100]).range([0, width]); | |
var y = d3.scaleLinear().domain([0, 100]).range([height, 0]); | |
var xr = d3.scaleLinear().domain([0, width]).range([0, 100]); | |
var yr = d3.scaleLinear().domain([height, 0]).range([0, 100]); | |
// wrapper | |
var svg = d3.select("body").append("svg") | |
.attr("width", width) | |
.attr("height", height); | |
// voronoi tesselation | |
var voronoi = d3.voronoi() | |
.x(function(d) { return x(d.x); }) | |
.y(function(d) { return y(d.y); }) | |
.extent([[0, 0], [width, height]]); | |
function redraw(data){ | |
// transition | |
var t = d3.transition() | |
.duration(duration); | |
// JOIN | |
var voronoiGroup = svg.selectAll(".voronoi") | |
.data(voronoi(data).polygons(), function(d){ return d.data.name; }); | |
var circle = svg.selectAll(".dot") | |
.data(data, function(d){ return d.name; }); | |
// EXIT | |
voronoiGroup.exit().remove(); | |
circle.exit().remove(); | |
// UPDATE | |
voronoiGroup | |
.transition(t) | |
.attr("d", function(d) { return d ? "M" + d.join("L") + "Z" : null; }) | |
.style("fill", function(d){ return c(area(d) * -1); }); | |
circle | |
.transition(t) | |
.attr("cx",function(d){ return x(d.x); }) | |
.attr("cy",function(d){ return y(d.y); }); | |
// ENTER | |
voronoiGroup.enter().append("path") | |
.attr("class", "voronoi") | |
.attr("d", function(d) { return d ? "M" + d.join("L") + "Z" : null; }) | |
.style("fill", function(d){ return c(area(d) * -1); }) | |
circle.enter().append("circle") | |
.attr("class", "dot") | |
.attr("r", 5) | |
.attr("cx",function(d){ return x(d.x); }) | |
.attr("cy",function(d){ return y(d.y); }) | |
.call(d3.drag() | |
.on("drag", dragged) | |
); | |
} | |
redraw(data); | |
d3.interval(function() { | |
data.forEach(function(d,i){ | |
d.x = d.x + (1 * r[random(0, r.length - 1)]); | |
d.y = d.y + (1 * r[random(0, r.length - 1)]); | |
if (d.x < 0){ | |
d.x = 0; | |
} else if (d.x > 100){ | |
d.x = 100; | |
} | |
if (d.y < 0){ | |
d.y = 0; | |
} else if (d.y > 100){ | |
d.y = 100; | |
} | |
data[i] = d; | |
}); | |
redraw(data); | |
}, duration * 2); | |
/*FUNCTIONS*/ | |
function dragged(d){ | |
var coordinates = [0, 0]; | |
coordinates = d3.mouse(this); | |
d.x = xr(coordinates[0]); | |
d.y = yr(coordinates[1]); | |
var i = findWithAttr(data, "name", d.name); | |
data[i] = d; | |
redraw(data); | |
} | |
function findWithAttr(array, attr, value) { | |
for(var i = 0; i < array.length; i += 1) { | |
if(array[i][attr] === value) { | |
return i; | |
} | |
} | |
return -1; | |
} | |
function random(min, max) { | |
return Math.floor(Math.random() * (max - min + 1)) + min; | |
} | |
function shuffle(array) { | |
var m = array.length, t, i; | |
// While there remain elements to shuffle… | |
while (m) { | |
// Pick a remaining element… | |
i = Math.floor(Math.random() * m--); | |
// And swap it with the current element. | |
t = array[m]; | |
array[m] = array[i]; | |
array[i] = t; | |
} | |
return array; | |
} | |
function area(points) { | |
var sum = 0.0; | |
var length = points.length; | |
if (length < 3) { | |
return sum; | |
} | |
points.forEach(function(d1, i1) { | |
i2 = (i1 + 1) % length; | |
d2 = points[i2]; | |
sum += (d2[1] * d1[0]) - (d1[1] * d2[0]); | |
}); | |
return sum / 2; | |
} | |
</script> | |
</body> | |
</html> |