block by harrystevens 1c07d73efaf074de05e63a33431eb80a

Basic Map Functions

Full Screen

Some simple, frequently used functions for creating a map with D3.js and Topojson.

This block also uses the libraries chroma and jeezy.

See this tutorial for more on making maps with D3. I got most of the centerZoom function from this block.

index.html

<html>

<head>
  <style>
    body {
      margin: 0;
      font-family: "Helvetica Neue", sans-serif;
    }

    #map .subunit {
      fill: #ddd;
      stroke: #fff;
      stroke-width: 1px;
    }

    #map .subunit.selected {
      stroke: #000;
      stroke-width: 2px;
    }

    #map .subunit-boundary {
      fill: none;
      stroke: #3a403d;
    }

    #map .place,
    .place-label {
      pointer-events: none;
    }

    #map .place-label {
      font-size: .7em;
      text-shadow: 0px 0px 2px #fff;
    }

    #tip {
      position: absolute;
      background: #fff;
      opacity: .9;
      padding: 10px;
    }

    #legend {
      position: absolute;
      left: 10px;
      top: 10px;
    }

    #legend .legend-title {
      margin-bottom: 4px;
      font-weight: bold;
    }

    #legend .legend-item {
      margin-bottom: 4px;
      font-size: .8em;
    }

    #legend .legend-swatch,
    #legend .legend-value {
      display: inline-block;
      vertical-align: bottom;
    }

    #legend .legend-swatch {
      width: 16px;
      height: 16px;
      margin-right: 4px;
    }
  </style>

</head>

<body>
  <div id="tip"></div>
  <div id="legend">
    <div class="legend-title">Legend</div>
  </div>
  <div id="map"></div>

  <script src="https://d3js.org/d3.v4.min.js"></script>
  <script src="https://unpkg.com/d3-moveto@0.0.3/build/d3-moveto.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.20/topojson.min.js"></script>
  <script src="https://unpkg.com/jeezy@1.12.0/lib/jeezy.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/1.3.4/chroma.min.js"></script>

  <script>
    var match_property = "dist",
      value_property = "value",
      colors = ["#edf8fb", "#b2e2e2", "#66c2a4", "#2ca25f", "#006d2c"],
      breakType = "e";

    var width = window.innerWidth,
      height = window.innerHeight;

    var projection = d3.geoMercator();

    var path = d3.geoPath()
      .projection(projection)
      .pointRadius(2);

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

    d3.queue()
      .defer(d3.json, "map.json")
      .defer(d3.csv, "data.csv")
      .await(ready);

    function ready(error, geo, data) {
      if (error) throw error;

      centerZoom(geo, width, height);
      drawSubUnits(geo);
      drawPlaces(geo);
      drawOuterBoundary(geo);

      fillSubUnits(data);
      drawTip(data);
      drawLegend(data);

      window.addEventListener("resize", resize);

      function resize(){
        var width = window.innerWidth,
          height = window.innerHeight;

        svg.attr("width", width).attr("height", height);

        centerZoom(geo, width, height);

        d3.selectAll("path").attr("d", path);
        d3.selectAll("text").attr("transform", function(d) { return "translate(" + projection(d.geometry.coordinates) + ")"; });
            
      }
      
    }

    function drawLegend(data, breakType, breakCount) {
      var breakCount = colors.length;
      var limits = chroma.limits(data.map(function(d) {
        return +d[value_property];
      }), breakType, breakCount);

      colors.forEach(function(color, color_index) {

        d3.select("#legend").append("div")
          .attr("class", "legend-item")
          .html("<div class='legend-swatch' style='background: " + color + "'></div><div class='legend-value'>" + jz.str.numberLakhs(limits[color_index].toFixed(1)) + " - " + jz.str.numberLakhs(limits[color_index + 1].toFixed(1)) + "</div>");

      });

    }

    function fillSubUnits(data, breakType, breakCount) {
      var breakCount = colors.length;
      var limits = chroma.limits(data.map(function(d) {
        return +d[value_property];
      }), breakType, breakCount);


      svg.selectAll(".subunit")
        .style("fill", function(d, i) {
          var match = matchData(d, data);

          var return_color = [];
          limits.forEach(function(limit, limit_index) {
            if (+match[value_property] >= limit && +match[value_property] <= limits[limit_index + 1]) {
              return_color.push(colors[limit_index]);
            }
          });
          return return_color[0];

        });

    }

    function drawTip(data) {
      svg.selectAll(".subunit")
        .on("mouseover", function(d) {

          d3.select("#tip").style("display", "block");
          var match = matchData(d, data);

          // make the content
          var keys = Object.keys(match);
          var content = keys.map(function(key) {
            return "<b>" + key + "</b>: " + match[key];
          }).join("<br />");
          d3.select("#tip").html(content);

          d3.select(".subunit." + jz.str.toSlugCase(d.properties[match_property])).classed("selected", true).moveToFront();
          d3.selectAll(".place").moveToFront();
          d3.selectAll(".place-label").moveToFront();
        })
        .on("mousemove", function() {
          // tip positioning
          var coordinates = [0, 0];
          coordinates = d3.mouse(this);
          var x = coordinates[0];
          var y = coordinates[1];

          d3.select("#tip")
            .style("left", x + 20)
            .style("top", y - 20);
        })
        .on("mouseout", function() {
          d3.select("#tip").style("display", "none");
          d3.selectAll(".subunit").classed("selected", false);
          d3.select(".subunit-boundary").moveToFront();
        });
    }

    function matchData(d, data) {
      return data.filter(function(e) {
        return d.properties[match_property] == e[match_property];
      })[0];
    }

    function centerZoom(data, width, height) {
      projection.fitSize([width, height], topojson.feature(data, data.objects.polygons));
    }

    function drawOuterBoundary(data) {
      var boundary = topojson.mesh(data, data.objects.polygons, function(a, b) { return a === b; });
      svg.append("path")
        .datum(boundary)
        .attr("d", path)
        .attr("class", "subunit-boundary");
    }

    function drawPlaces(data) {
      svg.append("path")
        .datum(topojson.feature(data, data.objects.places))
        .attr("d", path)
        .attr("class", "place");

      svg.selectAll(".place-label")
        .data(topojson.feature(data, data.objects.places).features)
        .enter().append("text")
        .attr("class", "place-label")
        .attr("transform", function(d) {
          return "translate(" + projection(d.geometry.coordinates) + ")";
        })
        .attr("dy", ".35em")
        .attr("x", function(d) {
          return projection(d.geometry.coordinates)[0] <= width / 2 ? -6 : 6;
        })
        .style("text-anchor", function(d) {
          return projection(d.geometry.coordinates)[0] <= width / 2 ? "end" : "start";
        })
        .text(function(d) {
          return d.properties.name;
        });
    }

    function drawSubUnits(data) {
      svg.selectAll(".subunit")
        .data(topojson.feature(data, data.objects.polygons).features)
        .enter().append("path")
        .attr("class", function(d) {
          return "subunit " + jz.str.toSlugCase(d.properties[match_property]);
        })
        .attr("d", path);
    }
  </script>

</body>

</html>

data.csv

dist,no,value
Rae Bareli,58,76
Saharanpur,60,21
Sant Ravi Das Nagar,62,26
Shahjahanpur,63,22
Shravasti,64,15
Siddharth Nagar,65,92
Sitapur,66,20
Sonbhadra,67,63
Sultanpur,68,11
Unnao,69,51
Varanasi,70,12
Agra,1,48
Aligarh,2,16
Allahabad,3,56
Amethi,75,30
Azamgarh,6,46
Baghpat,7,96
Bahraich,8,39
Barabanki,12,83
Bareilly,13,93
Chitrakoot,19,81
Firozabad,26,28
Ghazipur,29,76
Gonda,30,26
Gorakhpur,31,51
Hamirpur,32,98
Hardoi,33,38
Jalaun,35,66
Jaunpur,36,96
Jhansi,37,64
Kushinagar,44,72
Lakhimpur Kheri,43,68
Maharajganj,48,35
Mahoba,47,89
Mirzapur,53,48
Ambedkar Nagar,4,31
Amroha,38,97
Auraiya,5,40
Ballia,9,39
Balrampur,10,67
Banda,11,56
Basti,14,10
Bijnor,15,12
Budaun,16,30
Bulandshahr,17,75
Chandauli,18,37
Deoria,20,76
Etah,21,59
Etawah,22,57
Faizabad,23,15
Farrukhabad,24,21
Fatehpur,25,90
Gautam Buddha Nagar,27,97
Ghaziabad,28,32
Shamli,72,73
Hapur,73,93
Hathras,34,16
Kannauj,39,52
Kanpur Dehat,40,88
Kanpur Nagar,41,51
Kasganj,71,32
Kaushambi,42,70
Lalitpur,45,80
Lucknow,46,31
Mainpuri,49,55
Mathura,50,74
Mau,51,64
Meerut,52,58
Moradabad,54,83
Muzaffarnagar,55,24
Pilibhit,56,46
Pratapgarh,57,24
Rampur,59,80
Sambhal,74,34
Sant Kabir Nagar,61,96

package-lock.json

{
  "name": "1c07d73efaf074de05e63a33431eb80a",
  "version": "1.0.0",
  "lockfileVersion": 1,
  "requires": true,
  "dependencies": {
    "array-source": {
      "version": "0.0.4",
      "resolved": "https://registry.npmjs.org/array-source/-/array-source-0.0.4.tgz",
      "integrity": "sha512-frNdc+zBn80vipY+GdcJkLEbMWj3xmzArYApmUGxoiV8uAu/ygcs9icPdsGdA26h0MkHUMW6EN2piIvVx+M5Mw=="
    },
    "commander": {
      "version": "2.11.0",
      "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
      "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ=="
    },
    "file-source": {
      "version": "0.6.1",
      "resolved": "https://registry.npmjs.org/file-source/-/file-source-0.6.1.tgz",
      "integrity": "sha1-rhidSZN2a4Zad/g63Pm5pQTNN9w=",
      "requires": {
        "stream-source": "0.3.5"
      }
    },
    "indian-ocean": {
      "version": "3.0.2",
      "resolved": "https://registry.npmjs.org/indian-ocean/-/indian-ocean-3.0.2.tgz",
      "integrity": "sha512-n4CQMRU8dfrF3/YwGNMPT8V8VMjUr76R6KXdNI0qiTxbWrPUWcKu6SGh4TkfHRiUY5+JAh/GGAXkLZ0hJeXFDg==",
      "requires": {
        "shapefile": "0.6.6"
      }
    },
    "jeezy": {
      "version": "1.12.0",
      "resolved": "https://registry.npmjs.org/jeezy/-/jeezy-1.12.0.tgz",
      "integrity": "sha512-tiaIeaQxBm3msnbOhYe6H4VbDsGSh+L1eudHM35TYGj94x1kw/oeX1Kv96Jmu7jfBsv7akMJH4fTram5g62x3Q=="
    },
    "path-source": {
      "version": "0.1.3",
      "resolved": "https://registry.npmjs.org/path-source/-/path-source-0.1.3.tgz",
      "integrity": "sha512-dWRHm5mIw5kw0cs3QZLNmpUWty48f5+5v9nWD2dw3Y0Hf+s01Ag8iJEWV0Sm0kocE8kK27DrIowha03e1YR+Qw==",
      "requires": {
        "array-source": "0.0.4",
        "file-source": "0.6.1"
      }
    },
    "shapefile": {
      "version": "0.6.6",
      "resolved": "https://registry.npmjs.org/shapefile/-/shapefile-0.6.6.tgz",
      "integrity": "sha512-rLGSWeK2ufzCVx05wYd+xrWnOOdSV7xNUW5/XFgx3Bc02hBkpMlrd2F1dDII7/jhWzv0MSyBFh5uJIy9hLdfuw==",
      "requires": {
        "array-source": "0.0.4",
        "commander": "2.11.0",
        "path-source": "0.1.3",
        "slice-source": "0.4.1",
        "stream-source": "0.3.5",
        "text-encoding": "0.6.4"
      }
    },
    "slice-source": {
      "version": "0.4.1",
      "resolved": "https://registry.npmjs.org/slice-source/-/slice-source-0.4.1.tgz",
      "integrity": "sha1-QKV6wDxmaLXaIA4FN44AC/KmHXk="
    },
    "stream-source": {
      "version": "0.3.5",
      "resolved": "https://registry.npmjs.org/stream-source/-/stream-source-0.3.5.tgz",
      "integrity": "sha512-ZuEDP9sgjiAwUVoDModftG0JtYiLUV8K4ljYD1VyUMRWtbVf92474o4kuuul43iZ8t/hRuiDAx1dIJSvirrK/g=="
    },
    "text-encoding": {
      "version": "0.6.4",
      "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz",
      "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk="
    }
  }
}

package.json

{
  "name": "1c07d73efaf074de05e63a33431eb80a",
  "version": "1.0.0",
  "description": "Some simple, frequently used functions for creating a map with [D3.js](https://github.com/d3) and [Topojson](https://github.com/mbostock/topojson).",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://gist.github.com/1c07d73efaf074de05e63a33431eb80a.git"
  },
  "author": "Harry Stevens <harryjosephstevens@gmail.com> (http://harryjstevens.com/)",
  "license": "ISC",
  "bugs": {
    "url": "https://gist.github.com/1c07d73efaf074de05e63a33431eb80a"
  },
  "homepage": "https://gist.github.com/1c07d73efaf074de05e63a33431eb80a",
  "dependencies": {
    "indian-ocean": "^3.0.2",
    "jeezy": "^1.12.0"
  }
}

random_data.js

var io = require("indian-ocean"),
	jz = require("jeezy");

var json = io.readDataSync("map.json");

var props = json.objects.polygons.geometries.map(d => d.properties);

props.forEach(d => {
	d.value = jz.num.randBetween(10, 100);
	return d;
});

io.writeDataSync("data.csv", props);