Skip to content

Instantly share code, notes, and snippets.

@juanprq
Created October 16, 2016 23:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save juanprq/e55df12130eb4994e31eeb21ee1d9cbb to your computer and use it in GitHub Desktop.
Save juanprq/e55df12130eb4994e31eeb21ee1d9cbb to your computer and use it in GitHub Desktop.
JSON to Grid Choropleth
license: mit
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.state rect {
fill: #a6a8ab;
}
.state text {
font: 12px monospace;
font-weight: bold;
text-anchor: middle;
fill: white;
}
.q0-5 rect { fill: #fffafa; }
.q1-5 rect { fill: #ffd2c4; }
.q2-5 rect { fill: #ffa68e; }
.q3-5 rect { fill: #fc7657; }
.q4-5 rect { fill: #ef4123; }
#legend {
padding: 1.5em 0 0 1.5em;
}
.list-inline {
padding-left: 0;
list-style: none;
}
.list-inline > li {
display: inline-block;
}
li.key {
border-top-width: 15px;
border-top-style: solid;
font-size: .75em;
width: 10%;
padding-left: 0;
padding-right: 0;
}
li.q0-5 { color: #fffafa; }
li.q1-5 { color: #ffd2c4; }
li.q2-5 { color: #ffa68e; }
li.q3-5 { color: #fc7657; }
li.q4-5 { color: #ef4123; }
</style>
<body>
<svg width="960" height="500"></svg>
<script id="grid" type="text/plain">
ME
WI VT NH
WA ID MT ND MN IL MI NY MA RI
OR NV WY SD IA IN OH PA NJ CT
CA UT CO NE MO KY WV VA MD DE
AZ NM KS AR TN NC SC
OK LA MS AL GA
HI AK TX FL
</script>
<script src="jenks.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js"></script>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var states = [],
//
// EDIT!
//
dataKey = 'overallGrade',
//
// DON'T EDIT!
//
stateKey = 'stateAbbrev',
rowsArr = [
{
"stateName": "VERMONT",
"stateAbbrev": "VT",
"extremeHeat": 76,
"drought": null,
"wildfires": null,
"inlandFlooding": null,
"coastalFlooding": null,
"overallGrade": 60
},
{
"stateName": "WEST VIRGINIA",
"stateAbbrev": "WV",
"extremeHeat": 52,
"drought": null,
"wildfires": null,
"inlandFlooding": 36,
"coastalFlooding": null,
"overallGrade": 52
},
{
"stateName": "NORTH DAKOTA",
"stateAbbrev": "ND",
"extremeHeat": 76,
"drought": null,
"wildfires": null,
"inlandFlooding": 60,
"coastalFlooding": null,
"overallGrade": 60
},
{
"stateName": "HAWAII",
"stateAbbrev": "HI",
"extremeHeat": 12,
"drought": null,
"wildfires": null,
"inlandFlooding": null,
"coastalFlooding": 28,
"overallGrade": 28
},
{
"stateName": "NEW JERSEY",
"stateAbbrev": "NJ",
"extremeHeat": 52,
"drought": null,
"wildfires": null,
"inlandFlooding": 52,
"coastalFlooding": 28,
"overallGrade": 36
},
{
"stateName": "NEW HAMPSHIRE",
"stateAbbrev": "NH",
"extremeHeat": 60,
"drought": null,
"wildfires": null,
"inlandFlooding": null,
"coastalFlooding": 28,
"overallGrade": 52
},
{
"stateName": "RHODE ISLAND",
"stateAbbrev": "RI",
"extremeHeat": 76,
"drought": null,
"wildfires": null,
"inlandFlooding": 76,
"coastalFlooding": 60,
"overallGrade": 76
},
{
"stateName": "VIRGINIA",
"stateAbbrev": "VA",
"extremeHeat": 84,
"drought": null,
"wildfires": null,
"inlandFlooding": 76,
"coastalFlooding": 60,
"overallGrade": 76
},
{
"stateName": "DELAWARE",
"stateAbbrev": "DE",
"extremeHeat": 84,
"drought": null,
"wildfires": null,
"inlandFlooding": 84,
"coastalFlooding": 84,
"overallGrade": 84
},
{
"stateName": "ALASKA",
"stateAbbrev": "AK",
"extremeHeat": 100,
"drought": null,
"wildfires": null,
"inlandFlooding": null,
"coastalFlooding": 76,
"overallGrade": 84
},
{
"stateName": "CONNECTICUT",
"stateAbbrev": "CT",
"extremeHeat": 100,
"drought": null,
"wildfires": null,
"inlandFlooding": 100,
"coastalFlooding": 76,
"overallGrade": 100
},
{
"stateName": "MARYLAND",
"stateAbbrev": "MD",
"extremeHeat": 100,
"drought": null,
"wildfires": null,
"inlandFlooding": null,
"coastalFlooding": 100,
"overallGrade": 84
},
{
"stateName": "MASSACHUSETTS",
"stateAbbrev": "MA",
"extremeHeat": 100,
"drought": null,
"wildfires": null,
"inlandFlooding": 100,
"coastalFlooding": 100,
"overallGrade": 100
},
{
"stateName": "SOUTH CAROLINA",
"stateAbbrev": "SC",
"extremeHeat": 52,
"drought": null,
"wildfires": 76,
"inlandFlooding": 52,
"coastalFlooding": 28,
"overallGrade": 52
},
{
"stateName": "SOUTH DAKOTA",
"stateAbbrev": "SD",
"extremeHeat": 36,
"drought": 12,
"wildfires": null,
"inlandFlooding": 12,
"coastalFlooding": null,
"overallGrade": 28
},
{
"stateName": "ARKANSAS",
"stateAbbrev": "AR",
"extremeHeat": 28,
"drought": 12,
"wildfires": 12,
"inlandFlooding": 12,
"coastalFlooding": null,
"overallGrade": 12
},
{
"stateName": "NEVADA",
"stateAbbrev": "NV",
"extremeHeat": 12,
"drought": 12,
"wildfires": 36,
"inlandFlooding": null,
"coastalFlooding": null,
"overallGrade": 12
},
{
"stateName": "MONTANA",
"stateAbbrev": "MT",
"extremeHeat": 12,
"drought": 12,
"wildfires": 52,
"inlandFlooding": null,
"coastalFlooding": null,
"overallGrade": 28
},
{
"stateName": "KENTUCKY",
"stateAbbrev": "KY",
"extremeHeat": 36,
"drought": 36,
"wildfires": 36,
"inlandFlooding": 12,
"coastalFlooding": null,
"overallGrade": 28
},
{
"stateName": "ARIZONA",
"stateAbbrev": "AZ",
"extremeHeat": 60,
"drought": 36,
"wildfires": 28,
"inlandFlooding": null,
"coastalFlooding": null,
"overallGrade": 52
},
{
"stateName": "LOUISIANA",
"stateAbbrev": "LA",
"extremeHeat": 52,
"drought": 36,
"wildfires": 52,
"inlandFlooding": null,
"coastalFlooding": 76,
"overallGrade": 52
},
{
"stateName": "MISSISSIPPI",
"stateAbbrev": "MS",
"extremeHeat": 28,
"drought": 28,
"wildfires": 28,
"inlandFlooding": null,
"coastalFlooding": 12,
"overallGrade": 12
},
{
"stateName": "WYOMING",
"stateAbbrev": "WY",
"extremeHeat": 52,
"drought": 28,
"wildfires": 28,
"inlandFlooding": null,
"coastalFlooding": null,
"overallGrade": 28
},
{
"stateName": "TEXAS",
"stateAbbrev": "TX",
"extremeHeat": 12,
"drought": 28,
"wildfires": 28,
"inlandFlooding": null,
"coastalFlooding": 36,
"overallGrade": 12
},
{
"stateName": "ILLINOIS",
"stateAbbrev": "IL",
"extremeHeat": 36,
"drought": 28,
"wildfires": null,
"inlandFlooding": 28,
"coastalFlooding": null,
"overallGrade": 28
},
{
"stateName": "OHIO",
"stateAbbrev": "OH",
"extremeHeat": 12,
"drought": 28,
"wildfires": null,
"inlandFlooding": 28,
"coastalFlooding": null,
"overallGrade": 28
},
{
"stateName": "MISSOURI",
"stateAbbrev": "MO",
"extremeHeat": 52,
"drought": 28,
"wildfires": 12,
"inlandFlooding": 12,
"coastalFlooding": null,
"overallGrade": 12
},
{
"stateName": "IOWA",
"stateAbbrev": "IA",
"extremeHeat": 52,
"drought": 60,
"wildfires": null,
"inlandFlooding": 52,
"coastalFlooding": null,
"overallGrade": 60
},
{
"stateName": "TENNESSEE",
"stateAbbrev": "TN",
"extremeHeat": 60,
"drought": 60,
"wildfires": 60,
"inlandFlooding": 28,
"coastalFlooding": null,
"overallGrade": 52
},
{
"stateName": "GEORGIA",
"stateAbbrev": "GA",
"extremeHeat": 52,
"drought": 60,
"wildfires": 76,
"inlandFlooding": 52,
"coastalFlooding": 36,
"overallGrade": 52
},
{
"stateName": "WISCONSIN",
"stateAbbrev": "WI",
"extremeHeat": 76,
"drought": 52,
"wildfires": null,
"inlandFlooding": 60,
"coastalFlooding": null,
"overallGrade": 76
},
{
"stateName": "ALABAMA",
"stateAbbrev": "AL",
"extremeHeat": 36,
"drought": 52,
"wildfires": 12,
"inlandFlooding": null,
"coastalFlooding": 12,
"overallGrade": 28
},
{
"stateName": "KANSAS",
"stateAbbrev": "KS",
"extremeHeat": 60,
"drought": 52,
"wildfires": 52,
"inlandFlooding": 36,
"coastalFlooding": null,
"overallGrade": 36
},
{
"stateName": "INDIANA",
"stateAbbrev": "IN",
"extremeHeat": 52,
"drought": 52,
"wildfires": null,
"inlandFlooding": 52,
"coastalFlooding": null,
"overallGrade": 52
},
{
"stateName": "NEBRASKA",
"stateAbbrev": "NE",
"extremeHeat": 28,
"drought": 52,
"wildfires": null,
"inlandFlooding": 52,
"coastalFlooding": null,
"overallGrade": 36
},
{
"stateName": "IDAHO",
"stateAbbrev": "ID",
"extremeHeat": 28,
"drought": 52,
"wildfires": 60,
"inlandFlooding": null,
"coastalFlooding": null,
"overallGrade": 36
},
{
"stateName": "NEW MEXICO",
"stateAbbrev": "NM",
"extremeHeat": 76,
"drought": 52,
"wildfires": 76,
"inlandFlooding": null,
"coastalFlooding": null,
"overallGrade": 76
},
{
"stateName": "COLORADO",
"stateAbbrev": "CO",
"extremeHeat": 76,
"drought": 84,
"wildfires": null,
"inlandFlooding": null,
"coastalFlooding": null,
"overallGrade": 76
},
{
"stateName": "FLORIDA",
"stateAbbrev": "FL",
"extremeHeat": 28,
"drought": 84,
"wildfires": 84,
"inlandFlooding": 28,
"coastalFlooding": 12,
"overallGrade": 52
},
{
"stateName": "NORTH CAROLINA",
"stateAbbrev": "NC",
"extremeHeat": 84,
"drought": 84,
"wildfires": 100,
"inlandFlooding": 76,
"coastalFlooding": 52,
"overallGrade": 84
},
{
"stateName": "MINNESOTA",
"stateAbbrev": "MN",
"extremeHeat": 76,
"drought": 76,
"wildfires": null,
"inlandFlooding": null,
"coastalFlooding": null,
"overallGrade": 76
},
{
"stateName": "OKLAHOMA",
"stateAbbrev": "OK",
"extremeHeat": 60,
"drought": 76,
"wildfires": 52,
"inlandFlooding": 28,
"coastalFlooding": null,
"overallGrade": 60
},
{
"stateName": "UTAH",
"stateAbbrev": "UT",
"extremeHeat": 28,
"drought": 76,
"wildfires": 76,
"inlandFlooding": null,
"coastalFlooding": null,
"overallGrade": 60
},
{
"stateName": "MICHIGAN",
"stateAbbrev": "MI",
"extremeHeat": 76,
"drought": 76,
"wildfires": null,
"inlandFlooding": 76,
"coastalFlooding": null,
"overallGrade": 76
},
{
"stateName": "MAINE",
"stateAbbrev": "ME",
"extremeHeat": 28,
"drought": 76,
"wildfires": null,
"inlandFlooding": 52,
"coastalFlooding": 52,
"overallGrade": 28
},
{
"stateName": "WASHINGTON",
"stateAbbrev": "WA",
"extremeHeat": 28,
"drought": 76,
"wildfires": 100,
"inlandFlooding": 76,
"coastalFlooding": 52,
"overallGrade": 84
},
{
"stateName": "OREGON",
"stateAbbrev": "OR",
"extremeHeat": 12,
"drought": 100,
"wildfires": 84,
"inlandFlooding": 60,
"coastalFlooding": 52,
"overallGrade": 76
},
{
"stateName": "PENNSYLVANIA",
"stateAbbrev": "PA",
"extremeHeat": 84,
"drought": 100,
"wildfires": null,
"inlandFlooding": 84,
"coastalFlooding": 76,
"overallGrade": 100
},
{
"stateName": "NEW YORK",
"stateAbbrev": "NY",
"extremeHeat": 100,
"drought": 100,
"wildfires": null,
"inlandFlooding": 100,
"coastalFlooding": 76,
"overallGrade": 100
},
{
"stateName": "CALIFORNIA",
"stateAbbrev": "CA",
"extremeHeat": 100,
"drought": 100,
"wildfires": 100,
"inlandFlooding": 100,
"coastalFlooding": 100,
"overallGrade": 100
}
];
d3.select("#grid").text().split("\n").forEach(function(line, i) {
var re = /\w+/g, m;
while (m = re.exec(line)) {
console.log(_.findWhere(rowsArr, { 'stateAbbrev': m[0]}))
states.push({
name: m[0],
data: _.result(_.findWhere(rowsArr, { 'stateAbbrev': m[0]}), dataKey),
x: m.index / 3,
y: i
});
}
});
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var gridWidth = d3.max(states, function(d) { return d.x; }) + 1,
gridHeight = d3.max(states, function(d) { return d.y; }) + 1,
cellSize = 40;
var nestedData = d3.nest()
.key(function(d) { return d.stateAbbrev; })
.entries(rowsArr);
var mappedData = function mappedValues(key) {
return rowsArr.map(function(stateObj) {
return stateObj[dataKey]
})
}
var jenks5 = d3.scale.quantile()
.domain(jenks(mappedData(stateKey), 4))
.range(d3.range(5).map(function(i) { return "q" + i + "-5"; }));
var state = svg.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.selectAll(".state")
.data(states)
.enter().append("g")
.attr("transform", function(d) { return "translate(" + (d.x - gridWidth / 2) * cellSize + "," + (d.y - gridHeight / 2) * cellSize + ")"; })
.attr("class", function(d) {
var currData = d.data,
currClassStr = '';
if (currData !== null) {
currClassStr += "state " + jenks5(d.data)
} else {
currClassStr += "state"
}
return currClassStr;
});
state.append("rect")
.attr("x", -cellSize / 2)
.attr("y", -cellSize / 2)
.attr("width", cellSize - 1)
.attr("height", cellSize - 1);
state.append("text")
.attr("dy", ".35em")
.text(function(d) { return d.name; });
var legend = d3.select('body')
.insert('div', ":first-child")
.attr("id", "legend")
.append('ul')
.attr('class', 'list-inline');
var keys = legend.selectAll('li.key')
.data(jenks5.range());
keys.enter().append('li')
.attr('class', function(d) { return'key ' + d; })
.style('border-top-color', String);
</script>
// # [Jenks natural breaks optimization](http://en.wikipedia.org/wiki/Jenks_natural_breaks_optimization)
//
// Implementations: [1](http://danieljlewis.org/files/2010/06/Jenks.pdf) (python),
// [2](https://github.com/vvoovv/djeo-jenks/blob/master/main.js) (buggy),
// [3](https://github.com/simogeo/geostats/blob/master/lib/geostats.js#L407) (works)
var jenks = function jenks(data, n_classes) {
// Compute the matrices required for Jenks breaks. These matrices
// can be used for any classing of data with `classes <= n_classes`
function getMatrices(data, n_classes) {
// in the original implementation, these matrices are referred to
// as `LC` and `OP`
//
// * lower_class_limits (LC): optimal lower class limits
// * variance_combinations (OP): optimal variance combinations for all classes
var lower_class_limits = [],
variance_combinations = [],
// loop counters
i, j,
// the variance, as computed at each step in the calculation
variance = 0;
// Initialize and fill each matrix with zeroes
for (i = 0; i < data.length + 1; i++) {
var tmp1 = [],
tmp2 = [];
for (j = 0; j < n_classes + 1; j++) {
tmp1.push(0);
tmp2.push(0);
}
lower_class_limits.push(tmp1);
variance_combinations.push(tmp2);
}
for (i = 1; i < n_classes + 1; i++) {
lower_class_limits[1][i] = 1;
variance_combinations[1][i] = 0;
// in the original implementation, 9999999 is used but
// since Javascript has `Infinity`, we use that.
for (j = 2; j < data.length + 1; j++) {
variance_combinations[j][i] = Infinity;
}
}
for (var l = 2; l < data.length + 1; l++) {
// `SZ` originally. this is the sum of the values seen thus
// far when calculating variance.
var sum = 0,
// `ZSQ` originally. the sum of squares of values seen
// thus far
sum_squares = 0,
// `WT` originally. This is the number of
w = 0,
// `IV` originally
i4 = 0;
// in several instances, you could say `Math.pow(x, 2)`
// instead of `x * x`, but this is slower in some browsers
// introduces an unnecessary concept.
for (var m = 1; m < l + 1; m++) {
// `III` originally
var lower_class_limit = l - m + 1,
val = data[lower_class_limit - 1];
// here we're estimating variance for each potential classing
// of the data, for each potential number of classes. `w`
// is the number of data points considered so far.
w++;
// increase the current sum and sum-of-squares
sum += val;
sum_squares += val * val;
// the variance at this point in the sequence is the difference
// between the sum of squares and the total x 2, over the number
// of samples.
variance = sum_squares - (sum * sum) / w;
i4 = lower_class_limit - 1;
if (i4 !== 0) {
for (j = 2; j < n_classes + 1; j++) {
// if adding this element to an existing class
// will increase its variance beyond the limit, break
// the class at this point, setting the lower_class_limit
// at this point.
if (variance_combinations[l][j] >=
(variance + variance_combinations[i4][j - 1])) {
lower_class_limits[l][j] = lower_class_limit;
variance_combinations[l][j] = variance +
variance_combinations[i4][j - 1];
}
}
}
}
lower_class_limits[l][1] = 1;
variance_combinations[l][1] = variance;
}
// return the two matrices. for just providing breaks, only
// `lower_class_limits` is needed, but variances can be useful to
// evaluage goodness of fit.
return {
lower_class_limits: lower_class_limits,
variance_combinations: variance_combinations
};
}
// the second part of the jenks recipe: take the calculated matrices
// and derive an array of n breaks.
function breaks(data, lower_class_limits, n_classes) {
var k = data.length - 1,
kclass = [],
countNum = n_classes;
// the calculation of classes will never include the upper and
// lower bounds, so we need to explicitly set them
kclass[n_classes] = data[data.length - 1];
kclass[0] = data[0];
// the lower_class_limits matrix is used as indexes into itself
// here: the `k` variable is reused in each iteration.
while (countNum > 1) {
kclass[countNum - 1] = data[lower_class_limits[k][countNum] - 2];
k = lower_class_limits[k][countNum] - 1;
countNum--;
}
return kclass;
}
if (n_classes > data.length) return null;
// sort data in numerical order, since this is expected
// by the matrices function
data = data.slice().sort(function(a, b) {
return a - b;
});
// get our basic matrices
var matrices = getMatrices(data, n_classes),
// we only need lower class limits here
lower_class_limits = matrices.lower_class_limits;
// extract n_classes out of the computed matrices
return breaks(data, lower_class_limits, n_classes);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment