block by micahstubbs 7a2cd2e02367c7ac2643cd2198706e50

es2015 clicky label

Full Screen

click on a circle to add a draggable label with a nice swoopy arc connecting the label to the circle

this iteration converts the code to ES2015 in something like the airbnb style

forked from @jburnmurdoch‘s block: bcdb4e85c7523a2b0e64961f0d227154

the tweet

index.html

<!DOCTYPE html>
<html lang='en-US'>
<head>
	<meta charset='UTF-8'>
    <meta name='viewport' content='width=device-width,user-scalable=no'>
	<title>clicky label demo</title>
	<script src='https://unpkg.com/d3/build/d3.min.js' defer></script>
	<script src='https://unpkg.com/d3-swoopy-drag' defer></script>
	<script src='d3-jetpack-module.js' defer></script>
	<script src='https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.23.1/babel.min.js' defer></script>
	<script src='vis.js' defer></script>
	<style media='screen'>
		html, body{
			font-family:Arial,sans-serif;
			font-size: 18px;
		}
		text{
			fill: #000;
			font-size: 18px;
		}
		.heading{
			font-size: 1.15rem;
			font-weight: 600;
		}
		.title{
			font-size: 0.8rem;
		}
		.legend text{
			font-size: 0.7rem;
			fill: #74736c;
		}
		path.domain{
			fill: none;
			stroke: none;
		}
		.tick line{
			shape-rendering: crispEdges;
			stroke: lightgrey;
			stroke-width: 1;
		}
		.tick text{
			font-size: 0.75rem;
		}
		.label{
			font-size: 0.85rem;
		}
	    .shadow{
	    	text-shadow:2px 2px 2px #fff, -2px -2px 2px #fff, -2px 2px 2px #fff, 2px -2px 2px #fff, -2px 0px 2px #fff, 2px 0px 2px #fff;
	    }
	</style>
</head>
<body>
	<svg></svg>
</body>
</html>

d3-jetpack-module.js

/* eslint-disable */
// https://github.com/1wheel/d3-jetpack-module Version 0.0.14. Copyright 2016 Adam Pearce.
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-selection'), require('d3-transition'), require('d3-axis'), require('d3-scale'), require('d3-collection'), require('d3-queue'), require('d3-request')) :
  typeof define === 'function' && define.amd ? define(['exports', 'd3-selection', 'd3-transition', 'd3-axis', 'd3-scale', 'd3-collection', 'd3-queue', 'd3-request'], factory) :
  (factory((global.d3 = global.d3 || {}), global.d3, global.d3, global.d3, global.d3, global.d3, global.d3, global.d3));
}(this, function (exports, d3Selection, d3Transition, d3Axis, d3Scale, d3Collection, d3Queue, d3Request) { 'use strict';

  function translateSelection(xy) {
    return this.attr('transform', function (d, i) {
      return 'translate(' + [typeof xy == 'function' ? xy.call(this, d, i) : xy] + ')';
    });
  }

  function parseAttributes(name) {
    if (typeof name === 'string') {
      var attr = {},
        parts = name.split(/([\.#])/g), p;
      name = parts.shift();
      while ((p = parts.shift())) {
        if (p == '.') attr['class'] = attr['class'] ? attr['class'] + ' ' + parts.shift() : parts.shift();
        else if (p == '#') attr.id = parts.shift();
      }
      return { tag: name, attr };
    }
    return name;
  }

  function append(name) {
    var n = parseAttributes(name), s;
    name = d3Selection.creator(n.tag);
    s = this.select(function () {
      return this.appendChild(name.apply(this, arguments));
    });

    // attrs not provided by default in v4
    for (var key in n.attr) { s.attr(key, n.attr[key]); }
    return s;
  }

  function selectAppend(name) {
    var select = d3Selection.selector(name),
      n = parseAttributes(name), s;

    name = d3Selection.creator(n.tag);

    s = this.select(function () {
      return select.apply(this, arguments)
          || this.appendChild(name.apply(this, arguments));
    });

    // attrs not provided by default in v4
    for (var key in n.attr) { s.attr(key, n.attr[key]); }
    return s;
  }

  function tspans(lines, lh) {
    return this.selectAll('tspan')
        .data(lines).enter()
      .append('tspan')
        .html(function (d) { return d; })
        .attr('x', 0)
        .attr('dy', function (d, i) { return i ? (lh + 'em') || '1em' : 0; });
  }

  function appendMany(data, name) {
    return this.selectAll(null).data(data).enter().append(name);
  }

  function at(name, value) {
    if (typeof(name) == 'object') {
      for (var key in name) {
        this.attr(key.replace(/([a-z\d])([A-Z])/g, '$1-$2').toLowerCase(), name[key]);
      }
      return this;
    } else {
      return arguments.length == 1 ? this.attr(name) : this.attr(name, value);
    }
  }

  function f() {
    var functions = arguments;

    // convert all string arguments into field accessors
    var i = 0, l = functions.length;
    while (i < l) {
      if (typeof(functions[i]) === 'string' || typeof(functions[i]) === 'number') {
        functions[i] = (function (str) { return function (d) { return d[str]; }; })(functions[i]);
      }
      i++;
    }

     // return composition of functions
    return function (d) {
      var i = 0, l = functions.length;
      while (i++ < l) d = functions[i - 1].call(this, d);
      return d;
    };
  }

  f.not = function (d) { return !d; };
  f.run = function (d) { return d(); };
  f.objToFn = function (obj, defaultVal) {
    if (arguments.length == 1) defaultVal = undefined;

    return function (str) {
      return typeof(obj[str]) !== undefined ? obj[str] : defaultVal; };
  };

  function st(name, value) {
    if (typeof(name) == 'object') {
      for (var key in name) {
        addStyle(this, key, name[key]);
      }
      return this;
    } else {
      return arguments.length == 1 ? this.style(name) : addStyle(this, name, value);
    }


    function addStyle(sel, style, value) {
      var style = style.replace(/([a-z\d])([A-Z])/g, '$1-$2').toLowerCase();

      var pxStyles = 'top left bottom right padding-top padding-left padding-bottom padding-right border-top b-width border-left-width border-botto-width m border-right-width  margin-top margin-left margin-bottom margin-right font-size width height stroke-width line-height margin padding border max-width min-width';

      if (~pxStyles.indexOf(style)) {
        sel.style(style, typeof value == 'function' ? f(value, addPx) : addPx(value));
      } else {
        sel.style(style, value);
      }

      return sel;
    }

    function addPx(d) { return d.match ? d : d + 'px'; }
  }

  function wordwrap(line, maxCharactersPerLine) {
    var w = line.split(' '),
      lines = [],
      words = [],
      maxChars = maxCharactersPerLine || 40,
      l = 0;

    w.forEach(function (d) {
      if (l + d.length > maxChars) {
        lines.push(words.join(' '));
        words.length = 0;
        l = 0;
      }
      l += d.length;
      words.push(d);
    });
    if (words.length) {
      lines.push(words.join(' '));
    }
    return lines.filter(function (d) { return d != ''; });
  }

  function ascendingKey(key) {
    return typeof key == 'function' ? function (a, b) {
      return key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : key(a) >= key(b) ? 0 : NaN;
    } : function (a, b) {
      return a[key] < b[key] ? -1 : a[key] > b[key] ? 1 : a[key] >= b[key] ? 0 : NaN;
    };
  }

  function descendingKey(key) {
    return typeof key == 'function' ? function (a, b) {
      return key(b) < key(a) ? -1 : key(b) > key(a) ? 1 : key(b) >= key(a) ? 0 : NaN;
    } : function (a, b) {
      return b[key] < a[key] ? -1 : b[key] > a[key] ? 1 : b[key] >= a[key] ? 0 : NaN;
    };
  }

  function conventions(c) {
    c = c || {};

    c.margin = c.margin || { top: 20, right: 20, bottom: 20, left: 20 }
    ;['top', 'right', 'bottom', 'left'].forEach(function (d) {
      if (!c.margin[d] && c.margin[d] != 0) c.margin[d] = 20;
    });

    c.width = c.width || c.totalWidth - c.margin.left - c.margin.right || 900;
    c.height = c.height || c.totalHeight - c.margin.top - c.margin.bottom || 460;

    c.totalWidth = c.width + c.margin.left + c.margin.right;
    c.totalHeight = c.height + c.margin.top + c.margin.bottom;

    c.parentSel = c.parentSel || d3Selection.select('body');

    c.rootsvg = c.parentSel.append('svg');

    c.svg = c.rootsvg
        .attr('width', c.totalWidth)
        .attr('height', c.totalHeight)
      .append('g')
        .attr('transform', 'translate(' + c.margin.left + ',' + c.margin.top + ')');

    c.x = c.x || d3Scale.scaleLinear().range([0, c.width]);
    c.y = c.y || d3Scale.scaleLinear().range([c.height, 0]);

    c.xAxis = c.xAxis || d3Axis.axisBottom().scale(c.x);
    c.yAxis = c.yAxis || d3Axis.axisLeft().scale(c.y);

    c.drawAxis = function () {
      c.svg.append('g')
          .attr('class', 'x axis')
          .attr('transform', 'translate(0,' + c.height + ')')
          .call(c.xAxis);

      c.svg.append('g')
          .attr('class', 'y axis')
          .call(c.yAxis);
    };

    return c;
  }

  function attachTooltip(sel, tooltipSel, fieldFns) {
    if (!sel.size()) return;

    tooltipSel = tooltipSel || d3Selection.select('.tooltip');

    sel
        .on('mouseover.attachTooltip', ttDisplay)
        .on('mousemove.attachTooltip', ttMove)
        .on('mouseout.attachTooltip', ttHide)
        .on('click.attachTooltip', function (d) { console.log(d); });

    var d = sel.datum();
    fieldFns = fieldFns || d3Collection.keys(d)
        .filter(function (str) {
          return (typeof d[str] != 'object') && (d[str] != 'array');
        })
        .map(function (str) {
          return function (d) { return str + ': <b>' + d[str] + '</b>'; }; });

    function ttDisplay(d) {
      tooltipSel
          .classed('tooltip-hidden', false)
          .html('')
        .appendMany(fieldFns, 'div')
          .html(function (fn) { return fn(d); });

      d3Selection.select(this).classed('tooltipped', true);
    }

    function ttMove(d) {
      var tt = tooltipSel;
      if (!tt.size()) return;
      var e = d3Selection.event,
        x = e.clientX,
        y = e.clientY,
        n = tt.node(),
        nBB = n.getBoundingClientRect(),
        doctop = (window.scrollY) ? window.scrollY : (document.documentElement && document.documentElement.scrollTop) ? document.documentElement.scrollTop : document.body.scrollTop,
        topPos = y + doctop - nBB.height - 18;

      tt.style('top', (topPos < 0 ? 18 + y : topPos) + 'px');
      tt.style('left', Math.min(Math.max(20, (x - nBB.width / 2)), window.innerWidth - nBB.width - 20) + 'px');
    }

    function ttHide(d) {
      tooltipSel.classed('tooltip-hidden', true);

      d3Selection.selectAll('.tooltipped').classed('tooltipped', false);
    }
  }

  function loadData(files, cb) {
    var q = d3Queue.queue();
    files.forEach(function (d) {
      var type = d.split('.').reverse()[0];

      var loadFn = { csv: d3Request.csv, tsv: d3Request.tsv, json: d3Request.json }[type];
      if (!loadFn) return cb(new Error('Invalid type', d));
      q.defer(loadFn, d);
    });
    q.awaitAll(cb);
  }

  function nestBy(array, key) {
    return d3Collection.nest().key(key).entries(array).map(function (d) {
      d.values.key = d.key;
      return d.values;
    });
  }

  function round(n, p) {
    return p ? Math.round(n * (p = Math.pow(10, p))) / p : Math.round(n);
  }

  d3Selection.selection.prototype.translate = translateSelection;
  d3Transition.transition.prototype.translate = translateSelection;
  d3Selection.selection.prototype.append = append;
  d3Selection.selection.prototype.selectAppend = selectAppend;
  d3Selection.selection.prototype.tspans = tspans;
  d3Selection.selection.prototype.appendMany = appendMany;
  d3Selection.selection.prototype.at = at;
  d3Selection.selection.prototype.st = st;
  d3Selection.selection.prototype.prop = d3Selection.selection.prototype.property;

  exports.wordwrap = wordwrap;
  exports.parseAttributes = parseAttributes;
  exports.f = f;
  exports.ascendingKey = ascendingKey;
  exports.descendingKey = descendingKey;
  exports.conventions = conventions;
  exports.attachTooltip = attachTooltip;
  exports.loadData = loadData;
  exports.nestBy = nestBy;
  exports.round = round;

  Object.defineProperty(exports, '__esModule', { value: true });
}));

gapMinder2015.csv

code,shortName,region,lifeEx,gdpPerCap10,gdpPerCapPPP11,population
AFG,Afghanistan,South Asia,60.374,619.751,1808.126,32526562
ALB,Albania,Europe & Central Asia,77.83,4543.088,11015.164,2889167
DZA,Algeria,Middle East & North Africa,74.808,4794.049,13822.566,39666519
ASM,American Samoa,East Asia & Pacific,NA,NA,NA,55538
ADO,Andorra,Europe & Central Asia,NA,NA,NA,70473
AGO,Angola,Sub-Saharan Africa,52.267,4153.146,6937.631,25021974
ATG,Antigua and Barbuda,Latin America & Caribbean,75.938,13708.995,21660.222,91818
ARG,Argentina,Latin America & Caribbean,76.159,10514.588,19126.342,43416755
ARM,Armenia,Europe & Central Asia,74.676,3796.517,7906.917,3017712
ABW,Aruba,Latin America & Caribbean,75.451,NA,NA,103889
AUS,Australia,East Asia & Pacific,82.251,54708.184,43631.236,23781169
AUT,Austria,Europe & Central Asia,81.337,47754.595,44048.431,8611088
AZE,Azerbaijan,Europe & Central Asia,70.763,6115.758,16698.864,9651349
BHS,The Bahamas,Latin America & Caribbean,75.234,20683.869,21602.675,388019
BHR,Bahrain,Middle East & North Africa,76.683,22347.971,43753.898,1377237
BGD,Bangladesh,South Asia,71.626,972.881,3136.561,160995642
BRB,Barbados,Latin America & Caribbean,75.496,15971.015,15408.385,284215
BLR,Belarus,Europe & Central Asia,72.976,6158.995,16661.99,9513000
BEL,Belgium,Europe & Central Asia,80.588,45036.128,41825.875,11285721
BLZ,Belize,Latin America & Caribbean,70.077,4392.547,7967.767,359287
BEN,Benin,Sub-Saharan Africa,59.511,804.724,1931.665,10879829
BMU,Bermuda,North America,80.797,NA,NA,65235
BTN,Bhutan,South Asia,69.471,2668.121,7860.987,774830
BOL,Bolivia,Latin America & Caribbean,68.344,2392.766,6531.074,10724705
BIH,Bosnia and Herzegovina,Europe & Central Asia,76.433,4801.88,10118.77,3810416
BWA,Botswana,Sub-Saharan Africa,64.429,7080.119,14876.362,2262485
BRA,Brazil,Latin America & Caribbean,74.402,11159.254,14454.937,207847528
VGB,British Virgin Islands,Latin America & Caribbean,NA,NA,NA,30117
BRN,Brunei,East Asia & Pacific,78.81,32226.096,73604.88,423188
BGR,Bulgaria,Europe & Central Asia,75.407,7612.025,17000.142,7177991
BFA,Burkina Faso,Sub-Saharan Africa,58.588,644.609,1592.917,18105570
BDI,Burundi,Sub-Saharan Africa,56.692,206.711,682.944,11178921
CPV,Cabo Verde,Sub-Saharan Africa,73.147,3500.205,6157.897,520502
KHM,Cambodia,East Asia & Pacific,68.212,1020.908,3278.221,15577899
CMR,Cameroon,Sub-Saharan Africa,55.493,1303.589,2925.887,23344179
CAN,Canada,North America,81.957,50000.56,42894.667,35851774
CYM,Cayman Islands,Latin America & Caribbean,NA,NA,NA,59967
CAF,Central African Republic,Sub-Saharan Africa,50.658,292.165,581.136,4900274
TCD,Chad,Sub-Saharan Africa,51.556,951.685,2043.544,14037472
CHI,Channel Islands,Europe & Central Asia,80.606,NA,NA,163692
CHL,Chile,Latin America & Caribbean,81.496,14660.505,22197.043,17948141
CHN,China,East Asia & Pacific,75.782,6497.482,13571.686,1371220000
COL,Colombia,Latin America & Caribbean,73.993,7447.877,12988.339,48228704
COM,Comoros,Sub-Saharan Africa,63.257,758.694,1393.256,788474
ZAR,Dem. Rep. Congo,Sub-Saharan Africa,58.659,384.507,736.68,77266814
COG,Congo,Sub-Saharan Africa,62.311,3163.173,5993.153,4620330
CRI,Costa Rica,Latin America & Caribbean,79.403,9237.952,14646.57,4807850
CIV,Côte d'Ivoire,Sub-Saharan Africa,51.56,1496.236,3300.072,22701556
HRV,Croatia,Europe & Central Asia,77.329,13807.347,20663.929,4224404
CUB,Cuba,Latin America & Caribbean,79.391,NA,NA,11389562
CUW,Curaçao,Latin America & Caribbean,77.824,NA,NA,158040
CYP,Cyprus,Europe & Central Asia,80.132,27787.806,30603.531,1165300
CZE,Czech Republic,Europe & Central Asia,78.276,21214.196,30380.591,10551219
DNK,Denmark,Europe & Central Asia,80.549,58098.289,44041.735,5676002
DJI,Djibouti,Middle East & North Africa,62.016,1650.308,3279.124,887861
DMA,Dominica,Latin America & Caribbean,NA,6916.851,10204.036,72680
DOM,Dominican Republic,Latin America & Caribbean,73.5,6552.694,13371.529,10528391
ECU,Ecuador,Latin America & Caribbean,75.872,5366.545,10776.578,16144363
EGY,Egypt,Middle East & North Africa,71.122,2707.086,10249.958,91508084
SLV,El Salvador,Latin America & Caribbean,72.755,3853.108,8095.559,6126583
GNQ,Equatorial Guinea,Sub-Saharan Africa,57.647,19433.267,38243.354,845060
ERI,Eritrea,Sub-Saharan Africa,63.663,NA,NA,NA
EST,Estonia,Europe & Central Asia,77.239,17638.494,27345.247,1311998
ETH,Ethiopia,Sub-Saharan Africa,64.035,486.269,1529.894,99390750
FRO,Faroe Islands,Europe & Central Asia,81.693,NA,NA,48199
FJI,Fiji,East Asia & Pacific,70.089,4349.524,8756.4,892145
FIN,Finland,Europe & Central Asia,81.129,45132.79,38940.93,5482013
FRA,France,Europe & Central Asia,82.373,41533.867,37774.999,66808385
PYF,French Polynesia,East Asia & Pacific,76.542,NA,NA,282764
GAB,Gabon,Sub-Saharan Africa,64.383,10751.935,18860.214,1725292
GMB,The Gambia,Sub-Saharan Africa,60.228,536.278,1577.569,1990924
GEO,Georgia,Europe & Central Asia,74.669,4010.254,9015.888,3679000
DEU,Germany,Europe & Central Asia,80.844,45408.306,43787.816,81413145
GHA,Ghana,Sub-Saharan Africa,61.312,1696.645,3954.524,27409893
GIB,Gibraltar,Europe & Central Asia,NA,NA,NA,32217
GRC,Greece,Europe & Central Asia,81.285,22573.416,24094.794,10823732
GRL,Greenland,Europe & Central Asia,NA,NA,NA,56114
GRD,Grenada,Latin America & Caribbean,73.366,8391.155,12734.262,106825
GUM,Guam,East Asia & Pacific,79.126,NA,NA,169885
GTM,Guatemala,Latin America & Caribbean,71.722,3052.295,7252.95,16342897
GIN,Guinea,Sub-Saharan Africa,58.733,417.097,1135.487,12608590
GNB,Guinea-Bissau,Sub-Saharan Africa,55.16,533.794,1367.299,1844325
GUY,Guyana,Latin America & Caribbean,66.406,3663.418,7064.45,767085
HTI,Haiti,Latin America & Caribbean,62.747,727.783,1650.593,10711067
HND,Honduras,Latin America & Caribbean,73.136,2313.045,4785.444,8075060
HKG,"Hong Kong SAR, China",East Asia & Pacific,83.98,36173.317,53462.863,7305700
HUN,Hungary,Europe & Central Asia,75.873,14516.683,24831.346,9844686
ISL,Iceland,Europe & Central Asia,82.061,45411.045,42324.56,330823
IND,India,South Asia,68.014,1750.623,5729.777,1311050527
IDN,Indonesia,East Asia & Pacific,68.888,3834.056,10385.323,257563815
IRN,Iran,Middle East & North Africa,75.389,NA,NA,79109272
IRQ,Iraq,Middle East & North Africa,69.4,5119.252,14458.854,36423395
IRL,Ireland,Europe & Central Asia,81.154,65292.383,61378.36,4640703
IMY,Isle of Man,Europe & Central Asia,NA,NA,NA,87780
ISR,Israel,Middle East & North Africa,82.154,33116.78,31970.689,8380400
ITA,Italy,Europe & Central Asia,82.69,33849.392,34219.761,60802085
JAM,Jamaica,Latin America & Caribbean,75.654,5000.709,8333.485,2725941
JPN,Japan,East Asia & Pacific,83.588,47150.366,37872.462,126958472
JOR,Jordan,Middle East & North Africa,74.052,3976.043,10239.664,7594547
KAZ,Kazakhstan,Europe & Central Asia,71.62,10616.676,23522.291,17544126
KEN,Kenya,Sub-Saharan Africa,61.576,1133.459,2901.013,46050302
KIR,Kiribati,East Asia & Pacific,65.952,1615.576,1873.497,112423
PRK,Dem. People's Rep. Korea,East Asia & Pacific,70.075,NA,NA,25155317
KOR,Korea,East Asia & Pacific,82.156,25022.802,34386.575,50617045
KSV,Kosovo,Europe & Central Asia,71.098,3796.233,9142.148,1797151
KWT,Kuwait,Middle East & North Africa,74.585,35888.581,70107.458,3892115
KGZ,Kyrgyz Republic,Europe & Central Asia,70.402,1017.147,3224.932,5957000
LAO,Lao PDR,East Asia & Pacific,66.117,1531.221,5345.261,6802023
LVA,Latvia,Europe & Central Asia,74.188,14321.258,23080.361,1978440
LBN,Lebanon,Middle East & North Africa,79.373,7045.494,13089.012,5850743
LSO,Lesotho,Sub-Saharan Africa,49.701,1370.266,2770.2,2135022
LBR,Liberia,Sub-Saharan Africa,60.834,367.165,784.581,4503438
LBY,Libya,Middle East & North Africa,71.716,NA,NA,6278438
LIE,Liechtenstein,Europe & Central Asia,82.261,NA,NA,37531
LTU,Lithuania,Europe & Central Asia,73.966,15231.267,26807.182,2910199
LUX,Luxembourg,Europe & Central Asia,82.207,106408.574,92468.14,569676
MAC,"Macao SAR, China",East Asia & Pacific,80.553,55860.177,104718.262,587606
MKD,Macedonia,Europe & Central Asia,75.342,5093.836,12732.403,2078453
MDG,Madagascar,Sub-Saharan Africa,65.086,409.924,1376.26,24235390
MWI,Malawi,Sub-Saharan Africa,62.722,493.663,1111.649,17215232
MYS,Malaysia,East Asia & Pacific,74.718,10878.389,25311.907,30331007
MDV,Maldives,South Asia,76.773,7221.621,11993.911,409163
MLI,Mali,Sub-Saharan Africa,57.986,720.81,1904.857,17599694
MLT,Malta,Middle East & North Africa,81.746,24351.432,32719.946,431333
MHL,Marshall Islands,East Asia & Pacific,NA,3344.566,3673.154,52993
MRT,Mauritania,Sub-Saharan Africa,63.017,NA,NA,4067564
MUS,Mauritius,Sub-Saharan Africa,74.194,9468.941,18864.106,1262605
MEX,Mexico,Latin America & Caribbean,76.722,9510.596,16490.346,127017224
FSM,Micronesia,East Asia & Pacific,69.101,2793.358,3284.135,104460
MDA,Moldova,Europe & Central Asia,71.456,1978.256,4742.019,3554150
MCO,Monaco,Europe & Central Asia,NA,NA,NA,37731
MNG,Mongolia,East Asia & Pacific,69.464,3946.281,11477.827,2959134
MNE,Montenegro,Europe & Central Asia,76.181,7259.986,15254.307,622388
MAR,Morocco,Middle East & North Africa,74.016,3239.552,7364.766,34377511
MOZ,Mozambique,Sub-Saharan Africa,55.026,511.467,1119.698,27977863
MMR,Myanmar,East Asia & Pacific,65.858,1308.747,4930.59,53897154
NAM,Namibia,Sub-Saharan Africa,64.68,6000.038,9778.405,2458830
NRU,Nauru,East Asia & Pacific,NA,11158.993,14974.753,10222
NPL,Nepal,South Asia,69.605,689.514,2312.394,28513700
NLD,Netherlands,Europe & Central Asia,81.305,51268.471,46353.852,16936520
NCL,New Caledonia,East Asia & Pacific,77.573,NA,NA,273000
NZL,New Zealand,East Asia & Pacific,81.405,36801.396,35158.641,4595700
NIC,Nicaragua,Latin America & Caribbean,74.81,1849.029,4884.151,6082032
NER,Niger,Sub-Saharan Africa,61.458,383.831,897.395,19899120
NGA,Nigeria,Sub-Saharan Africa,52.754,2548.174,5638.887,182201962
MNP,Northern Mariana Islands,East Asia & Pacific,NA,NA,NA,55070
NOR,Norway,Europe & Central Asia,81.751,89492.841,63649.505,5195921
OMN,Oman,Middle East & North Africa,77.085,15965.94,37541.075,4490541
PAK,Pakistan,South Asia,66.183,1142.752,4706.185,188924874
PLW,Palau,East Asia & Pacific,NA,10410.088,14385.969,21291
PAN,Panama,Latin America & Caribbean,77.595,10750.937,20885.339,3929141
PNG,Papua New Guinea,East Asia & Pacific,62.607,NA,NA,7619321
PRY,PY,Latin America & Caribbean,72.922,3822.861,8639.282,6639123
PER,Peru,Latin America & Caribbean,74.526,5934.548,11767.522,31376670
PHL,Philippines,East Asia & Pacific,68.266,2639.868,6938.21,100699395
POL,Poland,Europe & Central Asia,77.254,14650.116,25322.538,37999494
PRT,Portugal,Europe & Central Asia,80.722,21960.475,26513.983,10348648
PRI,Puerto Rico,Latin America & Caribbean,79.375,NA,NA,3474182
QAT,Qatar,Middle East & North Africa,78.597,74686.617,132937.666,2235355
ROM,Romania,Europe & Central Asia,75.063,9530.657,20483.775,19832389
RUS,Russia,Europe & Central Asia,70.366,11038.811,23895.341,144096812
RWA,Rwanda,Sub-Saharan Africa,63.966,689.688,1655.175,11609666
WSM,Samoa,East Asia & Pacific,73.512,3641.009,5574.068,193228
SMR,San Marino,Europe & Central Asia,NA,NA,NA,31781
STP,São Tomé and Principe,Sub-Saharan Africa,66.385,1292.872,3022.902,190344
SAU,Saudi Arabia,Middle East & North Africa,74.337,21312.807,50283.934,31540372
SEN,Senegal,Sub-Saharan Africa,66.373,1042.495,2273.624,15129273
SRB,Serbia,Europe & Central Asia,75.534,5661.107,13277.752,7098247
SYC,Seychelles,Sub-Saharan Africa,73.229,13617.884,25524.955,92900
SLE,Sierra Leone,Sub-Saharan Africa,50.879,490.564,1473.913,6453184
SGP,Singapore,East Asia & Pacific,82.646,51855.079,80191.539,5535002
SXM,Sint Maarten (Dutch part),Latin America & Caribbean,NA,NA,NA,38817
SVK,Slovak Republic,Europe & Central Asia,76.715,18643.147,28254.256,5424050
SVN,Slovenia,Europe & Central Asia,80.52,23778.561,29097.343,2063768
SLB,Solomon Islands,East Asia & Pacific,67.931,1475.36,2067.13,583591
SOM,Somalia,Sub-Saharan Africa,55.355,NA,NA,10787104
ZAF,South Africa,Sub-Saharan Africa,57.182,7593.358,12393.255,54956920
SSD,South Sudan,Sub-Saharan Africa,55.682,717.696,1741.098,12339812
ESP,Spain,Europe & Central Asia,83.078,30587.551,32329.588,46418269
LKA,Sri Lanka,South Asia,74.795,3637.539,11047.667,20966000
KNA,St. Kitts and Nevis,Latin America & Caribbean,NA,15080.773,23562.942,55572
LCA,St. Lucia,Latin America & Caribbean,75.047,6822.606,10278.98,184999
MAF,St. Martin (French part),Latin America & Caribbean,79.322,NA,NA,31754
VCT,St. Vincent and the Grenadines,Latin America & Caribbean,72.937,6575.445,10462.41,109462
SDN,Sudan,Sub-Saharan Africa,63.459,1807.663,4121.137,40234882
SUR,Suriname,Latin America & Caribbean,71.151,9114.883,15687.232,542975
SWZ,Swaziland,Sub-Saharan Africa,48.935,4057.284,8122.049,1286970
SWE,Sweden,Europe & Central Asia,81.956,55186.039,45505.303,9798871
CHE,Switzerland,Europe & Central Asia,82.849,75531.472,56517.452,8286976
SYR,Syrian Arab Republic,Middle East & North Africa,70.071,NA,NA,18502413
TJK,Tajikistan,Europe & Central Asia,69.598,932.911,2661.385,8481855
TZA,Tanzania,Sub-Saharan Africa,64.944,842.374,2510.036,53470420
THA,Thailand,East Asia & Pacific,74.422,5775.137,15346.647,67959359
TMP,Timor-Leste,East Asia & Pacific,68.259,983.508,2253.168,1245015
TGO,Togo,Sub-Saharan Africa,59.656,553.86,1371.564,7304578
TON,Tonga,East Asia & Pacific,72.792,3700.243,5198.345,106170
TTO,Trinidad and Tobago,Latin America & Caribbean,70.441,16696.03,31283.55,1360088
TUN,Tunisia,Middle East & North Africa,74.144,4328.542,10769.933,11107800
TUR,Turkey,Europe & Central Asia,75.164,11522.705,19460.482,78665830
TKM,Turkmenistan,Europe & Central Asia,65.599,6932.844,15527.404,5373502
TCA,Turks and Caicos Islands,Latin America & Caribbean,NA,NA,NA,34339
TUV,Tuvalu,East Asia & Pacific,NA,3706.314,3687.484,9916
UGA,Uganda,Sub-Saharan Africa,58.466,672.809,1738.462,39032383
UKR,Ukraine,Europe & Central Asia,71.187,2825.85,7456.93,45198200
ARE,United Arab Emirates,Middle East & North Africa,77.368,39313.274,65716.984,9156963
GBR,United Kingdom,Europe & Central Asia,81.056,41187.684,38519.491,65138232
USA,United States,North America,78.941,51638.065,52704.199,321418820
URY,Uruguay,Latin America & Caribbean,76.986,13943.904,19952.253,3431555
UZB,Uzbekistan,Europe & Central Asia,68.339,1856.719,5716.496,31299500
VUT,Vanuatu,East Asia & Pacific,71.918,2823.185,2806.27,264652
VEN,Venezuela,Latin America & Caribbean,74.236,NA,NA,31108083
VNM,Vietnam,East Asia & Pacific,75.629,1684.866,5667.411,91703800
VIR,Virgin Islands,Latin America & Caribbean,79.773,NA,NA,103574
WBG,West Bank and Gaza,Middle East & North Africa,72.904,2648.974,4714.925,4422143
YEM,Yemen,Middle East & North Africa,63.818,774.45,2649.313,26832215
ZMB,Zambia,Sub-Saharan Africa,60.047,1607.358,3602.327,16211767
ZWE,Zimbabwe,Sub-Saharan Africa,57.498,814.56,1677.97,15602751

vis.js

/* global d3 */

const width = 600;
const height = 500;
const labels = [];

const margin = {
  left: 20,
  top: 80,
  right: 0,
  bottom: 30,
};

function calculateDistance(x1, y1, x2, y2) {
  return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
}

const svg = d3.select('svg')
  .at({
    width: `${width}px`,
    height: `${height}px`,
  })
  .st({
    width,
    height,
  });

const heading = svg.append('text.heading')
  .translate([0, 16])
  .tspans(['A snapshot of global socio-economic development in 2016,', 'as measured by GDP per capita and life expectancy'], 1.1);

d3.csv('gapMinder2015.csv', prepare, render);

function prepare(d) {
  d.label = d.shortName;
  d.region = d.region;
  d.lifeEx = +d.lifeEx;
  d.gdpPerCap10 = +d.gdpPerCap10;
  d.gdpPerCapPPP11 = +d.gdpPerCapPPP11;
  d.population = +d.population;
  return d;
}

function render(data) {
  data = data.filter(d => !isNaN(d.gdpPerCap10) && !isNaN(d.lifeEx));
  // console.log(data);

  // Define and draw x axis
  const x = d3.scaleLog()
    .base(10)
    .range([margin.left, width - margin.right])
    .domain(d3.extent(data, d => d.gdpPerCap10));

  const xAxis = d3.axisTop()
    .scale(x)
    .ticks(5)
    .tickFormat(d3.format(','))
    .tickSize(-(height - (margin.bottom + margin.top)));

  const xDraw = svg.append('g.axis.x')
    .translate([0, margin.top])
    .call(xAxis);

    // Show/hide and style ticks to make the log scale clear
  d3.selectAll('.x .tick').filter(d => [100, 300, 1000, 3000, 10000, 30000].indexOf(d) < 0)
    .selectAll('text, line')
    .st({
      'stroke-dasharray': '2 2',
    })
    .text('');

    // Add an x-axis title
  const xTitle = svg.append('text.x.title.shadow')
    .translate([x(300) - 11, margin.top + 13])
    .html('GDP per capita (constant 2010 US$) &rarr;');

    // Define and draw y axis
  const y = d3.scaleLinear()
    .range([height - margin.bottom, margin.top])
    .domain(d3.extent(data, d => d.lifeEx));

  const yAxis = d3.axisLeft()
    .scale(y)
    .ticks(5)
    .tickSize(-(width - (margin.left + margin.right)));

  const yDraw = svg.append('g.axis.y')
    .translate([margin.left, 0])
    .call(yAxis);

    // Add a y-axis title
  const yTitle = svg.append('text.y.title.shadow')
    .translate([margin.left + 2, y(79) - 6])
    .tspans(['Life', 'expectancy', 'at birth', '(years)', '&darr;'], 1.1);

    // Define a circle area scale
  const areaScale = d3.scaleSqrt()
    .range([0, 35])
    .domain([0, d3.max(data, d => d.population)]);

  const pops = data.map(d => d.population).sort((a, b) => a - b);

    // Draw an area legend
  const areaLegend = svg.append('g.legend').translate([width - 85, height - 180]);
  const areaGroups = areaLegend.selectAll('g')
    .data([10000000, 100000000, 500000000])
    .enter()
    .append('g');
  const areaCircles = areaGroups.append('circle')
    .at({
      cy: d => -areaScale(d),
      r: d => areaScale(d),
      fill: 'none',
      stroke: '#74736c',
    });
  const areaLines = areaGroups.append('line')
    .at({
      y1: d => -2 * areaScale(d),
      y2: d => -2 * areaScale(d),
      x2: 50,
      stroke: '#74736c',
    });
  const areaNames = areaGroups.append('text.shadow')
    .at({
      y: d => -2 * areaScale(d) + 5,
      x: 50,
    })
    .html(d => `${d / 1000000}m`);

  // Define a region colour scale
  const colours = d3.scaleOrdinal()
    .range(['#a6cee3', '#1f78b4', '#b2df8a', '#33a02c', '#fb9a99', '#e31a1c', '#fdbf6f'])
    .domain(data.map(d => d.region).filter((element, index, array) => array.indexOf(element) === index));

  // Draw a colour legend
  const colourLegend = svg.append('g.legend').translate([width - 155, height - 150]);
  const colourGroups = colourLegend.selectAll('g')
    .data(colours.domain())
    .enter()
    .append('g')
    .translate((d, i) => [0, i * 20]);
  const colourNames = colourGroups.append('text.shadow')
    .html(d => d);
  const colourDots = colourGroups.append('circle')
    .at({
      cx: -8,
      cy: -5,
      r: 5,
      fill: d => colours(d),
    });

  // Draw and position a group element for each country
  const countries = svg.selectAll('g.country')
    .data(data)
    .enter()
    .append('g.country')
    .translate(d => [x(d.gdpPerCap10), y(d.lifeEx)]);

  // Draw a bubble for each country
  const bubbles = countries.append('circle')
    .at({
      r: d => areaScale(d.population),
    })
    .st({
      fill: d => colours(d.region),
      'fill-opacity': 0.6,
      stroke: d => colours(d.region),
    });

  labels.forEach(d => {
    countries.filter(v => v.label === d.label)
      .selectAll('text.label')
      .data([d])
      .enter()
      .append('text.label.shadow')
      .html(d.label);

    countries.selectAll('.label').filter(v => v.label === d.label)
      .translate(d.translate);

    countries.filter(v => v.label === d.label)
      .selectAll('path')
      .data([d])
      .enter()
      .insert('path', 'text')
      .attr('d', d.path)
      .style('fill', 'none')
      .style('stroke', '#74736c');
  });

  countries.on('click', function (d) {
    const selection = d3.select(this);

    selection
      .selectAll('text.label')
      .data([d])
      .enter()
      .append('text.label.shadow')
      .html(d.label);

    if (labels.map(a => a.label).indexOf(selection.data()[0].label) < 0) {
      labels.push({
        label: selection.data()[0].label,
      });
    }

    countries.selectAll('.label')
      .st({ cursor: 'pointer' })
      .call(
        d3.drag()
          .on('drag', function () {
            let path;
            const selection = d3.select(this);
            const dataPoint = data[data.map(a => a.label).indexOf(selection.data()[0].label)];

            selection
              .translate([d3.event.x, d3.event.y])
              .st({ cursor: 'crosshair' });

            if (
              calculateDistance(
                d3.mouse(svg.node())[0],
                d3.mouse(svg.node())[1],
                x(dataPoint.gdpPerCap10),
                y(dataPoint.lifeEx)
              ) > areaScale(dataPoint.population)
            ) {
              path = d3.select(selection.node().parentNode)
                .selectAll('path')
                .data([`M ${d3.event.x + selection.node().getBoundingClientRect().width / 2}, ${d3.event.y} A 30 30 0 0 0 0,0`], p => p);

              path
                .attr('d', p => p)
                .style('fill', 'none')
                .style('stroke', '#74736c');

              path.exit().remove();

              path.enter()
                .insert('path', 'text')
                .attr('d', p => p)
                .style('fill', 'none')
                .style('stroke', '#74736c');
            } else {
              path = d3.select(selection.node().parentNode)
                .selectAll('path')
                .remove();
            }
          })
          .on('end', function () {
            const selection = d3.select(this);
            const dataPoint = data[data.map(a => a.label).indexOf(selection.data()[0].label)];

            selection
              .st({ cursor: 'pointer' });
            if (labels.map(a => a.label).indexOf(selection.data()[0].label) >= 0) {
              labels[labels.map(a => a.label).indexOf(selection.data()[0].label)].translate = [d3.event.x, d3.event.y];

              if (calculateDistance(d3.mouse(svg.node())[0], d3.mouse(svg.node())[1], x(dataPoint.gdpPerCap10), y(dataPoint.lifeEx)) > areaScale(dataPoint.population)) {
                labels[labels.map(a => a.label).indexOf(selection.data()[0].label)].path = d3.select(selection.node().parentNode).select('path').attr('d');
              } else {
                delete labels[labels.map(a => a.label).indexOf(selection.data()[0].label)].path;
              }
            } else {
              labels.push({
                label: selection.data()[0].label,
                translate: [d3.event.x, d3.event.y],
                path: d3.select(selection.node().parentNode).select('path').attr('d'),
              });
            }
          })
        );
  });

  countries.selectAll('.label')
      .st({ cursor: 'pointer' })
      .call(
        d3.drag()
          .on('drag', function () {
            let path;
            const selection = d3.select(this);
            const dataPoint = data[data.map(a => a.label).indexOf(selection.data()[0].label)];

            selection
              .translate([d3.event.x, d3.event.y])
              .st({ cursor: 'crosshair' });

            if (
              calculateDistance(
                d3.mouse(svg.node())[0],
                d3.mouse(svg.node())[1],
                x(dataPoint.gdpPerCap10),
                y(dataPoint.lifeEx)
              ) > areaScale(dataPoint.population)
            ) {
              path = d3.select(selection.node().parentNode)
                .selectAll('path')
                .data([`M ${d3.event.x + selection.node().getBoundingClientRect().width / 2}, ${d3.event.y} A 30 30 0 0 0 0,0`], p => p);

              path
                .attr('d', p => p)
                .style('fill', 'none')
                .style('stroke', '#74736c');

              path.exit().remove();

              path.enter()
                .insert('path', 'text')
                .attr('d', p => p)
                .style('fill', 'none')
                .style('stroke', '#74736c');
            } else {
              path = d3.select(selection.node().parentNode)
                .selectAll('path')
                .remove();
            }
          })
          .on('end', function () {
            const selection = d3.select(this);
            const dataPoint = data[data.map(a => a.label).indexOf(selection.data()[0].label)];

            selection
              .st({ cursor: 'pointer' });
            if (labels.map(a => a.label).indexOf(selection.data()[0].label) >= 0) {
              labels[labels.map(a => a.label).indexOf(selection.data()[0].label)].translate = [d3.event.x, d3.event.y];

              if (calculateDistance(d3.mouse(svg.node())[0], d3.mouse(svg.node())[1], x(dataPoint.gdpPerCap10), y(dataPoint.lifeEx)) > areaScale(dataPoint.population)) {
                labels[labels.map(a => a.label).indexOf(selection.data()[0].label)].path = d3.select(selection.node().parentNode).select('path').attr('d');
              } else {
                delete labels[labels.map(a => a.label).indexOf(selection.data()[0].label)].path;
              }
            } else {
              labels.push({
                label: selection.data()[0].label,
                translate: [d3.event.x, d3.event.y],
                path: d3.select(selection.node().parentNode).select('path').attr('d'),
              });
            }
          })
        );
}