block by emeeks 6147081

TopoJSON, geo.tile() and zoom

Full Screen

Using d3.geo.tile to display raster image tiles underneath some TopoJSON vectors, and d3.behavior.zoom for pan & zoom. This version adjusts the transform and stroke-width to update the displayed vector data efficiently. You can instead reproject interactively, but changing the transform is typically much faster.

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<style>

body {
  margin: 0;
}

path {
  fill: none;
  stroke: red;
  stroke-linejoin: round;
  stroke-width: 1.5px;
}

</style>
<body>
<div id="controlbar">
  <button onclick="colorBy('e')" >Expense</button>
  <button onclick="colorBy('s')" >Speed</button>
  <button onclick="colorByType()" >Type</button>
</div>
<div id="vizcontainer">
</div>
<footer>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//d3js.org/d3.geo.tile.v0.min.js"></script>
<script src="//d3js.org/topojson.v0.min.js"></script>
<script>

var typeHash = {road: "brown", overseas: "green", coastal: "lightgreen", upstream: "blue", downstream: "blue", ferry: "purple"}

var width = Math.max(960),
    height = Math.max(500);

var tile = d3.geo.tile()
    .size([width, height]);

var projection = d3.geo.mercator()
    .scale((1 << 12) / 2 / Math.PI)
    .translate([width / 2, height / 2]);

    var projection2 = d3.geo.mercator()
    .scale((1 << 11) / 2 / Math.PI)
    .translate([width / 2, height / 2]);

var center = projection([12, 42]);

var path = d3.geo.path()
    .projection(projection);

var zoom = d3.behavior.zoom()
    .scale(projection.scale() * 2 * Math.PI)
    .scaleExtent([1 << 12, 1 << 17])
    .translate([width - center[0], height - center[1]])
    .on("zoom", zoomed);

// With the center computed, now adjust the projection such that
// it uses the zoom behavior’s translate and scale.
projection
    .scale(1 / 2 / Math.PI)
    .translate([0, 0]);

var svg = d3.select("#vizcontainer").append("svg")
    .attr("width", width)
    .attr("height", height);

var raster = svg.append("g");

var vector = svg.append("path");

colorRamp=d3.scale.linear().domain([0,1,5,10]).range(["green", "yellow","orange","red"])


d3.json("july_topo.json", function(error, routes) {
  exposedroutes = routes;
  svg.call(zoom);
/*  vector
  .attr("d", path(topojson.mesh(routes, routes.objects.july)))
  .style("stroke", "lightgray");
 */

  svg.selectAll(".routes")
  .data(topojson.object(routes, routes.objects.july2).geometries)
  .enter()
  .append("path")
  .attr("class", "routes")
  .attr("d", path)
  .style("stroke-width", 2)
  .style("stroke-opacity", .75)
  .style("stroke", function(d,i) {return colorRamp(d.properties.e)})
  .on("mouseover", function(d) {
  d3.select(this).transition().duration(500).style("stroke-opacity", 1);
	})
  .on("mouseout", function() {
	d3.select(this).transition().duration(500).style("stroke-opacity", .5);
	});;


d3.csv("osites.csv", function(error, sites) {
  exposedsites = sites;
  var sitesG = svg.append("g").attr("id","sitesG")
        .attr("transform", "translate(" + zoom.translate() + ")scale(" + zoom.scale() + ")");
  var osites = sitesG.selectAll(".sites")
  .data(sites)
  .enter()
  .append("g")
  .attr("transform", function(d) {return "translate(" + projection([d.x,d.y]) + ")scale(" + projection.scale() + ")"})
  
  osites
  .append("circle")
  .attr("r", 15 / zoom.scale())
  .attr("class", "sitecirc")
 
})


  zoomed();
});

function zoomed() {
  var tiles = tile
      .scale(zoom.scale())
      .translate(zoom.translate())
      ();
      
//  projection.scale(zoom.scale())
//   .translate(zoom.translate())

  vector
      .attr("transform", "translate(" + zoom.translate() + ")scale(" + zoom.scale() + ")")
      .style("stroke-width", 1 / zoom.scale());

  d3.selectAll(".routes")
      .attr("transform", "translate(" + zoom.translate() + ")scale(" + zoom.scale() + ")")
      .style("stroke-width", 2 / zoom.scale());
  
//  d3.selectAll(".sites")
//  .attr("transform", "translate(" + zoom.translate() + ")scale(" + zoom.scale() + ")") 
//  .attr("transform", function(d) {return "translate(" + projection(d.coordinates) + ")scale("+projection.scale()+")"})

  d3.select("#sitesG")
      .attr("transform", "translate(" + zoom.translate() + ")scale(" + zoom.scale() + ")");
  
  d3.selectAll(".sitecirc")
      .attr("r", 15 / zoom.scale());

      
  var image = raster
      .attr("transform", "scale(" + tiles.scale + ")translate(" + tiles.translate + ")")
    .selectAll("image")
      .data(tiles, function(d) { return d; });

  image.exit()
      .remove();

  image.enter().append("image")
      .attr("xlink:href", function(d) { return "//" + ["a", "b", "c", "d"][Math.random() * 4 | 0] + ".tiles.mapbox.com/v3/elijahmeeks.map-zm593ocx/" + d[2] + "/" + d[0] + "/" + d[1] + ".png"; })
      .attr("width", 1)
      .attr("height", 1)
      .attr("x", function(d) { return d[0]; })
      .attr("y", function(d) { return d[1]; });
}

function colorBy(attribute) {
  d3.selectAll(".routes")
  .transition().duration(500).style("stroke", function(d) {return colorRamp(d.properties[attribute])})
}

function colorByType() {
  d3.selectAll(".routes")
  .transition().duration(500).style("stroke", function(d) {return typeHash[d.properties.t]}) 
}

</script>
</footer>
</body>