Computing the extent for a spherical Voronoi diagram.
The pain point here was that we're filtering a GeoJSON object, and want the results to stay in spherical coordinates. However the clipping should reflect the way features will be printed on paper (or on screen), i.e. in the final projection (Mercator).
Next steps: make this a configurable function and one that preserves the features’ properties.
by Philippe Rivière.
forked from Fil's block: geoVoronoi polygons()
xxxxxxxxxx
<meta charset="utf-8">
<style>
#sphere {
stroke: #444;
stroke-width: 2;
fill: #eee;
}
.polygons {
stroke: #444;
}
.sites {
stroke: black;
stroke-width: 0.5;
fill: white;
}
.sites .clipped {
fill: olive;
}
</style>
<body></body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3-geo-voronoi"></script>
<script src="https://d3js.org/d3-geo-projection.v1.min.js"></script>
<script>
var width = 960, height = 480,
svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height);
var center = [-60,35];
var points = {
type: "FeatureCollection",
features: d3.range(200).map(function(d,i) {
return {
type: "Feature",
geometry: {
type: "Point",
coordinates: [ center[0] + 25 * (Math.random() - Math.random()), center[1] + 10 * (Math.random() - Math.random()) ]
}
}
})
};
var padding = 30,
projection = d3.geoMercator()
.fitExtent([[padding,padding], [width-padding, height-padding]], points);
// Clip the features to a certain paper extent around a center.
var projection_invert = d3.geoTransform({
point: function(x, y) {
let u = projection.invert([x,y]);
this.stream.point(u[0], u[1]);
}
});
var clip_padding = 70;
var clip2paper = d3.geoIdentity()
.clipExtent([[clip_padding,clip_padding], [width-clip_padding, height-clip_padding]])
var clip = function(d) {
d = d3.geoProject(d, projection);
d = d3.geoProject(d, clip2paper);
d = d3.geoProject(d, projection_invert);
return d;
}
// normally one would use the same projection for clipping and for display;
// here we change it only for the pleasure of the demo
var projection2 = d3.geoOrthographic()
.rotate([-center[0], -center[1]])
.fitExtent([[padding,padding], [960-padding, 500-padding]], points);
var _path = d3.geoPath().projection(projection2),
path = function(d) { return _path(clip(d)); };
// end clip code
var v = d3.geoVoronoi()(points),
features = v.polygons().features;
var svg = d3.select("svg");
svg.append('g')
.attr('class', 'polygons')
.selectAll('path')
.data(features)
.enter()
.append('path')
.attr('d', path)
.attr('fill', function(_,i) { return d3.schemeCategory10[i%10]; })
.attr('fill-opacity', 0.3)
svg.append('g')
.attr('class', 'sites')
.selectAll('path')
.data(points.features)
.enter()
.append('path')
.attr('d', _path) // display all sites (no clipping)
.classed('clipped', d => !path(d)) // mark those that would be clipped
;
</script>
https://d3js.org/d3.v4.min.js
https://unpkg.com/d3-geo-voronoi
https://d3js.org/d3-geo-projection.v1.min.js