Skip to content

Instantly share code, notes, and snippets.

@mbostock
Last active October 27, 2016 18:09
Show Gist options
  • Save mbostock/5663666 to your computer and use it in GitHub Desktop.
Save mbostock/5663666 to your computer and use it in GitHub Desktop.
Custom Projection
license: gpl-3.0
height: 800

d3.geoPath is typically used to render spherical geometry by passing it through a geographic projection, projecting from the sphere to the plane while applying adaptive sampling. But what if your geometry is already planar? That is, what if you just want to take projected geometry, but still translate or scale it to fit the viewport?

You can implement a custom geometry transform to gain complete control over the projection process. Transforms implement the projection.stream method, and can be passed to path.projection in lieu of a geographic projection. The easiest way to implement a transform is d3.geoTransform. For example, here’s a 2D affine transform implementation:

function matrix(a, b, c, d, tx, ty) {
  return d3.geoTransform({
    point: function(x, y) {
      this.stream.point(a * x + b * y + tx, c * x + d * y + ty);
    }
  });
}

For example, to invert the y-axis, you can say:

var path = d3.geoPath()
    .projection(matrix(1, 0, 0, -1, 0, height));

A custom geometry transform can even use quantitative scales!

Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.land {
fill: #ddd;
}
.boundary {
fill: none;
stroke: #999;
}
</style>
<svg width="960" height="800"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var path = d3.geoPath()
.projection(matrix(1, 0, 0, -1, 0, height));
d3.json("ca.json", function(error, ca) {
if (error) throw error;
svg.append("path")
.datum(topojson.feature(ca, ca.objects.counties))
.attr("class", "land")
.attr("d", path);
svg.append("path")
.datum(topojson.mesh(ca, ca.objects.counties, function(a, b) { return a !== b; }))
.attr("class", "boundary")
.attr("d", path);
});
// https://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations
function matrix(a, b, c, d, tx, ty) {
return d3.geoTransform({
point: function(x, y) {
this.stream.point(a * x + b * y + tx, c * x + d * y + ty);
}
});
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment