<!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>
  <script src="pie_selection_rotation.js"></script>
  <script src="pie_transitions.js"></script>
  <script src="pie_icons.js"></script>
  <script src="basic_legend.js"></script>
  <script src="description_with_arrow.js"></script>

  <link rel="stylesheet" href="button.css">
  <link rel="stylesheet" href="description.css">
  <link rel="stylesheet" href="legend.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>

  <div class="descriptions">
    <button>Randomize Data</button>
    <div class="description" id="description1" data-target="#donut1"></div>
    <div class="description" id="description2" data-target="#donut2"></div>
  </div>

  <div id="legend"></div>

</body>

<script src="app.js"></script>

</html>
document.addEventListener('DOMContentLoaded', function() {
  'use strict';
  var donut,
      legend,
      description,
      events;

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

    legend = APP.basicLegend()
        .label(function(d) {return d.label;})
        .color(function(d) {return d.color;})
        .key(function(d) {return d.id;});

    description = APP.descriptionWithArrow()
        .label(function(d) {return formatDollar((d || {}).value);})
        .text(function(d) {return d ? d.description : 'no data for selection';});
  }

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

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

    d3.select('#legend')
        .datum(APP.generateData())
        .call(legend);
  }

  function addListeners() {
    donut.on('click', events.donutClick)
        .on('mouseenter', events.donutMouseEnter)
        .on('mouseleave', events.donutMouseLeave);
    legend.on('click', events.legendClick);
    d3.select('button').on('click', events.dataButtonClick);
    d3.selectAll('.donut-size').on('change', events.resizeSliderChange);
  }

  function setDescriptions() {
    d3.select('#description1')
        .datum(donut.selectedSegment(d3.select('#donut1')))
        .call(description);

    d3.select('#description2')
        .datum(donut.selectedSegment(d3.select('#donut2')))
        .call(description);
  }

  function formatDollar(num) {
    return typeof num === 'number' ? '$' + num.toFixed(2) : '';
  }

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

      d3.select('#donut2')
          .datum(APP.generateData(true))
          .transition()
          .delay(400)
          .duration(200)
          .call(donut);

      setDescriptions();
    },

    donutClick: function(d) {
      var container = this;

      d3.selectAll('.donut')
          .filter(function() {return this !== container;})
          .call(donut.selectedSegment, d)
          .call(donut);

      d3.select('#legend')
          .call(legend.selectedItem, d)
          .call(legend);

      setDescriptions();
    },

    donutMouseEnter: function(d) {
      d3.select('#legend')
          .call(legend.highlight, d)
    },

    donutMouseLeave: function(d) {
      d3.select('#legend')
          .call(legend.unhighlight, d)
    },

    legendClick: function(d) {
      d3.selectAll('.donut')
          .call(donut.selectedSegment, d)
          .call(donut);

      setDescriptions();
    },

    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)
          .transition()
          .duration(donut.animationDuration())
          .style('width', value + 'px')
          .style('height', value + 'px')
          .each(transitionDonutDescription);

      function transitionDonutDescription() {
        d3.select('.description[data-target="' + target + '"]')
            .transition()
            .duration(donut.animationDuration())
            .style('height', value + 'px');
      }
    }
  };

  build();
  addToDom();
  addListeners();
});
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;
}
path {
    cursor: pointer;
}
image {
    pointer-events: none;
}
.donuts {
    display: inline-block;
    position: relative;
}
.donuts {
    width: 300px;
    text-align: right;
    overflow: hidden;
}
.donut {
    margin-left: auto;
}
#donut1 {
    width: 200px;
}
#donut2 {
    width: 300px;
    height: 300px;
}
#donut1,
#description1 {
    height: 200px;
}
#donut2,
#description2 {
    height: 300px;
}
.donut-label {
    font-weight: bold;
}

.descriptions {
    width: 300px;
    position: relative;
}

.slider {
    display: inline-block;
    height: 20px;
    margin: 4px auto 16px;
}
#donut-size {
    margin-left: 8px;
}
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;
    left: 50%;
    transform: translateX(-50%);
}
button:hover {
    background-color: #ddd;
    border-color: #ccc
}
button:active {
    background-color: #ccc;
}
button:focus {
    outline:0;
}
.description {
    display: flex;
    flex-direction: row;
    margin-bottom: 40px;
}
.description .desc-left,
.description .desc-right {
    display: flex;
    flex-direction: column;
    justify-content: center;
}
.description .arrows {
    display: inline-block;
    position: relative;
}
.description .arrow {
    font-size: 24px;
    line-height: 24px;
    padding: 8px;
}
.description.no-data {
    color: #bbb;
}

.description.no-data .arrow {
    visibility: hidden;
}
#legend {
    position: relative;
    margin-top: 20px;
    width: 140px;
}

#legend li.legend-label {
    display: flex;
    align-items: center;
    position: absolute;
    padding: 2px 8px;
    width: calc(100% - 20px);
    cursor: pointer;
}

#legend li.legend-label svg {
    margin-right: 8px;
}

#legend .hovered {
    stroke: black;
    z-index: 1;
}

#legend rect.hovered {
    stroke-width: 2px;
}

#legend li.hovered,
#legend li.selected {
    background-color: #e2e8ff;
}
#legend li.hovered svg rect {
    stroke-width: 1px;
}
if(typeof APP === 'undefined') {APP = {};}
APP.basicLegend = function () {
  'use strict';
  var events = d3.dispatch('mouseenter', 'mouseleave', 'click'),
      selectedItem = d3.local();

  var o = {
    label: null,
    key: null,
    color: null
  };

  function legend(group) {
    group.each(function(data) {
      render.call(this, data, group)
    });
  }

  function render(data, group) {
    var context = d3.select(this),
        t,
        labels,
        labelsEnter;

    if (group instanceof d3.transition) {
      t = d3.transition(group);
    } else {
      t = d3.transition();
    }

    context
        .selectAll('ul')
        .data([data])
        .enter()
        .append('ul')
        .attr('class', 'legend');

    labels = context
        .selectAll('ul')
        .selectAll('li.legend-label')
        .data(Object, o.key);

    labelsEnter = labels.enter()
        .append('li')
        .attr('class', 'legend-label')
        .attr('data-id', o.key)
        .on('mouseenter mouseleave', listeners(context).mouseMovement)
        .on('click', listeners(context).labelClick)
        .call(labelInitialAttributes);

    labelsEnter
        .append('svg')
        .attr('width', 22)
        .attr('height', 22)
        .append('rect')
        .attr('fill', o.color)
        .attr('width', 20)
        .attr('height', 20)
        .attr('x', 1)
        .attr('y', 1);

    labelsEnter
        .append('span')
        .text(o.label);

    labelsEnter
        .merge(labels)
        .classed('selected', isSelected)
        .transition(t)
        .style('top', function(d, i) {return (i * 22) + 'px';})
        .style('opacity', 1)
        .style('left', '12px');

    labels.exit()
        .transition(t)
        .call(labelInitialAttributes)
        .remove();
  }

  function listeners(context) {
    return {
      labelClick: function(d) {
        selectedItem.set(context.node(), d);
        context.call(legend);
        events.call('click', context.node(), d);
      },
      mouseMovement: function(d) {
        context.call(highlight, d, d3.event.type);
        events.call(d3.event.type, context.node(), d);
      }
    }
  }

  function highlight(selection, d, action) {
    selection
        .selectAll('li[data-id="' + o.key(d) + '"]')
        .classed('hovered', action === 'mouseenter');
  }

  function labelInitialAttributes(selection) {
    selection
        .style('left', '-12px')
        .style('opacity', 0);
  }

  function isSelected(d) {
    return selectedItem.get(this) && o.key(d) === o.key(selectedItem.get(this));
  }

  legend.label = function(_) {
    if (!arguments.length) {return o.label;}
    o.label = _;
    return legend;
  };
  legend.key = function(_) {
    if (!arguments.length) {return o.key;}
    o.key = _;
    return legend;
  };
  legend.color = function(_) {
    if (!arguments.length) {return o.color;}
    o.color = _;
    return legend;
  };

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

  legend.on = function(evt, callback) {
    events.on(evt, callback);
    return legend;
  };

  legend.highlight = function(selection, d) {
    selection.call(highlight, d, 'mouseenter');
    return legend;
  };

  legend.unhighlight = function(selection, d) {
    selection.call(highlight, d, 'mouseleave');
    return legend;
  };

  return legend;
};
if(typeof APP === 'undefined') {APP = {};}
APP.generateData = function(splice) {
  'use strict';
  // Icons from Freepik at http://www.flaticon.com/packs/miscellaneous-elements
  var icons = ['car.svg', 'idea.svg', 'phone-call.svg', 'shopping-cart.svg', 'cutlery.svg'],
      labels = ['travel', 'electricity', 'phone', 'shopping', 'food'],
      descriptions = [
        'Including car payments, fuel, tolls', 'Electric Bill',
        'Cell phone, cell plan, land-line',
        'Any non-food shopping items such as clothing, gifts, etc.',
        'Groceries and restaurant expenses'
      ],
      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),
      icon: icons[i - 1],
      label: labels[i - 1],
      description: descriptions[i - 1]
    });
  }
  if (splice) {
    arr.sort(function() {return 0.5 - Math.random();})
        .splice(0, Math.random() * 5);
  }
  return arr;
};
if(typeof APP === 'undefined') {APP = {};}
APP.descriptionWithArrow = function() {
  'use strict';
  var o = {
    label: null,
    text: null
  };

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

  function render(data) {
    var context = d3.select(this),
        right;

    context
        .html('')
        .classed('no-data', !data)
        .append('div')
        .attr('class', 'desc-left arrow')
        .html('&larr;');

    right = context.append('div')
        .attr('class', 'desc-right');

    right.append('div')
        .attr('class', 'label')
        .text(o.label);

    right.append('div')
        .attr('class', 'text')
        .text(o.text);
  }

  description.label = function(_) {
    if (!arguments.length) {return o.label;}
    o.label = _;
    return description;
  };
  description.text = function(_) {
    if (!arguments.length) {return o.text;}
    o.text = _;
    return description;
  };

  return description;
};
if(typeof APP === 'undefined') {APP = {};}
APP.pieIcons = function() {
  'use strict';
  var icon = d3.local();

  var o = {
    iconPath: null,
    imageWidth: null,
    interpolate: null,
    container: function (selection) {return d3.select(selection._parents[0]);}
  };

  function icons(group) {
    var container = o.container(group);

    group.each(function(data) {
      render.call(this, data, container);
    });
  }

  function render(data, container) {
    var thisIcon = container
      .append('image')
      .attr('class', 'icon')
      .attr('xlink:href', o.iconPath.bind(null, data))
      .attr('width', o.imageWidth)
      .attr('height', o.imageWidth)
      .style('opacity', 0);

    icon.set(this, thisIcon);
  }

  function iconTranslate(i, t) {
    var dimensions = this.getBoundingClientRect(),
        coords = d3.arc().centroid(i(t)),
        adjustedCoords = [
          coords[0] - dimensions.width / 2,
          coords[1] - dimensions.height / 2
        ];

    return 'translate(' + adjustedCoords.join(',') + ')';
  }

  function removeIfParentIsGone(pieSegment) {
    return function() {
      if (!document.body.contains(pieSegment)) {
        this.remove();
      }
    };
  }

  function iconTween(pieSegment) {
    var i = o.interpolate(pieSegment);
    return function () {
      return iconTranslate.bind(this, i);
    };
  }

  icons.tween = function (transition, isExiting) {
    transition.selection().each(function () {
      icon.get(this)
          .transition(transition)
          .duration(transition.duration())
          .attr('width', o.imageWidth)
          .attr('height', o.imageWidth)
          .style('opacity', Number(!isExiting))
          .attrTween('transform', iconTween(this))
          .on('end', removeIfParentIsGone(this));
    });
  };

  icons.exitTween = function(transition) {
    icons.tween(transition, true);
  };

  icons.iconPath = function(_) {
    if (!arguments.length) {return o.iconPath;}
    o.iconPath = _;
    return icons;
  };
  icons.imageWidth = function(_) {
    if (!arguments.length) {return o.imageWidth;}
    o.imageWidth = _;
    return icons;
  };
  icons.interpolate = function(_) {
    if (!arguments.length) {return o.interpolate;}
    o.interpolate = _;
    return icons;
  };
  icons.container = function(_) {
    if (!arguments.length) {return o.container;}
    o.container = _;
    return icons;
  };

  return icons;
};
if(typeof APP === 'undefined') {APP = {};}
APP.pieSelectionRotation = function() {
  'use strict';
  var local = {
    angle: d3.local(),
    selectedSegment: d3.local(),
    selectedKey: d3.local()
  };

  var o = {
    key: null,
    alignmentAngle: 0
  };

  function rotation(group) {
    group.each(function() {
      var selectedData = getSelectedData(this);

      local.angle.set(this, local.angle.get(this) || 0);
      local.selectedSegment.set(this, selectedData);

      if (selectedData) {
        local.angle.set(this, newAngle(local.angle.get(this), meanAngle(selectedData)));
      }
    });
  }

  function newAngle(offsetAngle, currentAngle) {
    var radiansToTurn = degreesToRadians(o.alignmentAngle) - currentAngle - offsetAngle;
    return shorterRotation(radiansToTurn) + offsetAngle;
  }

  function meanAngle(data) {
    return d3.mean([data.startAngle, data.endAngle]);
  }

  function degreesToRadians(degrees) {
    return degrees * Math.PI * 2 / 360;
  }

  function shorterRotation(offset) {
    var tau = Math.PI * 2;
    offset = offset % tau;
    return (Math.abs(offset) > tau / 2) ? offset + tau * Math.sign(-offset) : offset;
  }

  function getSelectedData(node) {
    return d3.select(node)
        .datum()
        .filter(function(d) {return o.key(d.data) === local.selectedKey.get(node)})[0];
  }

  rotation.selectedSegment = function(selection, d) {
    var returnArray;

    function nodeMap(node) {
      return (local.selectedSegment.get(node) || {}).data;
    }

    if (typeof d === 'undefined' ) {
      returnArray = selection.nodes().map(nodeMap);
      return selection._groups[0] instanceof NodeList ? returnArray : returnArray[0];
    }

    selection.each(function() {
      local.selectedKey.set(this, o.key(d));
    });

    return rotation;
  };

  rotation.getAngle = function(selection) {
    var returnArray = selection.nodes()
        .map(function(node) {return local.angle.get(node) || 0;});

    return selection._groups[0] instanceof NodeList ? returnArray : returnArray[0];
  };

  rotation.key = function(_) {
    if (!arguments.length) {return o.key;}
    o.key = _;
    return rotation;
  };
  rotation.alignmentAngle = function(_) {
    if (!arguments.length) {return o.alignmentAngle;}
    o.alignmentAngle = _;
    return rotation;
  };

  return rotation;
};
if(typeof APP === 'undefined') {APP = {};}
APP.pieTransition = function() {
  'use strict';
  var allNodes,
      firstPreviousNode,
      firstCurrentNode,
      enteringSegments,
      transitioningSegments;

  var previousSegmentData = d3.local();

  var o = {
    arc: null,
    sort: null,
    offset: 0
  };

  var methods = {
    enter: function(transition) {
      transition
          .each(setEnterAngle)
          .call(render);
    },
    transition: render,
    exit: function(transition) {
      transition
          .each(setExitAngle)
          .call(render);
    },
    interpolate: function (segment) {
      var d = d3.select(segment).datum();
      var newData = {
        startAngle: d.startAngle + o.offset,
        endAngle: d.endAngle + o.offset,
        innerRadius: o.arc.innerRadius()(),
        outerRadius: o.arc.outerRadius()()
      };
      return d3.interpolate(previousSegmentData.get(segment), newData);
    }
  };

  function previousAdjacentAngle(node) {
    var index = allNodes.indexOf(node);
    if (index) {
      return previousSegmentData.get(allNodes[index - 1]).endAngle;

    } else if (firstPreviousNode) {
      return previousSegmentData.get(firstPreviousNode).startAngle;

    } else {
      return nodeData(node).startAngle;
    }
  }

  function currentAdjacentAngle(node) {
    var index = allNodes.indexOf(node);

    if (index) {
      return nodeData(allNodes[index - 1]).endAngle;

    } else {
      return nodeData(firstCurrentNode).startAngle;
    }
  }

  function updateNodes() {
    if (!transitioningSegments || !enteringSegments) {return;}

    allNodes = transitioningSegments.nodes()
        .concat(transitioningSegments.exit().nodes())
        .concat(enteringSegments.nodes())
        .sort(sortNodes);

    firstPreviousNode = transitioningSegments.nodes()
        .concat(transitioningSegments.exit().nodes())
        .sort(sortNodes)[0];

    firstCurrentNode = transitioningSegments.nodes()
        .concat(enteringSegments.nodes())
        .sort(sortNodes)[0];

    function sortNodes(a, b) {
      return o.sort(nodeData(a).data, nodeData(b).data);
    }
  }

  function nodeData(node) {
    return d3.select(node).datum();
  }

  function setEnterAngle() {
    var enterAngle = previousAdjacentAngle(this);
    previousSegmentData.set(this, {
      startAngle: enterAngle,
      endAngle: enterAngle,
      innerRadius: o.arc.innerRadius()(),
      outerRadius: o.arc.outerRadius()()
    });
  }

  function setExitAngle(d) {
    var exitAngle = currentAdjacentAngle(this);
    d.startAngle = exitAngle;
    d.endAngle = exitAngle;
  }

  function render(transition) {
    transition.attrTween('d', arcTween);
  }

  function arcTween() {
    var i = methods.interpolate(this);
    previousSegmentData.set(this, i(0));
    return function(t) {
      var interation = i(t);
      o.arc
          .innerRadius(interation.innerRadius)
          .outerRadius(interation.outerRadius);
      return o.arc(interation);
    };
  }

  methods.enteringSegments = function (_) {
    enteringSegments = _;
    updateNodes();
    return methods;
  };

  methods.transitioningSegments = function (_) {
    transitioningSegments = _;
    updateNodes();
    return methods;
  };

  methods.arc = function(_) {
    if (!arguments.length) {return o.arc;}
    o.arc = _;
    return methods;
  };
  methods.sort = function(_) {
    if (!arguments.length) {return o.sort;}
    o.sort = _;
    return methods;
  };
  methods.offset = function(_) {
    if (!arguments.length) {return o.offset;}
    o.offset = _;
    return methods;
  };

  return methods;
};
if(typeof APP === 'undefined') {APP = {};}
APP.rotatingDonut = function() {
  'use strict';
  var o,
      events,
      local,
      rotation;

  o = {
    animationDuration: 600,
    iconSize: 0.7,
    thickness: 0.4,
    value: null,
    icon: null,
    color: null,
    key: null,
    sort: null
  };

  events = d3.dispatch('mouseenter', 'mouseleave', 'click');

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

  rotation = APP.pieSelectionRotation()
      .key(function(d) {return o.key(d);});

  function donut(group) {
    group.each(function(data) {
      render.call(this, data, group);
    });
  }

  function render(data, group) {
    var context,
        t,
        dim,
        pie,
        arc,
        pieTransition,
        pieIcons,
        segments,
        segmentEnter;

    if (!data) {return;}

    context = d3.select(this);

    if (group instanceof d3.transition) {
      t = d3.transition(group);
    } else {
      t = d3.transition().duration(o.animationDuration);
    }

    dim = getDimensions(context);

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

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

    pieTransition = local.animate.get(this) || local.animate.set(this, APP.pieTransition());
    pieIcons = local.icons.get(this) || local.icons.set(this, APP.pieIcons());

    pieIcons
        .container(function() {return context.select('g.group');})
        .iconPath(dataAccess('icon'))
        .imageWidth(dim.outerRadius * o.thickness * o.iconSize)
        .interpolate(pieTransition.interpolate);

    context.selectAll('svg')
        .data([pie(data.sort(o.sort))])
        .call(rotation)
        .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')
        .transition(t)
        .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'))
        .on('mouseenter mouseleave click', onPathEvent(context));

    pieTransition
        .arc(arc)
        .sort(o.sort)
        .enteringSegments(segmentEnter)
        .transitioningSegments(segments)
        .offset(rotation.getAngle(context.select('svg')));

    segmentEnter
        .call(pieIcons)
        .transition(t)
        .call(pieTransition.enter)
        .call(pieIcons.tween);

    segments
        .transition(t)
        .call(pieTransition.transition)
        .call(pieIcons.tween);

    segments.exit()
        .transition(t)
        .call(pieTransition.exit)
        .call(pieIcons.exitTween)
        .remove();
  }

  function onPathEvent(context) {
    return function(d) {
      if (d3.event.type === 'click') {
        rotation.selectedSegment(context.select('svg'), d.data);
        context.call(donut);
      }
      events.call(d3.event.type, context.node(), d.data);
    };
  }

  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.selectedSegment = function(context, d) {
    if (typeof d === 'undefined' ) {return rotation.selectedSegment(context.select('svg'));}
    rotation.selectedSegment(context.select('svg'), d);
    return donut;
  };
  donut.alignmentAngle = function(_) {
    if (typeof _ === 'undefined' ) {return rotation.alignmentAngle();}
    rotation.alignmentAngle(_);
    return donut;
  };

  donut.animationDuration = function(_) {
    if (!arguments.length) {return o.animationDuration;}
    o.animationDuration = _;
    return donut;
  };
  donut.iconSize = function(_) {
    if (!arguments.length) {return o.iconSize;}
    o.iconSize = _;
    return donut;
  };
  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.icon = function(_) {
    if (!arguments.length) {return o.icon;}
    o.icon = _;
    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;
  };

  donut.on = function(evt, callback) {
    events.on(evt, callback);
    return donut;
  };

  return donut;
};
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">

  <path d="M108.064 299.735c-7.664 0-13.873 6.219-13.873 13.884 0 7.664 6.209 13.884 13.873 13.884 7.675 0 13.884-6.219 13.884-13.884s-6.209-13.884-13.884-13.884z" fill="#FFF"/>

  <path d="M477.28 193.324l-97.552-39.096c-16.24-6.513-33.348-9.811-50.854-9.811h-59.796c-21.422 0-42.845 5.13-61.943 14.847l-88.391 44.949-63.408 9.245c-15.37 2.241-29.422 9.947-39.588 21.705C5.591 246.912 0 261.937 0 277.464v37.421c0 5.78 4.691 10.47 10.47 10.47h44.918c2.251 10.188 7.413 19.506 15.035 26.94 10.135 9.853 23.506 15.287 37.651 15.287 14.135 0 27.506-5.434 37.641-15.287 7.633-7.444 12.805-16.784 15.046-26.94h187.66c2.251 10.188 7.413 19.506 15.046 26.93 10.125 9.863 23.495 15.297 37.63 15.297 14.145 0 27.516-5.434 37.641-15.287 7.633-7.434 12.795-16.753 15.046-26.94h47.745c5.78 0 10.47-4.691 10.47-10.47V244.65c.001-22.721-13.631-42.865-34.719-51.326zm-105.342-19.663l75.303 30.186h-56.823l-25.014-32.573c2.189.733 4.367 1.529 6.534 2.387zm-93.039-8.303h49.975c1.906 0 3.801.042 5.685.136l29.453 38.353h-85.113v-38.489zm-62.278 12.575c12.868-6.544 26.972-10.627 41.337-12.02v37.934h-92.296l50.959-25.914zM56.739 234.462c-4.303 14.564-17.15 25.495-32.709 26.982 5.454-13.684 17.673-24.207 32.709-26.982zm51.325 112.18c-18.239 0-33.023-14.784-33.023-33.023 0-18.239 14.784-33.023 33.023-33.023s33.023 14.784 33.023 33.023c.001 18.239-14.783 33.023-33.023 33.023zm293.045 0c-18.239 0-33.034-14.784-33.034-33.023 0-18.239 14.795-33.023 33.034-33.023 18.239 0 33.023 14.784 33.023 33.023 0 18.239-14.784 33.023-33.023 33.023zm89.95-42.227h-36.793c-4.387-25.38-26.542-44.761-53.168-44.761-26.616 0-48.771 19.381-53.158 44.761H161.243c-4.387-25.38-26.553-44.761-53.168-44.761-26.626 0-48.781 19.381-53.168 44.761H20.941v-21.862c29.663-.272 54.142-22.595 57.796-51.347l44.028-6.418H451.533v13.632c0 19.161 15.59 34.751 34.741 34.751h4.785v31.244zm0-52.184h-4.785c-7.602 0-13.8-6.198-13.8-13.81v-13.632h12.282c4.01 5.675 6.303 12.543 6.303 19.862v7.58z" fill="#FFF"/>

  <path d="M401.109 299.735c-7.675 0-13.884 6.219-13.884 13.884 0 7.664 6.209 13.884 13.884 13.884 7.664 0 13.873-6.219 13.873-13.884s-6.209-13.884-13.873-13.884zM290.199 254.133h-69.446c-5.783 0-10.47 4.688-10.47 10.47s4.688 10.47 10.47 10.47h69.446c5.782 0 10.47-4.688 10.47-10.47s-4.688-10.47-10.47-10.47zM108.064 299.735c-7.664 0-13.873 6.219-13.873 13.884 0 7.664 6.209 13.884 13.873 13.884 7.675 0 13.884-6.219 13.884-13.884s-6.209-13.884-13.884-13.884z" fill="#FFF"/>

  <path d="M401.109 299.735c-7.675 0-13.884 6.219-13.884 13.884 0 7.664 6.209 13.884 13.884 13.884 7.664 0 13.873-6.219 13.873-13.884s-6.209-13.884-13.873-13.884z" fill="#FFF"/>

  <path d="M108.064 299.735c-7.664 0-13.873 6.219-13.873 13.884 0 7.664 6.209 13.884 13.873 13.884 7.675 0 13.884-6.219 13.884-13.884s-6.209-13.884-13.884-13.884z" fill="#FFF"/>

  <path d="M401.109 299.735c-7.675 0-13.884 6.219-13.884 13.884 0 7.664 6.209 13.884 13.884 13.884 7.664 0 13.873-6.219 13.873-13.884s-6.209-13.884-13.873-13.884z" fill="#FFF"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512.001 512.001" width="512" height="512">
  <path d="M141.247 31.99C125.777 11.362 104.892.003 82.44.003c-22.451 0-43.335 11.36-58.806 31.986C8.921 51.606.819 77.505.819 104.916c0 27.412 8.102 53.31 22.816 72.927 8.605 11.473 18.888 20.068 30.18 25.446L36.546 462.948c-.842 12.653 3.645 25.221 12.309 34.48 8.665 9.261 20.909 14.573 33.591 14.573 12.679 0 24.917-5.309 33.579-14.565 8.664-9.26 13.151-21.83 12.307-34.486l-17.269-259.656c11.294-5.379 21.578-13.974 30.184-25.449 14.712-19.617 22.816-45.517 22.816-72.927s-8.102-53.311-22.816-72.928zM98.874 481.389c-4.3 4.596-10.135 7.127-16.429 7.127-6.299 0-12.138-2.533-16.442-7.132-4.302-4.6-6.442-10.592-6.025-16.875l16.954-254.934c1.826.153 3.659.258 5.508.258 1.848 0 3.68-.106 5.505-.258l16.954 254.934c.417 6.286-1.721 12.281-6.025 16.88zM82.44 186.346c-4.207 0-8.308-.645-12.264-1.839-.328-.121-.66-.232-1-.324-25.683-8.431-44.872-40.759-44.872-79.266 0-44.899 26.08-81.429 58.136-81.429 32.057 0 58.138 36.529 58.138 81.429s-26.08 81.429-58.138 81.429zM511.18 305.92V13.222l-.099.043c.339-2.587-.174-5.303-1.653-7.696-3.41-5.516-10.645-7.223-16.164-3.813-47.905 29.617-76.509 80.916-76.518 137.229 0 .008-.001.015-.001.022v153.781c0 13.493 10.783 24.483 24.169 24.891V476.7c0 19.373 15.761 35.134 35.134 35.134s35.135-15.761 35.135-35.134V305.979c-.001-.02-.003-.039-.003-.059zm-69.505-11.685c-.8-.001-1.444-.647-1.444-1.445V139.028c0-.005.001-.009.001-.014 0-40.621 17.413-78.189 47.462-104.139v259.36h-46.019zm46.022 182.467c0 6.423-5.225 11.649-11.65 11.649-6.423 0-11.649-5.225-11.649-11.649V317.721h23.299v158.981zM332.799.003c-6.485 0-11.743 5.257-11.743 11.743v133.4h-29.92v-133.4c0-6.485-5.258-11.743-11.743-11.743S267.65 5.26 267.65 11.746v133.4h-29.92v-133.4c0-6.485-5.258-11.743-11.743-11.743s-11.743 5.257-11.743 11.743V181.73c0 19.14 15.573 34.714 34.714 34.714h2.089l-16.475 247.65c-.827 12.352 3.551 24.624 12.013 33.67 8.463 9.049 20.42 14.238 32.807 14.238 12.385 0 24.342-5.189 32.806-14.237 8.461-9.045 12.839-21.318 12.013-33.666l-16.476-247.655h2.093c19.142 0 34.715-15.573 34.715-34.714V11.745C344.541 5.26 339.283.003 332.799.003zM295.045 481.72c-4.098 4.382-9.658 6.795-15.654 6.795s-11.556-2.413-15.654-6.795c-4.096-4.379-6.132-10.082-5.732-16.063l16.578-249.215h9.615l16.579 249.218c.4 5.978-1.635 11.681-5.732 16.06zm26.01-299.991c.001 6.192-5.036 11.228-11.228 11.228h-46.622c-.015.001-.031-.001-.046 0h-14.2c-6.192 0-11.228-5.036-11.228-11.228v-13.1h83.324v13.1z" fill="#FFF"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 511.997 511.997" width="512" height="512">
  <path d="M57.318 166.155H12.746C5.905 166.155.36 171.7.36 178.541s5.545 12.386 12.386 12.386h44.571c6.842 0 12.387-5.545 12.387-12.386s-5.545-12.386-12.386-12.386zM499.251 166.155h-44.572c-6.84 0-12.386 5.545-12.386 12.386s5.546 12.386 12.386 12.386h44.572c6.84 0 12.386-5.545 12.386-12.386s-5.546-12.386-12.386-12.386zM108.347 326.191c-4.837-4.837-12.679-4.837-17.517 0l-32.42 32.42c-4.837 4.837-4.837 12.679 0 17.517 2.419 2.419 5.589 3.628 8.758 3.628s6.34-1.209 8.758-3.628l32.421-32.42c4.837-4.836 4.837-12.679 0-17.517zM430.912 3.628c-4.838-4.837-12.678-4.837-17.517 0L381.863 35.16c-4.837 4.837-4.837 12.68 0 17.516 2.42 2.417 5.59 3.627 8.759 3.627 3.171 0 6.339-1.21 8.758-3.628l31.531-31.531c4.837-4.837 4.837-12.68.001-17.516zM130.117 35.142L98.602 3.628c-4.837-4.835-12.68-4.835-17.517 0-4.837 4.837-4.837 12.68 0 17.517L112.6 52.66c2.419 2.418 5.589 3.628 8.758 3.628s6.34-1.21 8.758-3.628c4.837-4.837 4.837-12.68.001-17.518zM453.585 358.615l-32.3-32.303c-4.838-4.837-12.678-4.837-17.517.001-4.837 4.837-4.837 12.68 0 17.516l32.301 32.301c2.419 2.419 5.589 3.628 8.758 3.628 3.171 0 6.339-1.21 8.758-3.628 4.837-4.836 4.837-12.679 0-17.515zM255.999 33.636c-85.648 0-155.327 72.885-155.327 162.474 0 41.65 14.919 86.482 40.933 123 19.979 28.045 30.538 59.249 30.538 90.24v77.875c0 13.659 11.113 24.772 24.772 24.772h118.169c13.659 0 24.772-11.113 24.772-24.772V409.35c0-30.99 10.559-62.195 30.536-90.24 26.014-36.517 40.934-81.35 40.934-123-.002-89.589-69.681-162.474-155.327-162.474zm59.083 453.591H196.913v-16.201h59.085c6.841 0 12.387-5.545 12.387-12.386s-5.546-12.386-12.387-12.386h-59.085v-16.2h118.172l.014 57.171c-.001.002-.006.002-.017.002zm-76.453-231.086l-16.564-35.387h67.866l-16.562 35.387c-.652 1.393-1.039 2.896-1.142 4.43l-9.605 144.711h-13.247l-9.605-144.711c-.102-1.534-.49-3.037-1.141-4.43zm111.586 48.597c-22.123 31.056-34.208 65.733-35.082 100.544h-27.684l9.341-140.749 23.831-50.915c1.796-3.836 1.507-8.325-.767-11.899-2.273-3.573-6.215-5.738-10.451-5.738H202.592c-4.236 0-8.177 2.165-10.451 5.738-2.273 3.573-2.563 8.062-.767 11.898l23.832 50.915 9.341 140.749h-27.686c-.874-34.812-12.958-69.488-35.082-100.544-22.754-31.942-36.338-72.551-36.338-108.628 0-75.93 58.567-137.702 130.555-137.702s130.555 61.772 130.555 137.702c.002 36.079-13.582 76.688-36.336 108.629z" fill="#FFF"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
  <path d="M492.557 400.56L392.234 300.238c-11.976-11.975-31.458-11.975-43.435 0l-26.088 26.088c-8.174 8.174-10.758 19.845-7.773 30.241l-9.843 9.843-.008.008c-6.99 6.998-50.523-3.741-103.145-56.363-52.614-52.613-63.356-96.139-56.366-103.142 0-.002.002-.002.002-.002l9.852-9.851c2.781.799 5.651 1.207 8.523 1.207 7.865 0 15.729-2.993 21.718-8.98l26.088-26.088c11.975-11.975 11.975-31.458 0-43.434L111.436 19.441c-5.8-5.8-13.513-8.994-21.716-8.994-8.205 0-15.915 3.196-21.716 8.994l-26.09 26.09c-8.174 8.174-10.758 19.846-7.773 30.241 0 0-8.344 8.424-8.759 8.956-27.753 30.849-32.96 79.418-14.561 137.487 18.017 56.857 56.857 117.088 109.367 169.595 52.508 52.508 112.739 91.348 169.596 109.367 22.84 7.237 44.207 10.823 63.61 10.823 31.813 0 58.337-9.648 77.35-28.66l5.474-5.474c2.74.788 5.602 1.213 8.532 1.213 8.205 0 15.917-3.196 21.716-8.994l26.09-26.09c11.975-11.975 11.975-31.459.001-43.435zM89.72 41.157l100.324 100.325-26.074 26.102-.014-.014-.375-.375-49.787-49.787-50.163-50.161 26.089-26.09zm319.309 420.466l-.005.005c-22.094 22.091-61.146 25.74-109.961 10.27-52.252-16.558-108.065-52.714-157.156-101.806C92.814 321 56.658 265.189 40.101 212.936c-15.47-48.817-11.821-87.87 10.275-109.967l.002-.002 2.77-2.77 77.857 77.856-7.141 7.141c-.005.005-.009.011-.015.017-29.585 29.622 5.963 96.147 56.378 146.562 37.734 37.734 84.493 67.14 118.051 67.14 11.284 0 21.076-3.325 28.528-10.778l.008-.008 7.133-7.133 77.857 77.856-2.775 2.773zm35.723-13.255L344.428 348.044l26.088-26.088L470.84 422.278s-26.079 26.099-26.088 26.09zM388.818 123.184c-29.209-29.209-68.042-45.294-109.344-45.293-8.481 0-15.356 6.875-15.356 15.356s6.876 15.356 15.356 15.356c33.1-.002 64.219 12.89 87.628 36.297 23.406 23.406 36.295 54.525 36.294 87.624 0 8.481 6.875 15.358 15.356 15.358 8.48 0 15.356-6.875 15.356-15.354.001-41.304-16.085-80.135-45.29-109.344z" fill="#FFF"/>
  <path d="M443.895 68.107C399.972 24.186 341.578-.002 279.468 0c-8.481 0-15.356 6.876-15.356 15.356 0 8.481 6.876 15.356 15.356 15.356 53.907-.002 104.588 20.992 142.709 59.111 38.118 38.118 59.111 88.799 59.11 142.706 0 8.481 6.875 15.356 15.356 15.356 8.48 0 15.356-6.875 15.356-15.354.002-62.112-24.186-120.504-68.104-164.424zM333.737 178.26c-14.706-14.706-33.465-22.477-54.256-22.477h-.006c-8.481.002-15.356 6.876-15.354 15.358.002 8.481 6.878 15.356 15.358 15.354h.005c12.644 0 23.593 4.536 32.539 13.481 8.819 8.82 13.481 20.075 13.479 32.544-.002 8.481 6.875 15.356 15.354 15.358h.002c8.481 0 15.354-6.875 15.356-15.354.001-20.792-7.77-39.556-22.477-54.264z" fill="#FFF"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 511.999 511.999" width="512" height="512">
  <path d="M214.685 402.828c-24.829 0-45.029 20.2-45.029 45.029 0 24.829 20.2 45.029 45.029 45.029s45.029-20.2 45.029-45.029c-.001-24.829-20.201-45.029-45.029-45.029zm0 64.914c-10.966 0-19.887-8.922-19.887-19.887 0-10.966 8.922-19.887 19.887-19.887s19.887 8.922 19.887 19.887c0 10.967-8.922 19.887-19.887 19.887zM372.63 402.828c-24.829 0-45.029 20.2-45.029 45.029 0 24.829 20.2 45.029 45.029 45.029s45.029-20.2 45.029-45.029c-.001-24.829-20.201-45.029-45.029-45.029zm0 64.914c-10.966 0-19.887-8.922-19.887-19.887 0-10.966 8.922-19.887 19.887-19.887 10.966 0 19.887 8.922 19.887 19.887 0 10.967-8.922 19.887-19.887 19.887zM383.716 165.755H203.567c-6.943 0-12.571 5.628-12.571 12.571s5.629 12.571 12.571 12.571h180.149c6.943 0 12.571-5.628 12.571-12.571 0-6.944-5.628-12.571-12.571-12.571zM373.911 231.035H213.373c-6.943 0-12.571 5.628-12.571 12.571s5.628 12.571 12.571 12.571H373.91c6.943 0 12.571-5.628 12.571-12.571 0-6.942-5.628-12.571-12.57-12.571z" fill="#FFF"/>
  <path d="M506.341 109.744c-4.794-5.884-11.898-9.258-19.489-9.258H95.278L87.37 62.097c-1.651-8.008-7.113-14.732-14.614-17.989l-55.177-23.95c-6.37-2.767-13.773.156-16.536 6.524-2.766 6.37.157 13.774 6.524 16.537L62.745 67.17l60.826 295.261c2.396 11.628 12.752 20.068 24.625 20.068h301.166c6.943 0 12.571-5.628 12.571-12.571s-5.628-12.571-12.571-12.571H148.197l-7.399-35.916H451.69c11.872 0 22.229-8.44 24.624-20.068l35.163-170.675c1.531-7.432-.341-15.071-5.136-20.954zM451.69 296.301H135.619l-35.161-170.674 386.393.001-35.161 170.673z" fill="#FFF"/>
</svg>