Skip to content

Instantly share code, notes, and snippets.

@mbostock
Forked from jasondavies/README.md
Last active December 19, 2022 23:56
Show Gist options
  • Save mbostock/3795544 to your computer and use it in GitHub Desktop.
Save mbostock/3795544 to your computer and use it in GitHub Desktop.
Adaptive Resampling
license: gpl-3.0
redirect: https://observablehq.com/@d3/adaptive-sampling

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment