Skip to content

Instantly share code, notes, and snippets.

@jurb
Last active November 16, 2017 14:49
Show Gist options
  • Select an option

  • Save jurb/15f9b6233e74d72b8124369a05f898dd to your computer and use it in GitHub Desktop.

Select an option

Save jurb/15f9b6233e74d72b8124369a05f898dd to your computer and use it in GitHub Desktop.
Choropleth of grey pressure in 2016 in the Netherlands, ckmeans, rounded buckets

This is a simple example of a choropleth map of grey pressure in 2016 in the Netherlands, with the (now) current municipality list of 2017. Grey pressure is the ratio between the number of people aged 65 or over and the number of people aged 20 to 65.

Find the official numbers on grey pressure in 2016 I used here. CBS.nl has all other demographic data on the Netherlands you might need, which can be easily reworked to fit this map. For a lookup table of municipality names and their 'gemeentecode', which you'll need for the join with the municipality GEOJSON, go here.

Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Grey pressure % CG
44.2 GM1680
28.5 GM0738
31.5 GM0358
36.9 GM0197
32 GM0059
35.2 GM0482
27.7 GM0613
29.4 GM0361
31.9 GM0141
15.7 GM0034
29.9 GM0484
36.2 GM1723
38.8 GM0060
22.8 GM0307
32.5 GM0362
17.6 GM0363
34.1 GM0200
42.2 GM0003
22.6 GM0202
31.8 GM0106
34.9 GM0743
46.6 GM0744
39.7 GM0308
27.1 GM0489
28.7 GM0203
35.6 GM0005
41.2 GM0888
35.1 GM0370
36.5 GM0889
46.9 GM0007
40.6 GM1945
36.6 GM1724
36.8 GM0893
59.6 GM0373
34.3 GM0748
41.4 GM1859
34.3 GM1721
30.1 GM0753
28.4 GM0209
28.8 GM0375
35.2 GM0063
44 GM0310
38.8 GM0585
34.4 GM1728
52.7 GM0376
53.9 GM0377
34.1 GM1901
30.1 GM0755
34.1 GM0009
41.4 GM1681
36.5 GM0147
33 GM0654
35.3 GM0756
35.5 GM0757
28.7 GM0758
36.4 GM0501
43.1 GM1876
42.3 GM0213
38.7 GM0899
39.3 GM0312
26.4 GM0313
30.9 GM0214
31.1 GM0502
43.5 GM0383
39.6 GM0109
37.9 GM1706
39 GM0611
34.4 GM1684
28.4 GM0216
34.9 GM0148
36 GM1891
22.8 GM0503
42.8 GM0010
34 GM0762
28.2 GM0150
24.7 GM0384
35.9 GM1774
39.1 GM0221
33.6 GM0222
33.9 GM0766
36.9 GM0058
30 GM0505
34.3 GM0498
35.1 GM1719
27.9 GM0303
29.8 GM0225
28.1 GM0226
38.9 GM1711
33.2 GM0385
30.1 GM0228
33.9 GM0317
37.9 GM1651
36.4 GM0770
40.8 GM1903
26.3 GM0772
33 GM0230
36.8 GM0114
33.1 GM0388
27.8 GM0153
43.6 GM0232
39.8 GM0233
32 GM0777
34.1 GM1722
36.4 GM0070
38.4 GM1940
33.1 GM0779
32.1 GM0236
36.1 GM1771
29.8 GM1652
37.3 GM0907
33.4 GM0689
33.6 GM0784
36 GM1924
39.1 GM0664
37.4 GM0785
38.9 GM1942
30.1 GM0512
30.4 GM0513
33.4 GM0786
22.4 GM0518
17.6 GM0014
30.1 GM0015
43.8 GM1729
39.7 GM0158
36.8 GM0788
27.2 GM0392
29.5 GM0393
26.1 GM0394
38.4 GM1655
30.4 GM0160
29.1 GM0243
32.4 GM0523
52.6 GM0017
41.3 GM0072
39.1 GM0244
36.2 GM0396
52.6 GM0397
39.6 GM0246
36.4 GM0074
28.9 GM0398
35.7 GM0917
40.3 GM1658
49.9 GM0399
35.3 GM0400
35.9 GM0163
31.9 GM0530
27 GM0794
28.5 GM0531
32.1 GM0164
27.2 GM0796
40.2 GM0252
32.8 GM0797
35 GM0534
36.1 GM0798
32.5 GM0402
43.8 GM1735
33.6 GM1911
36 GM0118
37.5 GM0018
29.5 GM0405
33.3 GM1507
20.9 GM0321
36 GM0406
40.7 GM0677
26.3 GM0353
33.2 GM1884
28.3 GM0166
33.6 GM0678
28.8 GM0537
39.6 GM0928
32.4 GM1598
32.4 GM0079
35.2 GM0588
43.5 GM0542
35.6 GM1931
35.8 GM1659
34.4 GM1685
38.5 GM0882
36.5 GM0415
31.7 GM0416
24.7 GM1621
64.8 GM0417
37 GM0022
33.6 GM0545
27.6 GM0080
38.3 GM0081
21.2 GM0546
37.8 GM0547
39.4 GM1916
26.2 GM0995
37.4 GM1640
34.7 GM0327
30.9 GM0733
33.2 GM1705
36.3 GM0553
31.6 GM0140
49.2 GM0262
36.4 GM0809
26.7 GM0331
34.8 GM0024
38.1 GM0168
29.8 GM0263
42.1 GM1641
38.4 GM0556
32.1 GM0935
39.7 GM1663
30.4 GM0025
33.5 GM0420
44.1 GM0938
GM1948
34.8 GM1908
32.3 GM1987
32.2 GM0119
37.3 GM0687
29.5 GM1842
38.4 GM1731
35 GM0815
35 GM1709
30.3 GM1927
38.4 GM1955
31.8 GM0335
45.9 GM0944
29.2 GM1740
35.7 GM0946
29.6 GM0304
29.7 GM0356
32.6 GM0569
31.5 GM0267
22.8 GM0268
29.4 GM1930
46.2 GM1695
47.6 GM1699
27.9 GM0171
35.3 GM0575
35.2 GM0576
45 GM0820
34.4 GM0302
45.3 GM0951
36.1 GM0579
34.3 GM0823
40.9 GM0824
40.5 GM1895
30.5 GM0269
37.8 GM0173
34.5 GM1773
36.2 GM0175
37.3 GM0881
35.4 GM1586
36.7 GM0826
39.7 GM0085
34.5 GM0431
34.3 GM0432
35.1 GM0086
31.5 GM0828
33.8 GM0584
38.4 GM1509
36.2 GM0437
35.5 GM0589
31 GM1734
36.9 GM0590
34.3 GM1894
35.8 GM0765
24.3 GM1926
30.8 GM0439
33.9 GM0273
35.4 GM0177
30 GM0703
49.4 GM0274
26.3 GM0339
28.8 GM1667
46 GM0275
37.3 GM0340
42.8 GM0597
33.2 GM0196
32.3 GM1742
39.3 GM0603
41.8 GM1669
32.3 GM0957
34.2 GM0736
34.5 GM1674
23.9 GM0599
56.3 GM0277
34.7 GM0840
39.6 GM0441
33.3 GM0279
27.8 GM0606
49.4 GM0088
43.6 GM0962
46.9 GM1676
42.9 GM0965
34.3 GM1702
36.8 GM0845
36.6 GM1883
33.6 GM0610
31.8 GM0040
48 GM1714
35.2 GM0090
39.9 GM0342
31.6 GM0847
42.1 GM0848
42.6 GM0037
27.4 GM0180
35 GM0532
35 GM0851
37.3 GM1708
42.7 GM0971
31.9 GM1904
35.6 GM0617
37.2 GM1900
42.3 GM0715
37.4 GM0093
40.2 GM0448
32 GM1525
31.9 GM0716
27.5 GM0281
25.4 GM0855
29.5 GM0183
30.6 GM1700
43.7 GM1730
39.4 GM0737
34 GM0856
27.8 GM0450
31 GM0451
16.6 GM0184
15.1 GM0344
43.9 GM1581
45.8 GM0981
47.8 GM0994
42.9 GM0858
36.9 GM0047
29.1 GM0345
46.7 GM0717
35 GM0861
33.3 GM0453
34.2 GM0983
32.4 GM0984
34.4 GM0620
35.6 GM0622
43.3 GM0048
31.6 GM0096
38.1 GM0718
46 GM0986
41.4 GM0626
39.7 GM0285
35.5 GM0865
43.5 GM0866
34.4 GM0867
33.8 GM0627
23.5 GM0289
49.1 GM0629
40.3 GM0852
37.7 GM0988
32.7 GM0457
33.2 GM0870
32.9 GM0668
49.7 GM1701
27 GM0293
31 GM1783
39.7 GM0098
46.6 GM0614
35 GM0189
32.8 GM0296
43.3 GM1696
28.7 GM0352
37.7 GM0053
37.6 GM0294
40.5 GM0873
30.1 GM0632
40.9 GM1690
39.8 GM0880
34.2 GM0351
32.5 GM0874
29.5 GM0479
30.3 GM0297
45.7 GM0473
32 GM0707
18.4 GM0050
36.3 GM0355
40.9 GM0299
27.6 GM0637
34.2 GM0638
34.2 GM0056
29.5 GM1892
34.2 GM0879
33.8 GM0301
28.2 GM1896
38.6 GM0642
24.1 GM0193
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<style>
@import url('https://fonts.googleapis.com/css?family=Lato');
body {
display: block;
margin: auto;
max-width: 900px;
background-color: transparent !important;
}
.names {
fill: none;
stroke: #fff;
stroke-linejoin: round;
}
/* legend CSS */
text {
font-family: Lato, sans-serif;
font-size: 12px !important;
}
.legend {
transform: translate(-80px, -70px);
}
/* Yes, I scale both in the D3 code and here in the CSS. Not very pretty,
but not without reason: the scaling in the d3 code is responsive, and
I use this as a kind of 'final' scale to tighten things up (especially
on mobile).
*/
.countries {
transform: scale(1.4);
-webkit-transform: scale(1.4);
-moz-transform: scale(1.4);
-ms-transform: scale(1.4);
-o-transform: scale(1.4);
transform: translate(20px, 0px);
}
}
}
/*
text{
pointer-events:none;
}
*/
.details {
color: white;
}
h5 {
font-family: Lato, sans-serif;
height: 18px;
text-align: center;
}
</style>
<body>
<h5 id="kaart_subheader">Grey pressure per Dutch municipality</h5>
<!-- /container -->
<script src="//d3js.org/d3.v4.min.js"></script>
<script src="//d3js.org/queue.v1.min.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script src="//d3js.org/d3-geo-projection.v1.min.js"></script>
<script src='//cdnjs.cloudflare.com/ajax/libs/simple-statistics/1.0.1/simple_statistics.js'></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3-legend/2.11.0/d3-legend.min.js"></script>
<script>
// Formatting and locale, Dutch
var locale = {
decimal: ',',
thousands: '.',
grouping: [3],
currency: ['€\u00a0', ''],
dateTime: '%a %e %B %Y %T',
date: '%d-%m-%Y',
time: '%H:%M:%S',
periods: ['AM', 'PM'],
days: [
'zondag',
'maandag',
'dinsdag',
'woensdag',
'donderdag',
'vrijdag',
'zaterdag'
],
shortDays: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'],
months: [
'januari',
'februari',
'maart',
'april',
'mei',
'juni',
'juli',
'augustus',
'september',
'oktober',
'november',
'december'
],
shortMonths: [
'jan',
'feb',
'mrt',
'apr',
'mei',
'jun',
'jul',
'aug',
'sep',
'okt',
'nov',
'dec'
]
};
// Uncomment the line below if you want to use the Dutch locale (configured above)
// d3.formatDefaultLocale(locale);
// Closure function to round numbers nicely. I use ckmeans clusters as a starting point
// for nice bins/buckets (see https://cran.r-project.org/web/packages/Ckmeans.1d.dp/index.html),
// but them round them for readability. Not the most scientific route! If you want accuracy, you
// can remove the rounding and use the straight ckmeans buckets.
(function() {
/**
* Decimal adjustment of a number.
*
* @param {String} type The type of adjustment.
* @param {Number} value The number.
* @param {Integer} exp The exponent (the 10 logarithm of the adjustment base).
* @returns {Number} The adjusted value.
*/
function decimalAdjust(type, value, exp) {
// If the exp is undefined or zero...
if (typeof exp === 'undefined' || +exp === 0) {
return Math[type](value);
}
value = +value;
exp = +exp;
// If the value is not a number or the exp is not an integer...
if (
value === null ||
isNaN(value) ||
!(typeof exp === 'number' && exp % 1 === 0)
) {
return NaN;
}
// If the value is negative...
if (value < 0) {
return -decimalAdjust(type, -value, exp);
}
// Shift
value = value.toString().split('e');
value = Math[type](+(value[0] + 'e' + (value[1] ? +value[1] - exp : -exp)));
// Shift back
value = value.toString().split('e');
return +(value[0] + 'e' + (value[1] ? +value[1] + exp : exp));
}
// Decimal round
if (!Math.round10) {
Math.round10 = function(value, exp) {
return decimalAdjust('round', value, exp);
};
}
// Decimal floor
if (!Math.floor10) {
Math.floor10 = function(value, exp) {
return decimalAdjust('floor', value, exp);
};
}
// Decimal ceil
if (!Math.ceil10) {
Math.ceil10 = function(value, exp) {
return decimalAdjust('ceil', value, exp);
};
}
})();
var formatNumber0dec = d3.format(',.0f');
var formatPercentage1dec = d3.format(',.1%');
var formatPercentage0dec = d3.format(',.0%');
d3.select('body').style('overflow', 'hidden');
var parentWidth = d3
.select('body')
.node()
.getBoundingClientRect().width;
var margin = {
top: 0,
right: 0,
bottom: 0,
left: 0
};
var width =
parseInt(d3.select('body').style('width')) - margin.left - margin.right;
var mapRatio = 0.7;
var height = width * mapRatio;
var color = d3
.scaleQuantile()
.range(['#fce3a8', '#ebc78e', '#daac75', '#c9915c', '#b67745', '#a45d2e']);
var svg = d3
.select('body')
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('class', 'map');
var projection = d3
.geoMercator()
.scale(width * 8)
.center([5.2, 52.2])
.translate([width / 2.2, height / 2.3]);
var path = d3.geoPath().projection(projection);
queue()
.defer(d3.json, 'gemeenten_2017_simple.geojson')
.defer(d3.csv, 'grey-pressure.csv')
.await(ready);
function ready(error, geography, data) {
var data_clean = data;
///////////////
// UI Building
//////////////
var waardenlookup = {
jaar: 'Jaar',
ZS: 'Hoofdtype',
HS: 'Hoofdstuk',
CG: 'Gemeente',
VS: 'Versie',
AP: 'Aantal patiënten',
PV: 'Aantal patiënten per 100.000 verzekerden',
BE: 'Kosten (x € 1.000)',
BP: 'Kosten per patiënt',
BV: 'Kosten per 100.000 verzekerden'
};
var waardenlookup_reverse = {
Jaar: 'jaar',
Hoofdtype: 'ZS',
Hoofdstuk: 'HS',
Gemeente: 'CG',
Versie: 'VS',
'Aantal patiënten': 'AP',
'Aantal patiënten per 100.000 verzekerden': 'PV',
'Kosten (x € 1.000)': 'BE',
'Kosten per patiënt': 'BP',
'Kosten per 100.000 verzekerden': 'BV'
};
function lookup(object, my_key) {
return object[my_key];
}
var waarden = [
lookup(waardenlookup, 'AP'),
lookup(waardenlookup, 'PV'),
lookup(waardenlookup, 'BE'),
lookup(waardenlookup, 'BP'),
lookup(waardenlookup, 'BV')
];
//////////////////////
////////// Data stuff
/////////////////////
// Set formats
var formatMouseover = formatPercentage1dec;
var formatLegend = formatPercentage0dec;
var waarden_selected = ['Grey pressure %'];
var colorVariable = 'Grey pressure %';
var geoIDVariable = 'CG';
var colorVariableValueByID = {};
var colorVariableValueByIDExtra = {};
data.forEach(function(d) {
d[colorVariable] = isNaN(d[colorVariable]) ? null : +d[colorVariable];
colorVariableValueByID[d[geoIDVariable]] = d[colorVariable];
});
geography.features.forEach(function(d) {
d[colorVariable] = colorVariableValueByID[d.id];
d[geoIDVariable] = d[geoIDVariable];
});
// calculate ckmeans clusters
// then use the max value of each cluster
// as a break
var numberOfClasses = color.range().length - 1;
var ckmeansClusters = ss.ckmeans(
data.map(function(d) {
return d[colorVariable];
}),
numberOfClasses
);
var ckmeansBreaks = ckmeansClusters.map(function(d) {
return d3.min(d);
});
var max_value = d3.max(data, function(d) {
return d[colorVariable];
});
// the max value doesn't get included, push to end of array.
// this is hacky, but I don't know how else to fix this
ckmeansBreaks.push(max_value);
// nicer buckets
roundPower = 0;
ckmeansRounded = [];
ckmeansRounded = ckmeansBreaks.map(function(x) {
return Math.ceil10(x, roundPower);
});
ckmeansRounded[0] = ckmeansBreaks[0];
ckmeansRounded[ckmeansRounded.length - 1] =
ckmeansBreaks[ckmeansBreaks.length - 1];
ckmeansBreaks = ckmeansRounded;
// console.log(ckmeansBreaks)
// set the domain of the color scale based on our data
color.domain(ckmeansBreaks);
d3.select('#kaart_subheader').text('Grey pressure per Dutch municipality');
var quantize = color.domain(ckmeansBreaks);
svg
.append('g')
.attr('class', 'countries')
.selectAll('path')
.data(geography.features)
.enter()
.append('path')
.attr('vector-effect', 'non-scaling-stroke')
.attr('d', path)
.style('fill', function(d) {
if (colorVariable == 1) {
return color(colorVariableValueByID[0]);
}
if (typeof colorVariableValueByID[d.id] !== 'undefined') {
return color(colorVariableValueByID[d.id]);
}
return 'white';
})
.style('fill-opacity', 0.8)
.style('stroke', function(d) {
if (d[colorVariable] !== 0) {
return 'grey';
}
return 'grey';
})
.style('stroke-width', 0.6)
.style('stroke-opacity', 1)
.on('mouseover', function(d) {
if (d[colorVariable] == undefined) {
} else {
d3
.select(this)
.style('fill-opacity', 1)
.style('stroke-opacity', 1)
.style('stroke', 'grey')
.style('stroke-width', 0.6);
d3
.select('#kaart_subheader')
.text(
d.properties.naam +
': ' +
// I divide the percentage by 100 here to convert them from the 0-100 range (of the dataset) into
// the 0-1 range.
formatMouseover(d[colorVariable] / 100)
);
}
})
.on('mouseout', function(d) {
d3
.select(this)
.style('fill-opacity', 0.8)
.style('stroke-opacity', 1)
.style('stroke-width', 0.6);
d3.select('#kaart_subheader').text('Grey pressure per Dutch municipality');
});
svg
.append('path')
.datum(
topojson.mesh(geography.features, function(a, b) {
return a.id !== b.id;
})
)
.attr('class', 'names')
.attr('d', path);
////////////////////////////////
// Simple legend ///////////////
////////////////////////////////
// same as color.range, but with last color removed (my ckmeans implementation is hacky)
var colorRangeArray = ['#fce3a8', '#ebc78e', '#daac75', '#c9915c', '#b67745'];
var legendDomainText = [
formatLegend(color.domain()[0] / 100) + ' to ' + formatLegend(color.domain()[1] / 100),
formatLegend(color.domain()[1] / 100) + ' to ' + formatLegend(color.domain()[2] / 100),
formatLegend(color.domain()[2] / 100) + ' to ' + formatLegend(color.domain()[3] / 100),
formatLegend(color.domain()[3] / 100) + ' to ' + formatLegend(color.domain()[4] / 100),
formatLegend(color.domain()[4] / 100) + ' to ' + formatLegend(color.domain()[5] / 100)
];
var legendRectSize = 10;
var legendSpacing = 5;
var legend = d3
.select('svg')
.append('g')
.attr('class', 'legend')
.selectAll('g')
.data(colorRangeArray)
.enter()
.append('g')
.attr('transform', function(d, i) {
var height = legendRectSize;
var x = 100;
var y = 100 + i * (height + 5);
return 'translate(' + x + ',' + y + ')';
});
legend
.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', function(d, i) {
return d;
})
.style('stroke', function(d, i) {
return d;
});
legend
.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize)
.text(function(d, i) {
return legendDomainText[i];
});
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment