Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Town/county map using d3 and TopoJSON

This is a demonstration of how to create a combination town/county map from a shapefile using TopoJSON and d3.js.

It includes a simplified version of the code used for the Massachusetts special Senate election results on The Huffington Post.

Get the data

Download a shapefile of Massachusetts towns from the state's GIS site:

wget http://wsgw.mass.gov/data/gispub/shape/census2000/towns/census2000towns_poly.exe

Unzip the file:

unzip census2000towns_poly.exe

Project the shapefile

We then create a new shapefile with projected coordinates, using an appropriate projection for Massachusetts. Read more here about projected TopoJSON

ogr2ogr -f 'ESRI Shapefile' -t_srs 'EPSG:3585' towns-projected.shp census2000towns_poly.shp

Convert the shapefile to TopoJSON

We already know the dimensions we want for our map: 615x375. Since we're using a projected shapefile, we can specify the width and height when we generate the TopoJSON, and then do not have to reproject and resize the map in the browser.

We also need to know which county each town is in, so we can show a map of counties as well as towns. The shapefile doesn't have county information, though, so we need to add it. We'll do that by using an external properties file that contains a list of town IDs (which do appear in the shapefile) and the FIPS code for the town's county.

We're also going to simplify the town shapes.

topojson --width=615 --height=373 -s 10 --id-property +TOWN_ID -e county_towns.csv -p town=town,fips=fips -o ma-towns.topojson towns=towns-projected.shp

That gives us a 73K file, ma-towns.topojson. You can reduce file size further by changing the simplification threshold.

Displaying the TopoJSON

See the code below for the basics of displaying the TopoJSON file and toggling between showing towns and counties.

id town fips
1 ABINGTON 25023
2 ACTON 25017
3 ACUSHNET 25005
4 ADAMS 25003
13 ASHFIELD 25011
5 AGAWAM 25013
6 ALFORD 25003
7 AMESBURY 25009
8 AMHERST 25015
14 ASHLAND 25017
9 ANDOVER 25009
10 ARLINGTON 25017
11 ASHBURNHAM 25027
12 ASHBY 25017
45 BROOKFIELD 25027
15 ATHOL 25027
16 ATTLEBORO 25005
17 AUBURN 25027
18 AVON 25021
19 AYER 25017
20 BARNSTABLE 25001
21 BARRE 25027
22 BECKET 25003
23 BEDFORD 25017
24 BELCHERTOWN 25015
70 DALTON 25003
25 BELLINGHAM 25021
26 BELMONT 25017
30 BEVERLY 25009
34 BOLTON 25027
27 BERKLEY 25005
28 BERLIN 25027
29 BERNARDSTON 25011
35 BOSTON 25025
36 BOURNE 25001
200 NEW ASHFORD 25003
31 BILLERICA 25017
32 BLACKSTONE 25027
33 BLANDFORD 25013
37 BOXBOROUGH 25017
38 BOXFORD 25009
39 BOYLSTON 25027
40 BRAINTREE 25021
43 BRIMFIELD 25013
41 BREWSTER 25001
42 BRIDGEWATER 25023
44 BROCKTON 25023
46 BROOKLINE 25021
47 BUCKLAND 25011
48 BURLINGTON 25017
49 CAMBRIDGE 25017
50 CANTON 25021
51 CARLISLE 25017
52 CARVER 25023
53 CHARLEMONT 25011
54 CHARLTON 25027
55 CHATHAM 25001
56 CHELMSFORD 25017
57 CHELSEA 25025
58 CHESHIRE 25003
59 CHESTER 25013
60 CHESTERFIELD 25015
61 CHICOPEE 25013
112 GRANVILLE 25013
62 CHILMARK 25007
63 CLARKSBURG 25003
64 CLINTON 25027
65 COHASSET 25021
66 COLRAIN 25011
67 CONCORD 25017
68 CONWAY 25011
69 CUMMINGTON 25015
71 DANVERS 25009
72 DARTMOUTH 25005
73 DEDHAM 25021
74 DEERFIELD 25011
120 HAMPDEN 25013
75 DENNIS 25001
76 DIGHTON 25005
77 DOUGLAS 25027
78 DOVER 25021
79 DRACUT 25017
80 DUDLEY 25027
81 DUNSTABLE 25017
82 DUXBURY 25023
83 EAST BRIDGEWATER 25023
84 EAST BROOKFIELD 25027
85 EAST LONGMEADOW 25013
86 EASTHAM 25001
87 EASTHAMPTON 25015
88 EASTON 25005
89 EDGARTOWN 25007
90 EGREMONT 25003
91 ERVING 25011
92 ESSEX 25009
93 EVERETT 25017
94 FAIRHAVEN 25005
121 HANCOCK 25003
95 FALL RIVER 25005
96 FALMOUTH 25001
97 FITCHBURG 25027
98 FLORIDA 25003
99 FOXBOROUGH 25021
100 FRAMINGHAM 25017
101 FRANKLIN 25021
102 FREETOWN 25005
113 GREAT BARRINGTON 25003
103 GARDNER 25027
104 AQUINNAH 25007
105 GEORGETOWN 25009
116 GROVELAND 25009
106 GILL 25011
107 GLOUCESTER 25009
108 GOSHEN 25015
109 GOSNOLD 25007
110 GRAFTON 25027
111 GRANBY 25015
114 GREENFIELD 25011
115 GROTON 25017
117 HADLEY 25015
118 HALIFAX 25023
119 HAMILTON 25009
122 HANOVER 25023
123 HANSON 25023
128 HAVERHILL 25009
129 HAWLEY 25011
124 HARDWICK 25027
125 HARVARD 25027
126 HARWICH 25001
127 HATFIELD 25015
130 HEATH 25011
131 HINGHAM 25023
132 HINSDALE 25003
133 HOLBROOK 25021
134 HOLDEN 25027
135 HOLLAND 25013
140 HUBBARDSTON 25027
136 HOLLISTON 25017
137 HOLYOKE 25013
138 HOPEDALE 25027
139 HOPKINTON 25017
141 HUDSON 25017
142 HULL 25023
143 HUNTINGTON 25015
144 IPSWICH 25009
145 KINGSTON 25023
148 LANESBOROUGH 25003
146 LAKEVILLE 25023
147 LANCASTER 25027
149 LAWRENCE 25009
150 LEE 25003
151 LEICESTER 25027
152 LENOX 25003
153 LEOMINSTER 25027
154 LEVERETT 25011
155 LEXINGTON 25017
156 LEYDEN 25011
157 LINCOLN 25017
158 LITTLETON 25017
159 LONGMEADOW 25013
160 LOWELL 25017
161 LUDLOW 25013
162 LUNENBURG 25027
163 LYNN 25009
164 LYNNFIELD 25009
165 MALDEN 25017
166 MANCHESTER 25009
167 MANSFIELD 25005
168 MARBLEHEAD 25009
169 MARION 25023
170 MARLBOROUGH 25017
171 MARSHFIELD 25023
178 MELROSE 25017
172 MASHPEE 25001
173 MATTAPOISETT 25023
174 MAYNARD 25017
175 MEDFIELD 25021
176 MEDFORD 25017
177 MEDWAY 25021
179 MENDON 25027
180 MERRIMAC 25009
181 METHUEN 25009
183 MIDDLEFIELD 25015
182 MIDDLEBOROUGH 25023
184 MIDDLETON 25009
185 MILFORD 25027
186 MILLBURY 25027
190 MONROE 25011
187 MILLIS 25021
188 MILLVILLE 25027
189 MILTON 25021
198 NATICK 25017
191 MONSON 25013
192 MONTAGUE 25011
193 MONTEREY 25003
194 MONTGOMERY 25013
195 MOUNT WASHINGTON 25003
196 NAHANT 25009
197 NANTUCKET 25019
199 NEEDHAM 25021
201 NEW BEDFORD 25005
202 NEW BRAINTREE 25027
203 NEW MARLBOROUGH 25003
204 NEW SALEM 25011
205 NEWBURY 25009
206 NEWBURYPORT 25009
207 NEWTON 25017
208 NORFOLK 25021
209 NORTH ADAMS 25003
210 NORTH ANDOVER 25009
211 NORTH ATTLEBOROUGH 25005
212 NORTH BROOKFIELD 25027
213 NORTH READING 25017
214 NORTHAMPTON 25015
215 NORTHBOROUGH 25027
216 NORTHBRIDGE 25027
217 NORTHFIELD 25011
218 NORTON 25005
223 ORANGE 25011
224 ORLEANS 25001
219 NORWELL 25023
220 NORWOOD 25021
225 OTIS 25003
221 OAK BLUFFS 25007
222 OAKHAM 25027
226 OXFORD 25027
227 PALMER 25013
228 PAXTON 25027
229 PEABODY 25009
230 PELHAM 25015
231 PEMBROKE 25023
232 PEPPERELL 25017
233 PERU 25003
234 PETERSHAM 25027
235 PHILLIPSTON 25027
236 PITTSFIELD 25003
237 PLAINFIELD 25015
238 PLAINVILLE 25021
239 PLYMOUTH 25023
240 PLYMPTON 25023
241 PRINCETON 25027
242 PROVINCETOWN 25001
257 RUTLAND 25027
243 QUINCY 25021
244 RANDOLPH 25021
260 SANDISFIELD 25003
245 RAYNHAM 25005
246 READING 25017
247 REHOBOTH 25005
248 REVERE 25025
249 RICHMOND 25003
250 ROCHESTER 25023
251 ROCKLAND 25023
252 ROCKPORT 25009
253 ROWE 25011
254 ROWLEY 25009
255 ROYALSTON 25027
256 RUSSELL 25013
258 SALEM 25009
259 SALISBURY 25009
261 SANDWICH 25001
262 SAUGUS 25009
263 SAVOY 25003
264 SCITUATE 25023
265 SEEKONK 25005
266 SHARON 25021
285 STOUGHTON 25021
267 SHEFFIELD 25003
268 SHELBURNE 25011
269 SHERBORN 25017
270 SHIRLEY 25017
271 SHREWSBURY 25027
272 SHUTESBURY 25011
286 STOW 25017
273 SOMERSET 25005
274 SOMERVILLE 25017
275 SOUTH HADLEY 25015
276 SOUTHAMPTON 25015
277 SOUTHBOROUGH 25027
278 SOUTHBRIDGE 25027
279 SOUTHWICK 25013
280 SPENCER 25027
281 SPRINGFIELD 25013
282 STERLING 25027
283 STOCKBRIDGE 25003
284 STONEHAM 25017
287 STURBRIDGE 25027
288 SUDBURY 25017
289 SUNDERLAND 25011
317 WELLESLEY 25021
290 SUTTON 25027
291 SWAMPSCOTT 25009
292 SWANSEA 25005
331 WESTHAMPTON 25015
293 TAUNTON 25005
346 WINTHROP 25025
294 TEMPLETON 25027
295 TEWKSBURY 25017
301 TYNGSBOROUGH 25017
296 TISBURY 25007
297 TOLLAND 25013
302 TYRINGHAM 25003
298 TOPSFIELD 25009
299 TOWNSEND 25017
300 TRURO 25001
303 UPTON 25027
304 UXBRIDGE 25027
305 WAKEFIELD 25017
306 WALES 25013
307 WALPOLE 25021
308 WALTHAM 25017
312 WARWICK 25011
313 WASHINGTON 25003
309 WARE 25015
310 WAREHAM 25023
311 WARREN 25027
314 WATERTOWN 25017
315 WAYLAND 25017
316 WEBSTER 25027
318 WELLFLEET 25001
319 WENDELL 25011
320 WENHAM 25009
321 WEST BOYLSTON 25027
322 WEST BRIDGEWATER 25023
323 WEST BROOKFIELD 25027
324 WEST NEWBURY 25009
325 WEST SPRINGFIELD 25013
326 WEST STOCKBRIDGE 25003
327 WEST TISBURY 25007
328 WESTBOROUGH 25027
329 WESTFIELD 25013
330 WESTFORD 25017
332 WESTMINSTER 25027
333 WESTON 25017
334 WESTPORT 25005
335 WESTWOOD 25021
336 WEYMOUTH 25021
337 WHATELY 25011
338 WHITMAN 25023
339 WILBRAHAM 25013
340 WILLIAMSBURG 25015
341 WILLIAMSTOWN 25003
342 WILMINGTON 25017
343 WINCHENDON 25027
344 WINCHESTER 25017
345 WINDSOR 25003
347 WOBURN 25017
348 WORCESTER 25027
349 WORTHINGTON 25015
350 WRENTHAM 25021
351 YARMOUTH 25001
<!doctype html>
<meta charset="utf-8">
<html>
<head>
<title>Massachusetts town/county map</title>
<style type="text/css">
#map {
width: 615px;
height: 375px;
}
</style>
</head>
<body>
<button id="toggle">Toggle</button>
<div id="map"></div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.js"></script>
<script src="main.js"></script>
</body>
</html>
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
;(function() {
var map;
function Map(topology) {
// Convert the topojson to geojson
var geojson = topojson.feature(topology, topology.objects.towns),
// Since we're using projected TopoJSON, we use a null projection here.
path = d3.geo.path().projection(null),
// Use topojson.mesh to create a path representing county outlines.
countyMesh = topojson.mesh(topology, topology.objects.towns, function(a, b) {
return a.properties.fips !== b.properties.fips;
}),
svg = d3.select("#map").append("svg")
.attr({
width: 615,
height: 375
}),
g = svg.append("g").attr({"class": "g-town"}),
colorScale = d3.scale.category20b(),
// Add town paths
town = g.selectAll("path.town").data(geojson.features)
.enter().append("path")
.attr({
"class": "town",
d: path
})
.style({
fill: function(d, i) {
return colorScale(Math.floor(Math.random()*1000));
},
stroke: "white"
}),
// Add county path and set its opacity to 0,
// since we don't want to show it initially.
county = g.append("path").datum(countyMesh)
.attr({
"class": "county",
d: path
})
.style({
opacity: 0,
stroke: "white",
"stroke-width": 2,
fill: "none"
}),
currentState = "town";
// To show the county outlines, we
// transition the town shapes to have
// a consistent stroke, and fill, in effect hiding
// them. We also transition the county
// path to be visible.
this.showCounties = function() {
var countyColors = {};
// Since we're using a mesh for the county outlines,
// it doesn't work well to add the fill to the county
// path. Instead, we'll set a consistent fill on all
// the towns in each county, to give the effect of
// filling in the county.
town.transition()
.duration(600)
.style({
fill: function(d, i) {
if (!countyColors[d.properties.fips]) {
countyColors[d.properties.fips] = colorScale(Math.floor(Math.random()*1000));
}
return countyColors[d.properties.fips];
},
stroke: function(d, i) {
return countyColors[d.properties.fips];
}
});
county.transition()
.duration(600)
.style({
opacity: 1
})
};
// To show the town outlines, we transition
// back to our original state.
this.showTowns = function() {
town.transition()
.duration(600)
.style({
stroke: "white",
fill: function(d, i) {
return colorScale(Math.floor(Math.random()*1000));
}
});
county.transition()
.duration(600)
.style({
opacity: 0
})
};
this.toggle = function() {
if (currentState === "town") {
this.showCounties();
currentState = "county";
} else {
this.showTowns();
currentState = "town";
}
};
}
d3.json("ma-towns.topojson", function(err, topology) {
map = new Map(topology);
});
d3.select("#toggle").on("click", function() {
map.toggle();
});
}());
@MissyG

This comment has been minimized.

Copy link

@MissyG MissyG commented Sep 3, 2016

Thanks for this tutorial. I am looking to add the roller over stats too. Can you point me to a good source for adding a pop up label to this map.

@flooie

This comment has been minimized.

Copy link

@flooie flooie commented Oct 11, 2016

Yes, Thanks

@flooie

This comment has been minimized.

Copy link

@flooie flooie commented Nov 30, 2016

If you don't mind, If you use projection NULL how do you calculate the location of cities on a map?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment