block by emeeks 27b334407c6aa894a236

Ch. 7, Fig. 20 - D3.js in Action

Full Screen

This is the code for Chapter 7, Figure 20 from D3.js in Action that demonstrates how to use d3.geo.tile() to create maps using terrain tiles.

index.html

<html>
<head>
  <title>D3 in Action Chapter 7 - Example 10</title>
  <meta charset="utf-8" />
<script src="https://d3js.org/d3.geo.projection.v0.min.js" type="text/JavaScript"></script>
<script src="https://d3js.org/queue.v1.min.js" type="text/JavaScript"></script>
<script src="//d3js.org/colorbrewer.v1.min.js" type="text/JavaScript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.19/topojson.min.js"></script>
<script src="tile.js" type="text/JavaScript"></script>
</head>
<style>
  svg {
    height: 500px;
    width: 500px;
    border: 1px solid gray;
  }
  
  .countries {
    fill: red;
    fill-opacity: .5;
    stroke: black;
    stroke-width: 1px;
  }

</style>
<body>

<div id="viz">
  <svg>
  </svg>
</div>
</body>
  <footer>
    
<script>

var width = 500,
    height = 500;
    
  d3.select("svg").append("g").attr("id", "tiles");
  d3.select("svg").append("g").attr("id", "vectors");

    queue()
    .defer(d3.json, "world.topojson")
    .defer(d3.csv, "cities.csv")
    .await(function(error, file1, file2) { createMap(file1, file2); });
    
  function createMap(countries1, cities) {
    var countries = topojson.feature(countries1, countries1.objects.countries);

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

var projection = d3.geo.mercator()
    .scale(120)
    .translate([width / 2, height / 2]);

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

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

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

    d3.select("svg").call(zoom);
    redraw();
    
    featureSize = d3.extent(countries.features, function(d) {return geoPath.area(d)});
    countryColor = d3.scale.quantize().domain(featureSize).range(colorbrewer.Reds[7]);

    d3.select("#vectors").selectAll("path.countries").data(countries.features)
    .enter()
    .append("path")
    .attr("d", geoPath)
    .attr("class", "countries")
    .style("fill", function(d) {return countryColor(geoPath.area(d))})
    .style("stroke-width", 1)
    .style("stroke", "black")
    .style("opacity", .5)
    .style("display", function(d,i) {return i%2 == 0 ? "none" : "block"})
    
    d3.select("#vectors").selectAll("circle").data(cities)
    .enter()
    .append("circle")
    .style("fill", "black")
    .style("stroke", "white")
    .style("stroke-width", 1)
    .attr("r", 3)
    .attr("cx", function(d) {return projection([d.y,d.x])[0]})
    .attr("cy", function(d) {return projection([d.y,d.x])[1]})

    redraw();

    function redraw() {
      var tiles = tile
      .scale(zoom.scale())
      .translate(zoom.translate())
      ();
      
      var image = d3.select("#tiles").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/examples.map-zgrqqx0w/" + 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]; });
      
      projection
      .scale(zoom.scale() / 2 / Math.PI)
      .translate(zoom.translate());

      d3.selectAll("path.countries")
      .attr("d", geoPath);

      d3.selectAll("circle")
    .attr("cx", function(d) {return projection([d.y,d.x])[0]})
    .attr("cy", function(d) {return projection([d.y,d.x])[1]})
      
    }
  }
</script>
  </footer>

</html>

cities.csv

"label","population","country","x","y"
"San Francisco", 750000,"USA",37,-122
"Fresno", 500000,"USA",36,-119
"Lahore",12500000,"Pakistan",31,74
"Karachi",13000000,"Pakistan",24,67
"Rome",2500000,"Italy",41,12
"Naples",1000000,"Italy",40,14
"Rio",12300000,"Brazil",-22,-43
"Sao Paolo",12300000,"Brazil",-23,-46

tile.js

d3.geo.tile = function() {
  var size = [960, 500],
      scale = 256,
      translate = [size[0] / 2, size[1] / 2],
      zoomDelta = 0;

  function tile() {
    var z = Math.max(Math.log(scale) / Math.LN2 - 8, 0),
        z0 = Math.round(z + zoomDelta),
        k = Math.pow(2, z - z0 + 8),
        origin = [(translate[0] - scale / 2) / k, (translate[1] - scale / 2) / k],
        tiles = [],
        cols = d3.range(Math.max(0, Math.floor(-origin[0])), Math.max(0, Math.ceil(size[0] / k - origin[0]))),
        rows = d3.range(Math.max(0, Math.floor(-origin[1])), Math.max(0, Math.ceil(size[1] / k - origin[1])));

    rows.forEach(function(y) {
      cols.forEach(function(x) {
        tiles.push([x, y, z0]);
      });
    });

    tiles.translate = origin;
    tiles.scale = k;

    return tiles;
  }

  tile.size = function(_) {
    if (!arguments.length) return size;
    size = _;
    return tile;
  };

  tile.scale = function(_) {
    if (!arguments.length) return scale;
    scale = _;
    return tile;
  };

  tile.translate = function(_) {
    if (!arguments.length) return translate;
    translate = _;
    return tile;
  };

  tile.zoomDelta = function(_) {
    if (!arguments.length) return zoomDelta;
    zoomDelta = +_;
    return tile;
  };

  return tile;
};