Skip to content

Instantly share code, notes, and snippets.

@sdjacobs
Last active January 26, 2018 21:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sdjacobs/9ce5fadce234497dc592 to your computer and use it in GitHub Desktop.
Save sdjacobs/9ce5fadce234497dc592 to your computer and use it in GitHub Desktop.
D3 mapzoom: Senior centers in Chicago

This is an example of how to render SVG elements on top of a zoomable map using D3. The dots on the map represent the locations of senior centers in Chicago, from publically available City of Chicago data.

To do this in a reusable way, I created a plugin called d3.mapzoom. It is inspired by d3.carto.map. My design goal to allow me to write idiomatic D3 code while keeping things small and understandable. Essentially all the plugin does is keep a d3.geo.projection and a d3.zoom in sync with one another. When the zoom event is triggered, the projection is updated to match the new position, and all layers are updated. mapzoom is agnostic to the underlying web technology, like D3 itself. Well, not quite: the function addTileLayer creates an SVG tiled image layer from a map tile provider, a la "Zoomable Map Tiles". This was done because this problem is pretty tough to work out, but very reusable.

In the future, I might make the tile drawing a little smoother (no need to delete all the images on every redraw), and/or add support for drawing tiles on canvas.

d3.mapzoom = function() {
var center = [0,0]
var scale = 10000
var projection = undefined,
zoom = undefined,
tile = undefined
var layers = []
function map(svg) {
projection = d3.geo.mercator()
.center(center)
.scale(scale)
zoom = d3.behavior.zoom()
.translate(projection([0,0]))
.scale(projection.scale() * 2 * Math.PI)
.on("zoom", redraw)
zoom(svg)
}
function redraw() {
projection.scale(zoom.scale() / (2 * Math.PI));
var c = projection.center(), t = projection.invert(zoom.translate())
var i = c[0] - t[0]
var j = c[1] - t[1]
projection.center([i, j])
layers.forEach(function(layer) {
layer();
});
}
map.center = function(_) {
if (!arguments.length)
return center;
else {
center = _;
}
return map;
}
map.scale = function(_) {
if (!arguments.length)
return scale;
else {
scale = _;
}
return map;
}
map.projection = function(_) {
if (!arguments.length)
return projection;
else
projection = _;
return map;
}
map.zoom = function(_) {
if (!arguments.length)
return zoom;
else
zoom = _;
return map;
}
map.tile = function(_) {
if (!arguments.length)
return tile;
else
tile = _;
return map;
}
map.layers = function(_) {
if (!arguments.length)
return layers;
else
laters = _;
return map;
}
map.addLayer = function(_) {
layers.push(_)
redraw()
}
map.addTileLayer = function(frame, url, prefixes) {
var tile = d3.geo.tile()
.zoomDelta((window.devicePixelRatio || 1) - .5)
function draw() {
var tiles = tile
.scale(zoom.scale())
.translate(zoom.translate())()
frame.selectAll('*').remove()
frame
.selectAll("image")
.data(tiles)
.enter().append("image")
.attr("xlink:href", function(d) { return "http://" + prefixes[Math.random() * prefixes.length | 0] + "." + url + d[2] + "/" + d[0] + "/" + d[1] + ".png"; })
.attr("width", Math.round(tiles.scale))
.attr("height", Math.round(tiles.scale))
.attr("x", function(d) { return Math.round((d[0] + tiles.translate[0]) * tiles.scale); })
.attr("y", function(d) { return Math.round((d[1] + tiles.translate[1]) * tiles.scale); })
}
layers.push(draw)
redraw()
}
return map
}
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/d3.geo.tile.v0.min.js"></script>
<script src="d3.mapzoom.js"></script>
<script>
var width = 900,
height = 600;
var center = [-87.6847, 41.8550], scale = 65000;
var mapzoom = d3.mapzoom()
.center(center)
.scale(scale)
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.call(mapzoom);
var frame = svg.append("g")
mapzoom.addTileLayer(frame, "tile.openstreetmap.fr/hot/", "ab");
var projection = mapzoom.projection()
d3.csv("Senior_Centers.csv", function(data) {
data.forEach(function(d) {
/*
* "LOCATION" is of the form:
* "2019 W. Lawrence Avenue
* Chicago, IL 60625
* (41.96872254686461, -87.68019238203728)"
*/
d.coord = d.LOCATION.split('\n')[2]
.match(/-?[\.\d]+/g)
.map(parseFloat)
.reverse();
});
var circles = svg.append("g")
.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("r", 5)
.attr("fill", "red")
circles
.append("title")
.text(function(d) { return d["SITE NAME"] + '\n' + d["PHONE"] })
mapzoom.addLayer(function() {
circles
.attr("transform", function(d) { return "translate(" + projection(d.coord) + ")" })
});
});
/* Include attribution: */
d3.select("body")
.append("div")
.text("Map data and imagery (c) Open Street Map");
</script>
<body>
PROGRAM SITE NAME HOURS OF OPERATION ADDRESS CITY STATE ZIP PHONE LOCATION
Regional Senior Center Northeast Mon - Fri 8:30 a.m. to 4:30 p.m. Sat 9:00 a.m. to 4:00 p.m. 2019 W. Lawrence Avenue Chicago IL 60625 312-744-0784 2019 W. Lawrence Avenue Chicago, IL 60625 (41.96872254686461, -87.68019238203728)
Regional Senior Center Southwest Mon - Fri 8:30 a.m. to 4:30 p.m. Sat 9:00 a.m. to 4:00 p.m. 6117 S. Kedzie Avenue Chicago IL 60629 312-747-0440 6117 S. Kedzie Avenue Chicago, IL 60629 (41.78212991978889, -87.70319764951621)
Regional Senior Center Northwest Mon - Fri 8:30 a.m. to 4:30 p.m. Sat - Sun 9:00 a.m. to 4:00 p.m 3160 N. Milwaukee Avenue Chicago IL 60618 312-744-6681 3160 N. Milwaukee Avenue Chicago, IL 60618 (41.938368983472756, -87.72268832479132)
Regional Senior Center Central West Mon - Fri 8:30 a.m. to 4:30 p.m. 2102 W. Ogden Avenue Chicago IL 60612 312-746-5300 2102 W. Ogden Avenue Chicago, IL 60612 (41.87104299405995, -87.67859824824583)
Regional Senior Center Southeast Mon - Fri 8:30 a.m. to 4:30 p.m. Sat - Sun 9:00 a.m. to 4:00 p.m 1767 E. 79th Street Chicago IL 60649 312-747-0189 1767 E. 79th Street Chicago, IL 60649 (41.751512943458266, -87.5819826459126)
Regional Senior Center Renaissance Court Mon - Fri 8:30 a.m. to 4:30 p.m. 78 E. Washington Street Chicago IL 60602 312-744-4550 78 E. Washington Street Chicago, IL 60602 (41.883854158623144, -87.62495524186401)
Satellite Senior Center Abbott Park Mon - Fri 8:30 a.m. to 4:30 p.m. 49 East 95th Street Chicago IL 60619 312-745-3493 49 East 95th Street Chicago, IL 60619 (41.721658859945016, -87.62236238854524)
Satellite Senior Center Edgewater Mon - Fri 8:30 a.m. to 4:30 p.m. 5917 N. Broadway Street Chicago IL 60660 312-744-4016 5917 N. Broadway Street Chicago, IL 60660 (41.98948336912841, -87.66014332799551)
Satellite Senior Center Englewood Mon - Fri 8:30 a.m. to 4:30 p.m. 653-657 W. 63rd Street Chicago IL 60621 312-745-3328 653-657 W. 63rd Street Chicago, IL 60621 (41.77992531402681, -87.63308324283993)
Satellite Senior Center West Town Mon - Fri 8:30 a.m. to 4:30 p.m. 1613 W. Chicago Avenue Chicago IL 60622 312-743-1016 1613 W. Chicago Avenue Chicago, IL 60622 (41.895752189070784, -87.6682665907951)
Satellite Senior Center Kelvyn Park Mon - Fri 8:30 a.m. to 4:30 p.m. 2715 N. Cicero Avenue Chicago IL 60639 312-744-4016 2715 N. Cicero Avenue Chicago, IL 60639 (41.92990843974359, -87.74606693954983)
Satellite Senior Center Auburn Gresham Mon - Fri 8:30 a.m. to 4:30 p.m. 1040 W. 79th Street Chicago IL 60620 312-745-4798 1040 W. 79th Street Chicago, IL 60620 (41.750577216156685, -87.65029061794029)
Satellite Senior Center Norwood Park Mon - Fri 8:30 a.m. to 4:30 p.m. 5801 N. Natoma Avenue Chicago IL 60631 312-744-4016 5801 N. Natoma Avenue Chicago, IL 60631 (41.98653177632919, -87.79412346217883)
Satellite Senior Center Garfield Ridge Mon - Fri 8:30 a.m. to 4:30 p.m. 5674-B S. Archer Avenue Chicago IL 60638 312-745-4250 5674-B S. Archer Avenue Chicago, IL 60638 (41.797226434618594, -87.75315471018392)
Satellite Senior Center Chatham Mon - Fri 8:30 a.m. to 4:30 p.m. 8300 S. Cottage Grove Avenue Chicago IL 60619 312-745-0385 8300 S. Cottage Grove Avenue Chicago, IL 60619 (41.743344084765305, -87.60553887925143)
Satellite Senior Center Austin Mon - Fri 8:30 a.m. to 4:30 p.m. 5071 W. Congress Parkway Chicago IL 60644 312-743-1538 5071 W. Congress Parkway Chicago, IL 60644 (41.87393384627249, -87.75205184121931)
Satellite Senior Center Truman College Computer Classes Only -Schedule varies 1145 W. Wilson Avenue Chicago IL 60640 312-744-4016 1145 W. Wilson Avenue Chicago, IL 60640 (41.96537172202582, -87.65901561549461)
Satellite Senior Center North Center Mon - Fri 8:30 a.m. to 4:30 p.m. 4040 N. Oakley Avenue Chicago IL 60618 312-745-4029 4040 N. Oakley Avenue Chicago, IL 60618 (41.954849869961514, -87.68608804614789)
Satellite Senior Center Portage Park Mon - Fri 8:30 a.m. to 4:30 p.m. 4100 N. Long Avenue Chicago IL 60641 312-744-9022 4100 N. Long Avenue Chicago, IL 60641 (41.95497573162156, -87.76210231986396)
Satellite Senior Center Pilsen Mon - Fri 8:30 a.m. to 4:30 p.m. 2021 S. Morgan Street Chicago IL 60608 312-743-0493 2021 S. Morgan Street Chicago, IL 60608 (41.855146, -87.651544)
Satellite Senior Center South Chicago Mon - Fri 8:30 a.m. to 4:30 p.m. 9233 S. Burley Chicago IL 60617 312-745-1282 9233 S. Burley Ave. Chicago, IL 60617 (41.72769331491184, -87.54550604728695)
@jan-pachon
Copy link

Hi,

I really love your code. Do you have an update that makes the tile drawing smoother?

Thanks!

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