<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8" />
    <title>Rotating Donut</title>
    <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1" />
    <script data-require="d3@4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
    <script src="data.js"></script>
    <script src="rotating_donut.js"></script>
    <link rel="stylesheet" href="button.css" />
    <link rel="stylesheet" href="style.css" />
  </head>

  <body>
    <div class="donuts">
      <div class="donut" id="donut1"></div>
      <div class="slider">
        <label for="donut-size-1">Size</label>
        <input type="range" class="donut-size" id="donut-size-1" title="donut-size" data-target="#donut1" value="100" max="150" />
      </div>
      <div class="donut" id="donut2"></div>
      <div class="slider">
        <label for="donut-size-2">Size</label>
        <input type="range" class="donut-size" id="donut-size-2" title="donut-size" data-target="#donut2" max="150" value="150" />
      </div>
    </div>
    <button>Randomize Data</button>
    <script src="app.js"></script>
  </body>

</html>
body {
    display: flex;
    font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
    font-weight: 300;
    height: 600px;
    margin: 8px;
}
.donuts {
    display: inline-block;
    position: relative;
}
.donuts {
    width: 300px;
    text-align: right;
    overflow: hidden;
}
.donut {
    margin-left: auto;
}
#donut1 {
    width: 200px;
    height: 200px;
}
#donut2 {
    width: 300px;
    height: 300px;
}

.donut-label {
    font-weight: bold;
}

.slider {
    display: inline-block;
    height: 20px;
    margin: 4px auto 16px;
}
#donut-size {
    margin-left: 8px;
}
if(typeof APP === 'undefined') {APP = {};}
APP.generateData = function(splice) {
  'use strict';
  var colors = d3.scaleOrdinal(d3.schemeCategory10),
      arr = [],
      i;

  for (i = 1; i <= 5; i++) {
    arr.push({
      id: i,
      value: 5 + Math.random() * 15,
      color: colors(i)
    });
  }
  if (splice) {
    arr.sort(function() {return 0.5 - Math.random();})
        .splice(0, Math.random() * 5);
  }
  return arr;
};
document.addEventListener('DOMContentLoaded', function() {
  'use strict';
  var donut,
      events;

  function build() {
    donut = APP.rotatingDonut()
        .thickness(0.5)
        .value(function(d) {return d.value;})
        .color(function(d) {return d.color;})
        .key(function(d) {return d.id;})
        .sort(function(a, b) {return a.id - b.id;});
  }

  function addToDom() {
    d3.select('#donut1')
        .datum(APP.generateData())
        .call(donut.label, 'Smith')
        .call(donut);

    d3.select('#donut2')
        .datum(APP.generateData())
        .call(donut.label, 'Jones')
        .call(donut);
  }

  function addListeners() {
    d3.select('button').on('click', events.dataButtonClick);
    d3.selectAll('.donut-size').on('change', events.resizeSliderChange);
  }

  events = {
    dataButtonClick: function() {
      d3.select('#donut1')
          .datum(APP.generateData(true))
          .call(donut);

      d3.select('#donut2')
          .datum(APP.generateData(true))
          .call(donut);
    },

    resizeSliderChange: function() {
      var target = d3.select(this).attr('data-target'),
          value = this.value * 2;

      d3.selectAll(target)
          .call(donut.dimensions, {width: value, height: value})
          .call(donut)
          .style('width', value + 'px')
          .style('height', value + 'px');
    }
  };

  build();
  addToDom();
  addListeners();
});
if(typeof APP === 'undefined') {APP = {};}
APP.rotatingDonut = function() {
  'use strict';
  var o,
      local;

  o = {
    thickness: 0.4,
    value: null,
    color: null,
    key: null,
    sort: null
  };

  local = {
    label: d3.local(),
    dimensions: d3.local()
  };

  function donut(group) {
    group.each(render);
  }

  function render(data) {
    var context,
        dim,
        pie,
        arc,
        segments,
        segmentEnter;

    if (!data) {return;}

    context = d3.select(this);
    dim = getDimensions(context);

    pie = d3.pie()
        .value(o.value)
        .sort(null);

    arc = d3.arc()
        .outerRadius(dim.outerRadius)
        .innerRadius(dim.innerRadius);

    context.selectAll('svg')
        .data([pie(data.sort(o.sort))])
        .enter()
        .append('svg')
        .append('g')
        .attr('class', 'group')
        .append('text')
        .attr('class', 'donut-label')
        .attr('text-anchor', 'middle')
        .attr('dominant-baseline', 'middle');

    context.selectAll('svg')
        .attr('width', dim.width)
        .attr('height', dim.height)
        .selectAll('g.group')
        .attr('transform', 'translate(' + dim.width / 2 + ',' + dim.height / 2 + ')');

    context.select('text.donut-label')
        .text(local.label.get(context.node()));

    segments = context.selectAll('svg')
        .select('g.group')
        .selectAll('path.segment')
        .data(Object, dataAccess('key'));

    segmentEnter = segments.enter()
        .append('path')
        .attr('class', 'segment')
        .attr('fill', dataAccess('color'));

    segmentEnter
        .merge(segments)
        .attr('d', arc);

    segments.exit()
        .remove();
  }

  function dataAccess(key) {
    return function(d) {
      return o[key](d.data);
    };
  }

  function getDimensions(context) {
    var thisDimensions = local.dimensions.get(context.node()) || {},
        width = thisDimensions.width || context.node().getBoundingClientRect().width,
        height = thisDimensions.height || context.node().getBoundingClientRect().height,
        outerRadius = Math.min(width, height) / 2,
        innerRadius = outerRadius * (1 - o.thickness);

    return {
      width: width,
      height: height,
      outerRadius: outerRadius,
      innerRadius: innerRadius
    };
  }

  donut.thickness = function(_) {
    if (!arguments.length) {return o.thickness;}
    o.thickness = _;
    return donut;
  };
  donut.value = function(_) {
    if (!arguments.length) {return o.value;}
    o.value = _;
    return donut;
  };
  donut.color = function(_) {
    if (!arguments.length) {return o.color;}
    o.color = _;
    return donut;
  };
  donut.key = function(_) {
    if (!arguments.length) {return o.key;}
    o.key = _;
    return donut;
  };
  donut.sort = function(_) {
    if (!arguments.length) {return o.sort;}
    o.sort = _;
    return donut;
  };

  donut.dimensions = function(context, _) {
    var returnArray;
    if (typeof _ === 'undefined' ) {
      returnArray = context.nodes()
          .map(function (node) {return local.dimensions.get(node);});
      return context._groups[0] instanceof NodeList ? returnArray : returnArray[0];
    }
    context.each(function() {local.dimensions.set(this, _);});
    return donut;
  };
  donut.label = function(context, _) {
    var returnArray;
    if (typeof _ === 'undefined' ) {
      returnArray = context.nodes()
          .map(function (node) {return local.label.get(node);});
      return context._groups[0] instanceof NodeList ? returnArray : returnArray[0];
    }
    context.each(function() {local.label.set(this, _);});
    return donut;
  };

  return donut;
};
button {
    background-color: #eee;
    border: 1px solid #ddd;
    border-radius: 5px;
    font-size: 11px;
    padding: 6px 10px;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    cursor: pointer;
    position: absolute;
    right: 16px;
}
button:hover {
    background-color: #ddd;
    border-color: #ccc
}
button:active {
    background-color: #ccc;
}
button:focus {
    outline:0;
}