Skip to content

Instantly share code, notes, and snippets.

@huroh
Last active December 10, 2015 19:59
Show Gist options
  • Save huroh/4485328 to your computer and use it in GitHub Desktop.
Save huroh/4485328 to your computer and use it in GitHub Desktop.
Updating d3 overlays on polymaps without reloading paths

Fixed 2013-01-10: Thanks to Alexander Skaburskis: https://groups.google.com/forum/?fromgroups=#!topic/d3-js/lm3nmWyEXwk


I'm using polymaps and D3 to overlay a large geoJSON object (created from a shapefile) on a map. d3 is doing the overlay, not polymaps.

Normally, the paths of the overlayed regions would be re-loaded and projected on each move and zoom - i.e. update the path generator and the projection and re-generate each path on move. Since this is a very large set of objects, this takes a while.

To get nice and fluid updates on move and zoom, I thought I would do the following - on move/zoom, the whole group of region paths are not reloaded, rather they are scaled and translated along the x and y axes as necessary.

I've got the translation part down. I use findMapBounds() to get the coordinates of the upper left point of the group of paths, and translate by the difference with respect to the coordinates on initial load. it works fine.

However on zoom, it's not so easy. Getting the right factor by which to scale the group of paths is fine: I just calculate the ratio of the current path group bounds size to the initial size. The problem is that I also need to compensate for the translation when the map is zoomed. When the scale changes, I don't know how to translate to get the overlayed paths into the right spot.

Does anyone have an idea how to translate an object after a zoom? It seems like with that fixed, this method could prevent a lot of unnecessary path regeneration.

[note: small subset of data used in example dataset uk_lads.json]

<!DOCTYPE html>
<html>
<head>
<style type="text/css">
@import url("http://polymaps.org/style.css");
html, body {
height: 100%;
background: #E6E6E6;
margin: 0;
font: 10px sans-serif;
}
svg {
display: block;
}
circle {
stroke: black;
fill: brown;
fill-opacity: .5;
}
#map {
width: 960px;
height: 500px;
}
</style>
</head>
<body>
<div id="map"></div>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="http://polymaps.org/polymaps.min.js"></script>
<script type="text/javascript">
var po = org.polymaps;
var initialZoom = 5;
// Create the map object, add it to #map…
var map = po.map()
.container(d3.select("#map").append("svg:svg").node())
.zoom(initialZoom)
.center({lat:55, lon:-3})
.add(po.interact());
// Add the CloudMade image tiles as a base layer…
map.add(po.image()
.url(po.url("http://{S}tile.cloudmade.com"
+ "/677444bfa43f4b1a99a366b991353d2d" // http://cloudmade.com/register
+ "/998/256/{Z}/{X}/{Y}.png")
.hosts(["a.", "b.", "c.", ""])));
// Add the compass control on top.
map.add(po.compass()
.pan("none"));
// sync up the d3 projection with the polymaps coords system
var pt = map.locationPoint({lat: 0, lon: 0});
var projection = d3.geo.mercator()
.translate([pt.x, pt.y])
.scale(Math.pow(2, map.zoom()) * 256)
var path = d3.geo.path()
.projection(projection)
///////////////
// map.add(po.geoJson()
// .url("http://localhost:8888/projects/ukMap/uk_lads.json")
// .id("lad"));
///////////////
// scaffolding for the map
var ladsLayer = d3.select('#map svg')
.insert("svg:g", ".compass")
.attr('class', 'lads')
var mapBounds = [];
var prevBounds = [];
var shapesBounds = [];
function findMapBounds(bounds) {
var topleft = map.locationPoint({lat:bounds[1][1], lon: bounds[0][0]})
var bottomright = map.locationPoint({lat:bounds[0][1], lon: bounds[1][0]})
var w = bottomright.x - topleft.x;
var h = bottomright.y - topleft.y;
return {
topleft: topleft,
bottomright: bottomright,
width : w,
height : h
};
}
d3.json('uk_lads.json', function(data) {
shapesBounds = d3.geo.bounds(data);
mapBounds = findMapBounds(shapesBounds);
prevBounds = mapBounds;
// boudns = [[left lon, bottom lat], [right lon, top lat]]
// ladsLayer.append('svg:rect')
// .attr('height', mapBounds.height)
// .attr('width', mapBounds.width)
// .style('fill', 'none')
// .style('stroke', 'red')
// .attr('x', 0)
// .attr('y', 0)
//ladsLayer.attr('transform', transform)
var lads = ladsLayer.selectAll('path').data(data.features);
lads.enter()
.append('path')
.attr('class', 'district')
.attr('d', path)
.attr('id', function(d) {
return d.properties.LAD11CD;
})
})
/*
<path d="...", transform="translate(x, y) scale(y)"/>
*/
function transform(d, i) {
var newBounds = findMapBounds(shapesBounds);
var scale = newBounds.width/mapBounds.width;
var tranx = newBounds.topleft.x - (mapBounds.topleft.x * scale);
var trany = newBounds.topleft.y - (mapBounds.topleft.y * scale);
var translate = 'translate(' + tranx + ',' + trany + ')';
scale = 'scale(' + scale + ')';
var transformstring = translate + ' ' + scale;
console.log(transformstring);
return transformstring
}
function update() {
// update the projection
// console.log('updating projection')
// 1 get the center from polymaps
var pt = map.locationPoint({lat: 0, lon: 0});
// projection
// .translate([pt.x, pt.y])
// .scale(Math.pow(2, map.zoom()) * 256)
if (shapesBounds.length > 0) {
ladsLayer.attr('transform', transform)
}
// .attr('height', newBounds.height)
// .attr('width', newBounds.width)
// .attr('x', newBounds.topleft.x)
// .attr('y', newBounds.topleft.y)
// path.projection(projection)
// ladsLayer.
// ladsLayer.selectAll('path.district')
// .attr('d', path)
mapBounds.prevZoom = map.zoom();
}
map.on('resize', update);
map.on('move', update);
</script>
</body>
</html>
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment