D3 3.0’s projections use adaptive resampling to increase the accuracy of projected lines and polygons while still performing efficiently. On the left is no resampling, where obvious polygonal artifacts are visible due to projected lines becoming curves. Uniform resampling improves the appearance, but is inefficient because most of the resampling points are wasted, and areas of extreme distortion still exhibit artifacts. Adaptive resampling takes an idea from line simplification and resamples only where needed, producing high-quality results with low computational overhead.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.title {
font: bold 16px "Helvetica Neue";
text-transform: capitalize;
}
.graticule {
fill: none;
stroke: #999;
stroke-width: .5px;
}
.resample {
stroke: #f00;
stroke-width: 1.5px;
fill: none;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var width = 320,
height = 580,
velocity = .003,
time = Date.now();
var projection = d3.geo.equirectangular()
.rotate([0, 0, 89])
.translate([width / 2, height / 2])
.scale(85)
.precision(0);
var path = d3.geo.path()
.projection(projection);
var graticule = d3.geo.graticule();
d3.select("body")
.call(svg, "no resampling", [180].concat(d3.range(-90, 180, 90), [180]).map(function(x) { return [x, 0]; }), 0)
.call(svg, "uniform resampling", [180].concat(d3.range(-176, 180, 4), [180]).map(function(x) { return [x, 0]; }), 0)
.call(svg, "adaptive resampling", [[-180, 0], [-90, 0], [0, 0], [90, 0], [180, 0]], .5);
redraw();
function svg(body, title, coordinates, precision) {
var svg = body.append("svg")
.datum({
type: "LineString",
coordinates: coordinates,
precision: precision
})
.attr("width", width)
.attr("height", height);
svg.append("text")
.attr("class", "title")
.attr("x", width / 2)
.attr("y", height - 50)
.style("text-anchor", "middle")
.text(title);
var g = svg.append("g")
.attr("transform", "rotate(90 " + projection.translate() + ")");
g.append("path")
.attr("class", "resample");
g.append("path")
.datum(graticule)
.attr("class", "graticule");
}
function redraw() {
d3.selectAll("svg > g").each(function(d) {
var g = d3.select(this);
projection.precision(.3);
g.selectAll(".graticule").attr("d", path);
projection.precision(d.precision);
g.selectAll(".resample").attr("d", path);
// Override the path context to extract the resampled points.
path.context(bufferContext());
path(d);
var points = path.context().buffer();
path.context(null);
var point = g.selectAll(".point").data(points);
point.exit().remove();
point.enter().append("circle").attr("class", "point resample").attr("r", 4.5);
point.attr("transform", function(d) { return "translate(" + d + ")"; });
});
}
d3.timer(function() {
var dt = Date.now() - time;
projection.rotate([velocity * dt, 0, 89]);
redraw();
});
function bufferContext() {
var buffer = [];
return {
moveTo: function(x, y) { buffer.push([x, y]); },
lineTo: function(x, y) { buffer.push([x, y]); },
closePath: function() {},
buffer: function() { var _ = buffer; buffer = []; return _; }
};
}
d3.select(self.frameElement).style("height", height + "px");
</script>