dataviz/bounded-voronoi.html

182 lines
4.3 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE html>
<!--
This is based off of Philippe Rivières Blocks at https://bl.ocks.org/Fil/aa92ffae75cc880f7880a3dd6da3ae18.
I just wanted to make it take data from a pre-set list, instead of randomly generating
data points each time it's loaded.
-->
<meta charset="utf-8">
<style>
.links {
stroke: #000;
stroke-opacity: 0;
}
.polygons {
stroke: #fff;
}
.polygons :first-child {
fill: #ff3d5a;
}
.sites {
fill: none;
stroke: none;
}
.sites :first-child {
fill: #fff;
}
.convex-hull {
fill: none;
stroke: #feccd5;
stroke-width: 5px;
}
</style>
<svg width="960" height="800"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script>
var sourcedata = [
100, 100, 200, 130, 400,
150, 360, 170, 130, 900,
100, 500, 200, 130, 482,
150, 160, 170, 130, 400 ];
var svg = d3.select("svg");
var width = svg.attr("width");
var height = svg.attr("height");
var margin = 0.1; // 0 ≤ m < 0.5
var color = d3.scaleOrdinal(d3.schemePastel1);
var line = d3.line().curve(d3.curveCatmullRomClosed);
function makeData (inputData) {
var outputData = [];
// First sum all the values. Then calculate a percentage of the whole.
var sum = inputData.reduce(function(acc, val) {
return acc + val;
}, 0);
// Now turn the single data point into a pair of length, angle
for (var i = 0; i < inputData.length; i++) {
// calc the percentage
var value = 5 * inputData[i] / sum;
// calculate length and angle from that percentage
var len = Math.min(width, height) / 2 *
(1 - margin) * Math.sqrt(value);
var angle = value * 2 * Math.PI;
// stick the length and angle into the output data
outputData[i] = [ width / 2 + len * Math.cos(angle),
height / 2 + len * Math.sin(angle) ];
}
return outputData;
}
var sites = makeData( sourcedata );
var voronoi = d3.voronoi()
.extent([[-1, -1], [width + 1, height + 1]]);
var convexhull = svg.append('path')
.attr('class', 'convex-hull');
var polygon = svg.append("g")
.attr("class", "polygons");
var link = svg.append("g")
.attr("class", "links");
var site = svg.append("g")
.attr("class", "sites");
redraw();
function redraw() {
var links = voronoi.links(sites),
ext = Math.sqrt(d3.median(links, function(l) {
var dx = l.source[0] - l.target[0],
dy = l.source[1] - l.target[1];
return dx*dx + dy*dy;
}));
var convex = d3.polygonHull(sites);
convex.centroid = d3.polygonCentroid(convex);
convex = convex.map(function(p){
var dx = p[0] - convex.centroid[0],
dy = p[1] - convex.centroid[1],
angle = Math.atan2(dy, dx);
return [p[0] + Math.cos(angle) * ext, p[1] + Math.sin(angle) * ext];
});
var sites2 = sites.slice(); // clone
for (var i = 0; i < convex.length; i++) {
var n = convex[i], m = convex[i+1]||convex[0];
var dx = n[0] - m[0],
dy = n[1] - m[1],
dist = Math.sqrt(dx * dx + dy * dy);
var pts = 10 * Math.ceil(dist / 2 / ext);
for(var j=0; j <= pts; j++) {
var p = [m[0] + dx *j / pts, m[1] + dy * j / pts];
p.artificial = 1;
sites2.push(p);
}
}
var diagram = voronoi(sites2);
var p = polygon.selectAll("path")
.data(diagram.polygons());
p.enter().append("path").merge(p).call(redrawPolygon)
p.exit().remove();
var l = link
.selectAll("line")
.data(diagram.links().filter(function(l){
return !l.source.artificial && !l.target.artificial;
}));
l.enter()
.append("line")
.merge(l)
.call(redrawLink)
.exit()
.remove();
convexhull.attr('d', line(convex));
}
function redrawPolygon(polygon) {
polygon
.attr("fill", function(d, i) {
return i < sites.length ? color(i) : 'none';
})
.attr("stroke-width", function(d, i) {
return i < sites.length ? 2 : 0;
})
.attr("d", function(d) { return d ? "M" + d.join("L") + "Z" : null; });
}
function redrawLink(link) {
link
.attr("x1", function(d) { return d.source[0]; })
.attr("y1", function(d) { return d.source[1]; })
.attr("x2", function(d) { return d.target[0]; })
.attr("y2", function(d) { return d.target[1]; });
}
function redrawSite(site) {
site
.attr("cx", function(d) { return d[0]; })
.attr("cy", function(d) { return d[1]; });
}
</script>