block by micahstubbs b642950b3be93bac301ad3ac409a4bcb

Home Ownership by Country

Full Screen

TODO


in this iteration, we use the ckmeans algorithm from the simple-statistics package to cluster our data. we pick the minimum value of each cluster as a break. we then use these breaks with a quantile scale to map values in the data to colors on the choropleth map.

this is method is my current favorite way to create breaks, or color thresholds, for a choropleth map.

🎩 @Elijah_Meeks for the idea to try the ckmeans algorithm, as it seems to be the new hotness in the choropleth map breaks scene

a further 🙏 to @recifs for talking through where in the ckmeans clusters its reasonable to pick breaks from.
tl;dr any number between max(class n) and min(class n+1) is OK

do check out the other examples in this world map series:

world map 00 original example
world map 01 fix tooltip value
world map 02 d3 v4
world map 03 es2015 + update code style
world map 04 manual breaks + threshold scale
world map 05 linear breaks + quantize scale
world map 06 linear breaks + quantiles scale
world map 07 Jenks natural breaks
world map 08 ckmeans cluster max breaks
world map 09 ckmeans cluster min breaks

index.js

// configuration
const colorVariable = 'Home ownership  rate(%)'
const geoIDVariable = 'id'
const format = d3.format(',')

// Set tooltips
const tip = d3
  .tip()
  .attr('class', 'd3-tip')
  .offset([-10, 0])
  .html(
    d =>
      `<strong>Country: </strong><span class='details'>${
        d.properties.name
      }<br></span><strong>Population: </strong><span class='details'>${format(
        d[colorVariable]
      )}</span>`
  )

tip.direction(function(d) {
  if (d.properties.name === 'Antarctica') return 'n'
  // Americas
  if (d.properties.name === 'Greenland') return 's'
  if (d.properties.name === 'Canada') return 'e'
  if (d.properties.name === 'USA') return 'e'
  if (d.properties.name === 'Mexico') return 'e'
  // Europe
  if (d.properties.name === 'Iceland') return 's'
  if (d.properties.name === 'Norway') return 's'
  if (d.properties.name === 'Sweden') return 's'
  if (d.properties.name === 'Finland') return 's'
  if (d.properties.name === 'Russia') return 'w'
  // Asia
  if (d.properties.name === 'China') return 'w'
  if (d.properties.name === 'Japan') return 's'
  // Oceania
  if (d.properties.name === 'Indonesia') return 'w'
  if (d.properties.name === 'Papua New Guinea') return 'w'
  if (d.properties.name === 'Australia') return 'w'
  if (d.properties.name === 'New Zealand') return 'w'
  // otherwise if not specified
  return 'n'
})

tip.offset(function(d) {
  // [top, left]
  if (d.properties.name === 'Antarctica') return [0, 0]
  // Americas
  if (d.properties.name === 'Greenland') return [10, -10]
  if (d.properties.name === 'Canada') return [24, -28]
  if (d.properties.name === 'USA') return [-5, 8]
  if (d.properties.name === 'Mexico') return [12, 10]
  if (d.properties.name === 'Chile') return [0, -15]
  // Europe
  if (d.properties.name === 'Iceland') return [15, 0]
  if (d.properties.name === 'Norway') return [10, -28]
  if (d.properties.name === 'Sweden') return [10, -8]
  if (d.properties.name === 'Finland') return [10, 0]
  if (d.properties.name === 'France') return [-9, 66]
  if (d.properties.name === 'Italy') return [-8, -6]
  if (d.properties.name === 'Russia') return [5, 385]
  // Africa
  if (d.properties.name === 'Madagascar') return [-10, 10]
  // Asia
  if (d.properties.name === 'China') return [-16, -8]
  if (d.properties.name === 'Mongolia') return [-5, 0]
  if (d.properties.name === 'Pakistan') return [-10, 13]
  if (d.properties.name === 'India') return [-11, -18]
  if (d.properties.name === 'Nepal') return [-8, 1]
  if (d.properties.name === 'Myanmar') return [-12, 0]
  if (d.properties.name === 'Laos') return [-12, -8]
  if (d.properties.name === 'Vietnam') return [-12, -4]
  if (d.properties.name === 'Japan') return [5, 5]
  // Oceania
  if (d.properties.name === 'Indonesia') return [0, -5]
  if (d.properties.name === 'Papua New Guinea') return [-5, -10]
  if (d.properties.name === 'Australia') return [-15, 0]
  if (d.properties.name === 'New Zealand') return [-15, 0]
  // otherwise if not specified
  return [-10, 0]
})

d3.select('body').style('overflow', 'hidden')

const parentWidth = d3
  .select('body')
  .node()
  .getBoundingClientRect().width
const margin = { top: 0, right: 0, bottom: 0, left: 0 }
const width = 960 - margin.left - margin.right
const height = 500 - margin.top - margin.bottom

const color = d3
  .scaleQuantile()
  .range([
    'rgb(247,251,255)',
    'rgb(222,235,247)',
    'rgb(198,219,239)',
    'rgb(158,202,225)',
    'rgb(107,174,214)',
    'rgb(66,146,198)',
    'rgb(33,113,181)',
    'rgb(8,81,156)',
    'rgb(8,48,107)',
    'rgb(3,19,43)'
  ])

const svg = d3
  .select('body')
  .append('svg')
  .attr('width', width)
  .attr('height', height)
  .append('g')
  .attr('class', 'map')

const projection = d3
  .geoRobinson()
  .scale(148)
  .rotate([352, 0, 0])
  .translate([width / 2, height / 2])

const path = d3.geoPath().projection(projection)

svg.call(tip)

queue()
  .defer(d3.json, 'world_countries.json')
  .defer(d3.tsv, 'home-ownership-by-country-wikipedia.tsv')
  .await(ready)

function ready(error, geography, data) {
  console.log('data', data)
  data.forEach(d => {
    console.log('d', d)
    console.log('d[colorVariable]', d[colorVariable])
    d[colorVariable] = Number(d[colorVariable].replace(',', ''))
  })

  const colorVariableValueByID = {}

  data.forEach(d => {
    colorVariableValueByID[d[geoIDVariable]] = d[colorVariable]
  })
  geography.features.forEach(d => {
    d[colorVariable] = colorVariableValueByID[d.id]
  })

  // calculate ckmeans clusters
  // then use the max value of each cluster
  // as a break
  const numberOfClasses = color.range().length - 1
  const ckmeansClusters = ss.ckmeans(
    data.map(d => d[colorVariable]),
    numberOfClasses
  )
  const ckmeansBreaks = ckmeansClusters.map(d => d3.min(d))
  console.log('numberOfClasses', numberOfClasses)
  console.log('ckmeansClusters', ckmeansClusters)
  console.log('ckmeansBreaks', ckmeansBreaks)

  // set the domain of the color scale based on our data
  color.domain(ckmeansBreaks)
  //
  // .domain(jenksNaturalBreaks)
  //
  // .domain(d3.extent(data, d => d[colorVariable]));
  //
  // .domain([
  //   10000,
  //   100000,
  //   500000,
  //   1000000,
  //   5000000,
  //   10000000,
  //   50000000,
  //   100000000,
  //   500000000,
  //   1500000000
  // ]);

  svg
    .append('g')
    .attr('class', 'countries')
    .selectAll('path')
    .data(geography.features)
    .enter()
    .append('path')
    .attr('d', path)
    .style('fill', d => {
      if (typeof colorVariableValueByID[d.id] !== 'undefined') {
        return color(colorVariableValueByID[d.id])
      }
      return 'white'
    })
    .style('fill-opacity', 0.8)
    .style('stroke', d => {
      if (d[colorVariable] !== 0) {
        return 'white'
      }
      return 'lightgray'
    })
    .style('stroke-width', 1)
    .style('stroke-opacity', 0.5)
    // tooltips
    .on('mouseover', function(d) {
      tip.show(d)
      d3.select(this)
        .style('fill-opacity', 1)
        .style('stroke-opacity', 1)
        .style('stroke-width', 2)
    })
    .on('mouseout', function(d) {
      tip.hide(d)
      d3.select(this)
        .style('fill-opacity', 0.8)
        .style('stroke-opacity', 0.5)
        .style('stroke-width', 1)
    })

  svg
    .append('path')
    .datum(topojson.mesh(geography.features, (a, b) => a.id !== b.id))
    .attr('class', 'names')
    .attr('d', path)
}

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<style>
  .names {
    fill: none;
    stroke: #fff;
    stroke-linejoin: round;
  }
  
  /* Tooltip CSS */
  .d3-tip {
    line-height: 1.5;
    font-weight: 400;
    font-family:"avenir next", Arial, sans-serif;
    padding: 6px;
    background: rgba(0, 0, 0, 0.6);
    color: #FFA500;
    border-radius: 1px;
    pointer-events: none;
  }

  /* Creates a small triangle extender for the tooltip */
  .d3-tip:after {      
    box-sizing: border-box;
    display: inline;
    font-size: 8px;
    width: 100%;
    line-height: 1.5;
    color: rgba(0, 0, 0, 0.6);
    position: absolute;
    pointer-events: none;
    
  }

  /* Northward tooltips */
  .d3-tip.n:after {
    content: "\25BC";
    margin: -1px 0 0 0;
    top: 100%;
    left: 0;
    text-align: center;
  }

  /* Eastward tooltips */
  .d3-tip.e:after {
    content: "\25C0";
    margin: -4px 0 0 0;
    top: 50%;
    left: -8px;
  }

  /* Southward tooltips */
  .d3-tip.s:after {
    content: "\25B2";
    margin: 0 0 1px 0;
    top: -8px;
    left: 0;
    text-align: center;
  }

  /* Westward tooltips */
  .d3-tip.w:after {
    content: "\25B6";
    margin: -4px 0 0 -1px;
    top: 50%;
    left: 100%;
  }

  /*    
  text{
    pointer-events:none;
  }
  */

  .details{
    color: white;
  }
</style>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/queue.v1.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<script src="https://d3js.org/d3-geo-projection.v1.min.js"></script>
<script src="d3-tip.js"></script>
<script src='https://unpkg.com/simple-statistics@2.0.0/dist/simple-statistics.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.10.3/babel.min.js'></script>
<script src='./index.js' lang='babel' type='text/babel'>
</script>
</body>
</html>

d3-tip.js

/**
 * d3.tip
 * Copyright (c) 2013 Justin Palmer
 *
 * Tooltips for d3.js SVG visualizations
 */
// eslint-disable-next-line no-extra-semi
;(function(root, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module with d3 as a dependency.
    define([
      'd3-collection',
      'd3-selection'
    ], factory)
  } else if (typeof module === 'object' && module.exports) {
    /* eslint-disable global-require */
    // CommonJS
    var d3Collection = require('d3-collection'),
        d3Selection = require('d3-selection')
    module.exports = factory(d3Collection, d3Selection)
    /* eslint-enable global-require */
  } else {
    // Browser global.
    var d3 = root.d3
    // eslint-disable-next-line no-param-reassign
    root.d3.tip = factory(d3, d3)
  }
}(this, function(d3Collection, d3Selection) {
  // Public - contructs a new tooltip
  //
  // Returns a tip
  return function() {
    var direction = d3TipDirection,
        offset    = d3TipOffset,
        html      = d3TipHTML,
        node      = initNode(),
        svg       = null,
        point     = null,
        target    = null

    function tip(vis) {
      svg = getSVGNode(vis)
      if (!svg) return
      point = svg.createSVGPoint()
      document.body.appendChild(node)
    }

    // Public - show the tooltip on the screen
    //
    // Returns a tip
    tip.show = function() {
      var args = Array.prototype.slice.call(arguments)
      if (args[args.length - 1] instanceof SVGElement) target = args.pop()

      var content = html.apply(this, args),
          poffset = offset.apply(this, args),
          dir     = direction.apply(this, args),
          nodel   = getNodeEl(),
          i       = directions.length,
          coords,
          scrollTop  = document.documentElement.scrollTop ||
            document.body.scrollTop,
          scrollLeft = document.documentElement.scrollLeft ||
            document.body.scrollLeft

      nodel.html(content)
        .style('opacity', 1).style('pointer-events', 'all')

      while (i--) nodel.classed(directions[i], false)
      coords = directionCallbacks.get(dir).apply(this)
      nodel.classed(dir, true)
        .style('top', (coords.top + poffset[0]) + scrollTop + 'px')
        .style('left', (coords.left + poffset[1]) + scrollLeft + 'px')

      return tip
    }

    // Public - hide the tooltip
    //
    // Returns a tip
    tip.hide = function() {
      var nodel = getNodeEl()
      nodel.style('opacity', 0).style('pointer-events', 'none')
      return tip
    }

    // Public: Proxy attr calls to the d3 tip container.
    // Sets or gets attribute value.
    //
    // n - name of the attribute
    // v - value of the attribute
    //
    // Returns tip or attribute value
    // eslint-disable-next-line no-unused-vars
    tip.attr = function(n, v) {
      if (arguments.length < 2 && typeof n === 'string') {
        return getNodeEl().attr(n)
      }

      var args =  Array.prototype.slice.call(arguments)
      d3Selection.selection.prototype.attr.apply(getNodeEl(), args)
      return tip
    }

    // Public: Proxy style calls to the d3 tip container.
    // Sets or gets a style value.
    //
    // n - name of the property
    // v - value of the property
    //
    // Returns tip or style property value
    // eslint-disable-next-line no-unused-vars
    tip.style = function(n, v) {
      if (arguments.length < 2 && typeof n === 'string') {
        return getNodeEl().style(n)
      }

      var args = Array.prototype.slice.call(arguments)
      d3Selection.selection.prototype.style.apply(getNodeEl(), args)
      return tip
    }

    // Public: Set or get the direction of the tooltip
    //
    // v - One of n(north), s(south), e(east), or w(west), nw(northwest),
    //     sw(southwest), ne(northeast) or se(southeast)
    //
    // Returns tip or direction
    tip.direction = function(v) {
      if (!arguments.length) return direction
      direction = v == null ? v : functor(v)

      return tip
    }

    // Public: Sets or gets the offset of the tip
    //
    // v - Array of [x, y] offset
    //
    // Returns offset or
    tip.offset = function(v) {
      if (!arguments.length) return offset
      offset = v == null ? v : functor(v)

      return tip
    }

    // Public: sets or gets the html value of the tooltip
    //
    // v - String value of the tip
    //
    // Returns html value or tip
    tip.html = function(v) {
      if (!arguments.length) return html
      html = v == null ? v : functor(v)

      return tip
    }

    // Public: destroys the tooltip and removes it from the DOM
    //
    // Returns a tip
    tip.destroy = function() {
      if (node) {
        getNodeEl().remove()
        node = null
      }
      return tip
    }

    function d3TipDirection() { return 'n' }
    function d3TipOffset() { return [0, 0] }
    function d3TipHTML() { return ' ' }

    var directionCallbacks = d3Collection.map({
          n:  directionNorth,
          s:  directionSouth,
          e:  directionEast,
          w:  directionWest,
          nw: directionNorthWest,
          ne: directionNorthEast,
          sw: directionSouthWest,
          se: directionSouthEast
        }),
        directions = directionCallbacks.keys()

    function directionNorth() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.n.y - node.offsetHeight,
        left: bbox.n.x - node.offsetWidth / 2
      }
    }

    function directionSouth() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.s.y,
        left: bbox.s.x - node.offsetWidth / 2
      }
    }

    function directionEast() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.e.y - node.offsetHeight / 2,
        left: bbox.e.x
      }
    }

    function directionWest() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.w.y - node.offsetHeight / 2,
        left: bbox.w.x - node.offsetWidth
      }
    }

    function directionNorthWest() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.nw.y - node.offsetHeight,
        left: bbox.nw.x - node.offsetWidth
      }
    }

    function directionNorthEast() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.ne.y - node.offsetHeight,
        left: bbox.ne.x
      }
    }

    function directionSouthWest() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.sw.y,
        left: bbox.sw.x - node.offsetWidth
      }
    }

    function directionSouthEast() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.se.y,
        left: bbox.se.x
      }
    }

    function initNode() {
      var div = d3Selection.select(document.createElement('div'))
      div
        .style('position', 'absolute')
        .style('top', 0)
        .style('opacity', 0)
        .style('pointer-events', 'none')
        .style('box-sizing', 'border-box')

      return div.node()
    }

    function getSVGNode(element) {
      var svgNode = element.node()
      if (!svgNode) return null
      if (svgNode.tagName.toLowerCase() === 'svg') return svgNode
      return svgNode.ownerSVGElement
    }

    function getNodeEl() {
      if (node == null) {
        node = initNode()
        // re-add node to DOM
        document.body.appendChild(node)
      }
      return d3Selection.select(node)
    }

    // Private - gets the screen coordinates of a shape
    //
    // Given a shape on the screen, will return an SVGPoint for the directions
    // n(north), s(south), e(east), w(west), ne(northeast), se(southeast),
    // nw(northwest), sw(southwest).
    //
    //    +-+-+
    //    |   |
    //    +   +
    //    |   |
    //    +-+-+
    //
    // Returns an Object {n, s, e, w, nw, sw, ne, se}
    function getScreenBBox() {
      var targetel   = target || d3Selection.event.target

      while (targetel.getScreenCTM == null && targetel.parentNode == null) {
        targetel = targetel.parentNode
      }

      var bbox       = {},
          matrix     = targetel.getScreenCTM(),
          tbbox      = targetel.getBBox(),
          width      = tbbox.width,
          height     = tbbox.height,
          x          = tbbox.x,
          y          = tbbox.y

      point.x = x
      point.y = y
      bbox.nw = point.matrixTransform(matrix)
      point.x += width
      bbox.ne = point.matrixTransform(matrix)
      point.y += height
      bbox.se = point.matrixTransform(matrix)
      point.x -= width
      bbox.sw = point.matrixTransform(matrix)
      point.y -= height / 2
      bbox.w = point.matrixTransform(matrix)
      point.x += width
      bbox.e = point.matrixTransform(matrix)
      point.x -= width / 2
      point.y -= height / 2
      bbox.n = point.matrixTransform(matrix)
      point.y += height
      bbox.s = point.matrixTransform(matrix)

      return bbox
    }

    // Private - replace D3JS 3.X d3.functor() function
    function functor(v) {
      return typeof v === 'function' ? v : function() {
        return v
      }
    }

    return tip
  }
// eslint-disable-next-line semi
}));

home-ownership-by-country-wikipedia.tsv

Rank	Country	Home ownership  rate(%)	Date of  Information	id	link
1	Romania	96.4	2015[2]	ROU	Romania
2	Singapore	90.7	2017[3]	SGP	Singapore
3	Slovakia	90.3	2014[2]	SVK	Slovakia
4	China	90	2014[4]	CHN	China
5	Cuba	90	2014[5]	CUB	Cuba
6	Croatia	89.7	2014[2]	HRV	Croatia
7	Lithuania	89.4	2015[2]	LTU	Lithuania
8	India	86.6	2011[6]	IND	India
9	Hungary	86.3	2015[2]	HUN	Hungary
10	Russia	84	2012[7]	RUS	Russia
11	Poland	83.5	2014[2]	POL	Poland
12	Oman	83	2014[8]	OMN	Oman
13	Norway	82.8	2015[2]	NOR	Norway
14	Bulgaria	82.3	2015[2]	BGR	Bulgaria
15	Serbia	82	2017[9]	SRB	Serbia
16	Estonia	81.5	2015[2]	EST	Estonia
17	Latvia	80.2	2015[2]	LVA	Latvia
18	Malta	80	2014[2]	173	Malta
19	Mexico	80	2009[10]	MEX	Mexico
20	Thailand	80	2002[11]	THA	Thailand
21	Spain	78.2	2015[2]	ESP	Spain
22	Czech Republic	78	2015[2]	CZE	Czech Republic
23	Iceland	77.8	2015[2]	ISL	Iceland
24	Slovenia	76.2	2015[2]	SVN	Slovenia
25	Trinidad and Tobago	76	2013[12]	TTO	Trinidad and Tobago
26	Portugal	74.9	2014[2]	PRT	Portugal
27	Brazil	74.4	2008[13]	BRA	Brazil
28	Greece	74	2014[2]	GRC	Greece
29	Cyprus	73.1	2014[2]	CYP	Cyprus
30	Italy	72.9	2014[2]	ITA	Italy
31	Finland	72.7	2015[2]	FIN	Finland
32	Luxembourg	72.5	2014[2]	LUX	Luxembourg
33	Belgium	71.3	2016[2]	BEL	Belgium
34	Sweden	70.6	2015[2]	SWE	Sweden
35	Ireland	68.6	2014[14]	IRL	Ireland
36	Netherlands	67.8	2015[2]	NLD	Netherlands
37	Canada	67.6	2013[15]	CAN	Canada
38	Israel	67.3	2014[16]	ISR	Israel
39	Turkey	67.3	2011[17]	TUR	Turkey
40	Australia	65.5	2016[18]	AUS	Australia
41	France	65	2014[2]	FRA	France
42	United States	64.5	2014[19]	USA	United States
43	United Kingdom	63.5	2015[2]	GBR	United Kingdom
44	New Zealand	63.2	2017[20]	NZL	New Zealand
45	Denmark	62.7	2015[2]	DNK	Denmark
46	Japan	61.6	2008[21]	JPN	Japan
47	South Korea	56.8	2015[22]	KOR	South Korea
48	Austria	55	2016[2]	AUT	Austria
49	Germany	51.9	2015[2]	DEU	Germany
50	Hong Kong	51	2014[23]	HKG	Hong Kong
51	Switzerland	43.4	2015[2]	CHE	Switzerland

jenks.js

// # [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)
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);

}

world_population.tsv

id	name	population	
CHN	China	"1330141295"	
IND	India	"1173108018"	
USA	United States	"310232863"	
IDN	Indonesia	"242968342"	
BRA	Brazil	"201103330"	
PAK	Pakistan	"177276594"	
BGD	Bangladesh	"158065841"	
NGA	Nigeria	"152217341"	
RUS	Russia	"139390205"	
JPN	Japan	"126804433"	
MEX	Mexico	"112468855"	
PHL	Philippines	"99900177"	
VNM	Vietnam	"89571130"	
ETH	Ethiopia	"88013491"	
DEU	Germany	"82282988"	
EGY	Egypt	"80471869"	
TUR	Turkey	"77804122"	
COD	"Congo, Democratic Republic of the"	"70916439"	
IRN	Iran	"67037517"	
THA	Thailand	"66404688"	
FRA	France	"64057792"	
GBR	United Kingdom	"61284806"	
ITA	Italy	"58090681"	
MMR	Burma	"53414374"	
ZAF	South Africa	"49109107"	
KOR	"Korea, South"	"48636068"	
UKR	Ukraine	"45415596"	
COL	Colombia	"44205293"	
SDN	Sudan	"41980182"	
TZA	Tanzania	"41892895"	
ARG	Argentina	"41343201"	
ESP	Spain	"40548753"	
KEN	Kenya	"40046566"	
POL	Poland	"38463689"	
DZA	Algeria	"34586184"	
CAN	Canada	"33759742"	
UGA	Uganda	"33398682"	
MAR	Morocco	"31627428"	
PER	Peru	"29907003"	
IRQ	Iraq	"29671605"	
SAU	Saudi Arabia	"29207277"	
AFG	Afghanistan	"29121286"	
NPL	Nepal	"28951852"	
UZB	Uzbekistan	"27865738"	
VEN	Venezuela	"27223228"	
MYS	Malaysia	"26160256"	
GHA	Ghana	"24339838"	
YEM	Yemen	"23495361"	
TWN	Taiwan	"23024956"	
PRK	"Korea, North"	"22757275"	
SYR	Syria	"22198110"	
ROU	Romania	"22181287"	
MOZ	Mozambique	"22061451"	
AUS	Australia	"21515754"	
LKA	Sri Lanka	"21513990"	
MDG	Madagascar	"21281844"	
CIV	Cote d'Ivoire	"21058798"	
CMR	Cameroon	"19294149"	
NLD	Netherlands	"16783092"	
CHL	Chile	"16746491"	
BFA	Burkina Faso	"16241811"	
NER	Niger	"15878271"	
KAZ	Kazakhstan	"15460484"	
MWI	Malawi	"15447500"	
ECU	Ecuador	"14790608"	
KHM	Cambodia	"14753320"	
SEN	Senegal	"14086103"	
MLI	Mali	"13796354"	
GTM	Guatemala	"13550440"	
AGO	Angola	"13068161"	
ZMB	Zambia	"12056923"	
ZWE	Zimbabwe	"11651858"	
CUB	Cuba	"11477459"	
RWA	Rwanda	"11055976"	
GRC	Greece	"10749943"	
PRT	Portugal	"10735765"	
TUN	Tunisia	"10589025"	
TCD	Chad	"10543464"	
BEL	Belgium	"10423493"	
GIN	Guinea	"10324025"	
CZE	Czech Republic	"10201707"	
SOM	Somalia	"10112453"	
BOL	Bolivia	"9947418"	
HUN	Hungary	"9880059"	
BDI	Burundi	"9863117"	
DOM	Dominican Republic	"9794487"	
BLR	Belarus	"9612632"	
HTI	Haiti	"9203083"	
SWE	Sweden	"9074055"	
BEN	Benin	"9056010"	
AZE	Azerbaijan	"8303512"	
AUT	Austria	"8214160"	
HND	Honduras	"7989415"	
CHE	Switzerland	"7623438"	
TJK	Tajikistan	"7487489"	
ISR	Israel	"7353985"	
SRB	Serbia	"7344847"	
BGR	Bulgaria	"7148785"	
HKG	Hong Kong	"7089705"	
LAO	Laos	"6993767"	
LBY	Libya	"6461454"	
JOR	Jordan	"6407085"	
PRY	Paraguay	"6375830"	
TGO	Togo	"6199841"	
PNG	Papua New Guinea	"6064515"	
SLV	El Salvador	"6052064"	
NIC	Nicaragua	"5995928"	
ERI	Eritrea	"5792984"	
DNK	Denmark	"5515575"	
KGZ	Kyrgyzstan	"5508626"	
SVK	Slovakia	"5470306"	
FIN	Finland	"5255068"	
SLE	Sierra Leone	"5245695"	
ARE	United Arab Emirates	"4975593"	
TKM	Turkmenistan	"4940916"	
CAF	Central African Republic	"4844927"	
SGP	Singapore	"4701069"	not listed
NOR	Norway	"4676305"	
BIH	Bosnia and Herzegovina	"4621598"	
GEO	Georgia	"4600825"	
CRI	Costa Rica	"4516220"	
HRV	Croatia	"4486881"	
MDA	Moldova	"4317483"	
NZL	New Zealand	"4252277"	
IRL	Ireland	"4250163"	
COG	"Congo, Republic of the"	"4125916"	
LBN	Lebanon	"4125247"	
PRI	Puerto Rico	"3977663"	
LBR	Liberia	"3685076"	
ALB	Albania	"3659616"	
LTU	Lithuania	"3545319"	
URY	Uruguay	"3510386"	
PAN	Panama	"3410676"	
MRT	Mauritania	"3205060"	
MNG	Mongolia	"3086918"	
OMN	Oman	"2967717"	
ARM	Armenia	"2966802"	
JAM	Jamaica	"2847232"	
KWT	Kuwait	"2789132"	
PSE	West Bank	"2514845"	
LVA	Latvia	"2217969"	
NAM	Namibia	"2128471"	
MKD	Macedonia	"2072086"	
BWA	Botswana	"2029307"	
SVN	Slovenia	"2003136"	
LSO	Lesotho	"1919552"	
GMB	"Gambia, The"	"1824158"	
KWT	Kosovo	"1815048"	
149	Gaza Strip	"1604238"	not listed
GNB	Guinea-Bissau	"1565126"	
GAB	Gabon	"1545255"	
SWZ	Swaziland	"1354051"	
153	Mauritius	"1294104"	not listed
EST	Estonia	"1291170"	
TTO	Trinidad and Tobago	"1228691"	
TLS	Timor-Leste	"1154625"	
CYP	Cyprus	"1102677"	
FJI	Fiji	"957780"	
QAT	Qatar	"840926"	
160	Comoros	"773407"	not listed
GUY	Guyana	"748486"	
DJI	Djibouti	"740528"	
163	Bahrain	"738004"	not listed
BTN	Bhutan	"699847"	
MNE	Montenegro	"666730"	
GNQ	Equatorial Guinea	"650702"	
SLB	Solomon Islands	"609794"	
168	Macau	"567957"	not listed
169	Cape Verde	"508659"	not listed
LUX	Luxembourg	"497538"	
ESH	Western Sahara	"491519"	
SUR	Suriname	"486618"	
173	Malta	"406771"	not listed
174	Maldives	"395650"	not listed
BRN	Brunei	"395027"	
BLZ	Belize	"314522"	
BHS	"Bahamas, The"	"310426"	
ISL	Iceland	"308910"	
179	French Polynesia	"291000"	not listed
180	Barbados	"285653"	not listed
181	Mayotte	"231139"	not listed
NCL	New Caledonia	"229993"	
183	Netherlands Antilles	"228693"	not listed
VUT	Vanuatu	"221552"	
185	Samoa	"192001"	not listed
186	Sao Tome and Principe	"175808"	not listed
187	Saint Lucia	"160922"	not listed
188	Tonga	"122580"	not listed
189	Virgin Islands	"109775"	not listed
190	Grenada	"107818"	not listed
191	"Micronesia, Federated States of"	"107154"	not listed
192	Aruba	"104589"	not listed
193	Saint Vincent and the Grenadines	"104217"	not listed
194	Kiribati	"99482"	not listed
195	Jersey	"91812"	not listed
196	Seychelles	"88340"	not listed
197	Antigua and Barbuda	"86754"	not listed
198	Andorra	"84525"	not listed
199	Isle of Man	"76913"	not listed
DOM	Dominica	"72813"	
201	Bermuda	"68268"	not listed
202	American Samoa	"66432"	not listed
203	Marshall Islands	"65859"	not listed
204	Guernsey	"65632"	not listed
GRL	Greenland	"57637"	
206	Cayman Islands	"50209"	not listed
207	Saint Kitts and Nevis	"49898"	not listed
208	Faroe Islands	"49057"	not listed
209	Northern Mariana Islands	"48317"	not listed
210	Liechtenstein	"35002"	not listed
211	San Marino	"31477"	not listed
212	Monaco	"30586"	not listed
213	Saint Martin	"30235"	not listed
214	Gibraltar	"28877"	not listed
215	British Virgin Islands	"24939"	not listed
216	Turks and Caicos Islands	"23528"	not listed
217	Palau	"20879"	not listed
218	Akrotiri	"15700"	not listed
219	Dhekelia	"15700"	not listed
220	Wallis and Futuna	"15343"	not listed
221	Anguilla	"14764"	not listed
222	Nauru	"14264"	not listed
223	Cook Islands	"11488"	not listed
224	Tuvalu	"10472"	not listed
225	"Saint Helena, Ascension, and Tristan da Cunha"	"7670"	not listed
226	Saint Barthelemy	"7406"	not listed
227	Saint Pierre and Miquelon	"6010"	not listed
228	Montserrat	"5118"	not listed
FLK	Falkland Islands (Islas Malvinas)	"3140"	
230	Norfolk Island	"2155"	not listed
231	Svalbard	"2067"	not listed
232	Christmas Island	"1402"	not listed
233	Tokelau	"1400"	not listed
234	Niue	"1354"	not listed
235	Holy See (Vatican City)	829	not listed
236	Cocos (Keeling) Islands	596	not listed
237	Pitcairn Islands	48	not listed
ATA	Antarctica	0	
ATF	French Southern and Antarctic Lands	0
SDS	South Sudan	"12152321" 
ABV	Somaliland	"3500000"
OSA	Kosovo	"1824000"