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.
<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>
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
{
"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);