block by thadk 37ff4c03358b414acd1e

ckmeans & now deprecated Jenks Natural Breaks with simple-statistics and d3

Full Screen

Demonstrating jenks natural breaks implemented in simple-statistics. “Ckmeans clustering is an improvement on heuristic-based clustering approaches like Jenks.” It is “a dynamic programming approach to the problem of clustering numeric data into groups with the least within-group sum-of-squared-deviations”

Rendered by d3js, based on an example by Mike Bostock and Tom MacWright’s original comparison with quantize.

More on ckmeans is in the Simple-Statistics documentation. Also see the PR removing Jenks here and the original narrative on how Jenks algorithm was reimplemented through Tom’s literature review.

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<style>

body {
  font:normal 14px sans-serif;
}

#form {
  position: absolute;
  top: 10px;
  left: 10px;
  background-color: hsla(0, 100%, 100%, 0.8);
}

input {
  margin-right: 10px;
}

.states {
  fill: none;
  stroke: #fff;
  stroke-linejoin: round;
}

path {
  -webkit-transition: fill 200ms linear;
}

.q0-9 { fill:rgb(247,251,255); }
.q1-9 { fill:rgb(222,235,247); }
.q2-9 { fill:rgb(198,219,239); }
.q3-9 { fill:rgb(158,202,225); }
.q4-9 { fill:rgb(107,174,214); }
.q5-9 { fill:rgb(66,146,198); }
.q6-9 { fill:rgb(33,113,181); }
.q7-9 { fill:rgb(8,81,156); }
.q8-9 { fill:rgb(8,48,107); }

</style>
<body>
<div id='form'>
  <input checked='true' type='radio' name='scale' id='jenks9' /><label for='jenks9'>Jenks from SS v0.9</label>
  <input type='radio' name='scale' id='ckmeans' /><label for='ckmeans'>New ckmeans in SS v1.0</label>
  <input type='radio' name='scale' id='quantize' /><label for='quantize'>Quantize</label>
</div>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="simple_statistics.js"></script>
<script src="ckmeans_simple_statistics.js"></script>
<script src="//d3js.org/queue.v1.min.js"></script>
<script src="//d3js.org/topojson.v0.min.js"></script>
<script>

var width = 960,
    height = 500;

var scales = {};

scales.quantize = d3.scale.quantize()
    .domain([0, .15])
    .range(d3.range(9).map(function(i) { return "q" + i + "-9"; }));

var path = d3.geo.path();

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

queue()
    .defer(d3.json, "/d/4090846/us.json")
    .defer(d3.tsv, "unemployment.tsv")
    .await(ready);

function ready(error, us, unemployment) {
  var rateById = {};

  unemployment.forEach(function(d) { rateById[d.id] = +d.rate; });

  scales.jenks9 = d3.scale.threshold()
      .domain(ss.jenks(unemployment.map(function(d) { return +d.rate; }), 9))
      .range(d3.range(9).map(function(i) { return "q" + i + "-9"; }));
  
  scales.ckmeans = d3.scale.threshold()
      .domain(ssck.ckmeans(unemployment.map(function(d) { return +d.rate; }), 9).map(function(cluster) {return cluster[0];}))
      .range(d3.range(9).map(function(i) { return "q" + i + "-9"; }));

  var counties = svg.append("g")
      .attr("class", "counties")
    .selectAll("path")
      .data(topojson.object(us, us.objects.counties).geometries)
    .enter().append("path")
      .attr("d", path);

  d3.selectAll('input').on('change', function() {
      setScale(this.id);
  });

  function setScale(s) {
      counties.attr("class", function(d) { return scales[s](rateById[d.id]); })
  }

  setScale('jenks9');

  svg.append("path")
      .datum(topojson.mesh(us, us.objects.states, function(a, b) { return a.id !== b.id; }))
      .attr("class", "states")
      .attr("d", path);
}

</script>