Skip to content

Instantly share code, notes, and snippets.

@HarryStevens
Last active February 7, 2018 14:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save HarryStevens/1c07d73efaf074de05e63a33431eb80a to your computer and use it in GitHub Desktop.
Save HarryStevens/1c07d73efaf074de05e63a33431eb80a to your computer and use it in GitHub Desktop.
Basic Map Functions
license: gpl-3.0
node_modules

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

This block also uses the libraries chroma and jeezy.

  • centerZoom - Automatically centers and scales your map to its container, and returns your map's outer boundaries in case you want to draw them.
  • drawOuterBoundary - Uses the boundary returned from centerZoom to draw a boundary around your whole map.
  • drawPlaces - Draws place names, if your topojson has places.
  • drawSubunits - Draws subunits.
  • fillSubUnits - Fills subunits according to specified limits and color scale.
  • drawTip - Draws and positions and tooltip based on the data of the selected subunit.
  • drawLegend - Draws a legend according to specified limits and color scale.

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

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
<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>
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
{
"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="
}
}
}
{
"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"
}
}
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);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment