2017-03-26 10:24:54 -04:00
|
|
|
|
<!DOCTYPE html>
|
2017-03-26 10:32:52 -04:00
|
|
|
|
<!--
|
|
|
|
|
This is based off of Philippe Rivière’s 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.
|
|
|
|
|
-->
|
2017-03-26 10:24:54 -04:00
|
|
|
|
<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>
|
|
|
|
|
|
2017-03-26 10:32:52 -04:00
|
|
|
|
var sourcedata = [
|
|
|
|
|
100, 100, 200, 130, 400,
|
|
|
|
|
150, 360, 170, 130, 900,
|
|
|
|
|
100, 500, 200, 130, 482,
|
|
|
|
|
150, 160, 170, 130, 400 ];
|
2017-03-26 10:24:54 -04:00
|
|
|
|
|
2017-03-26 10:32:52 -04:00
|
|
|
|
var svg = d3.select("svg");
|
|
|
|
|
var width = svg.attr("width");
|
|
|
|
|
var height = svg.attr("height");
|
|
|
|
|
var margin = 0.1; // 0 ≤ m < 0.5
|
2017-03-26 10:24:54 -04:00
|
|
|
|
|
2017-03-26 10:32:52 -04:00
|
|
|
|
var color = d3.scaleOrdinal(d3.schemePastel1);
|
|
|
|
|
var line = d3.line().curve(d3.curveCatmullRomClosed);
|
2017-03-26 10:24:54 -04:00
|
|
|
|
|
|
|
|
|
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>
|