Skip to content

Instantly share code, notes, and snippets.

@mbostock
Forked from mbostock/.block
Last active September 11, 2018 09:45
Show Gist options
  • Select an option

  • Save mbostock/4136647 to your computer and use it in GitHub Desktop.

Select an option

Save mbostock/4136647 to your computer and use it in GitHub Desktop.
U.S. TopoJSON

A demo of TopoJSON using a single file that contains county, state and country boundaries. This example uses topojson.mesh to extract separating lines between states and counties from the geometry collections.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
width: 100%;
}
path {
fill: none;
stroke-linejoin: round;
}
.land-glow {
fill: #000;
fill-opacity: .1;
filter: url(#glow);
}
.land-fill {
fill: #fff;
}
.state-boundary {
stroke: #ccc;
stroke-width: .70px;
}
.county-boundary {
stroke: #ccc;
stroke-width: .35px;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="topojson.js"></script>
<script>
var width = 960,
height = 500;
var color = d3.scale.category20c();
var path = d3.geo.path();
var svg = d3.select("body").append("svg")
.attr("viewBox", "0 0 " + width + " " + height)
.attr("width", width)
.attr("height", height);
svg.append("filter")
.attr("id", "glow")
.append("feGaussianBlur")
.attr("stdDeviation", 5);
d3.json("../4090846/us.json", function(error, topology) {
svg.append("path") // translucent outer glow
.datum(topojson.object(topology, topology.objects.land))
.attr("d", path)
.attr("class", "land-glow");
svg.append("path") // solid white fill
.datum(topojson.object(topology, topology.objects.land))
.attr("d", path)
.attr("class", "land-fill");
svg.append("path") // thin grey stroke for internal county boundaries
.datum(topojson.mesh(topology, topology.objects.counties, true))
.attr("d", path)
.attr("class", "county-boundary");
svg.append("path") // thick grey stroke for state boundaries
.datum(topojson.mesh(topology, topology.objects.states))
.attr("d", path)
.attr("class", "state-boundary");
});
</script>
topojson = (function() {
function merge(topology, arcs) {
var arcsByEnd = {},
fragmentByStart = {},
fragmentByEnd = {};
arcs.forEach(function(i) {
var e = ends(i);
(arcsByEnd[e[0]] || (arcsByEnd[e[0]] = [])).push(i);
(arcsByEnd[e[1]] || (arcsByEnd[e[1]] = [])).push(~i);
});
arcs.forEach(function(i) {
var e = ends(i),
start = e[0],
end = e[1],
f, g;
if (f = fragmentByEnd[start]) {
delete fragmentByEnd[f.end];
f.push(i);
f.end = end;
if (g = fragmentByStart[end]) {
delete fragmentByStart[g.start];
var fg = f.concat(g);
fragmentByStart[fg.start = f.start] = fragmentByEnd[fg.end = g.end] = fg;
} else if (g = fragmentByEnd[end]) {
delete fragmentByStart[g.start];
delete fragmentByEnd[g.end];
var fg = f.concat(g.map(function(i) { return ~i; }).reverse());
fragmentByStart[fg.start = f.start] = fragmentByEnd[fg.end = g.start] = fg;
} else {
fragmentByStart[f.start] = fragmentByEnd[f.end] = f;
}
} else if (f = fragmentByStart[end]) {
delete fragmentByStart[f.start];
f.unshift(i);
f.start = start;
if (g = fragmentByEnd[start]) {
delete fragmentByEnd[g.end];
var gf = g.concat(f);
fragmentByStart[gf.start = g.start] = fragmentByEnd[gf.end = f.end] = gf;
} else if (g = fragmentByStart[start]) {
delete fragmentByStart[g.start];
delete fragmentByEnd[g.end];
var gf = g.map(function(i) { return ~i; }).reverse().concat(f);
fragmentByStart[gf.start = g.end] = fragmentByEnd[gf.end = f.end] = gf;
} else {
fragmentByStart[f.start] = fragmentByEnd[f.end] = f;
}
} else if (f = fragmentByStart[start]) {
delete fragmentByStart[f.start];
f.unshift(~i);
f.start = end;
if (g = fragmentByEnd[end]) {
delete fragmentByEnd[g.end];
var gf = g.concat(f);
fragmentByStart[gf.start = g.start] = fragmentByEnd[gf.end = f.end] = gf;
} else if (g = fragmentByStart[end]) {
delete fragmentByStart[g.start];
delete fragmentByEnd[g.end];
var gf = g.map(function(i) { return ~i; }).reverse().concat(f);
fragmentByStart[gf.start = g.end] = fragmentByEnd[gf.end = f.end] = gf;
} else {
fragmentByStart[f.start] = fragmentByEnd[f.end] = f;
}
} else if (f = fragmentByEnd[end]) {
delete fragmentByEnd[f.end];
f.push(~i);
f.end = start;
if (g = fragmentByEnd[start]) {
delete fragmentByStart[g.start];
var fg = f.concat(g);
fragmentByStart[fg.start = f.start] = fragmentByEnd[fg.end = g.end] = fg;
} else if (g = fragmentByStart[start]) {
delete fragmentByStart[g.start];
delete fragmentByEnd[g.end];
var fg = f.concat(g.map(function(i) { return ~i; }).reverse());
fragmentByStart[fg.start = f.start] = fragmentByEnd[fg.end = g.start] = fg;
} else {
fragmentByStart[f.start] = fragmentByEnd[f.end] = f;
}
} else {
f = [i];
fragmentByStart[f.start = start] = fragmentByEnd[f.end = end] = f;
}
});
function ends(i) {
var arc = topology.arcs[i], p0 = arc[0], p1 = [0, 0];
arc.forEach(function(dp) { p1[0] += dp[0], p1[1] += dp[1]; });
return [p0, p1];
}
var fragments = [];
for (var k in fragmentByEnd) fragments.push(fragmentByEnd[k]);
return fragments;
}
function mesh(topology, o, internalOnly) {
var arcs = [];
if (arguments.length > 1) {
var countByArc = [];
if (arguments.length < 3) internalOnly = false;
function arc(i) {
if (i < 0) i = ~i;
countByArc[i] = (countByArc[i] || 0) + 1;
}
function line(arcs) {
arcs.forEach(arc);
}
function polygon(arcs) {
arcs.forEach(line);
}
function geometry(o) {
geometryType[o.type](o.arcs);
}
var geometryType = {
LineString: line,
MultiLineString: polygon,
Polygon: polygon,
MultiPolygon: function(arcs) { arcs.forEach(polygon); }
};
o.type === "GeometryCollection"
? o.geometries.forEach(geometry)
: geometry(o);
for (var i in countByArc) if (countByArc[i] > internalOnly) arcs.push([i]);
} else {
for (var i = 0, n = topology.arcs.length; i < n; ++i) arcs.push([i]);
}
return object(topology, {type: "MultiLineString", arcs: merge(topology, arcs)});
}
function object(topology, o) {
var tf = topology.transform,
kx = tf.scale[0],
ky = tf.scale[1],
dx = tf.translate[0],
dy = tf.translate[1],
arcs = topology.arcs;
function arc(i, points) {
if (points.length) points.pop();
for (var a = arcs[i < 0 ? ~i : i], k = 0, n = a.length, x = 0, y = 0, p; k < n; ++k) points.push([
(x += (p = a[k])[0]) * kx + dx,
(y += p[1]) * ky + dy
]);
if (i < 0) reverse(points, n);
}
function line(arcs) {
var points = [];
for (var i = 0, n = arcs.length; i < n; ++i) arc(arcs[i], points);
return points;
}
function polygon(arcs) {
return arcs.map(line);
}
function geometry(o) {
o = Object.create(o);
o.coordinates = geometryType[o.type](o.arcs);
return o;
}
var geometryType = {
LineString: line,
MultiLineString: polygon,
Polygon: polygon,
MultiPolygon: function(arcs) { return arcs.map(polygon); }
};
return o.type === "GeometryCollection"
? (o = Object.create(o), o.geometries = o.geometries.map(geometry), o)
: geometry(o);
}
function reverse(array, n) {
var t, j = array.length, i = j - n; while (i < --j) t = array[i], array[i++] = array[j], array[j] = t;
}
return {
version: "0.0.3",
mesh: mesh,
object: object
};
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment