<div id="activity"></div>
#activity, #activity svg {
  margin-left: auto;
  margin-right: auto;
}

#activity {
  width: 450px;
  margin: 5rem auto 0 auto;
}

body {
  background-color: black;
}
/* Statistics */
const stats = [
 {
    name: 'Moving',
    value: 122,
    goal: 350,
    perc: 0.35,
    unit: 'kcal',
    color: 'hotpink'
  }, {
    name: 'Exercising',
    value: 40,
    goal: 40,
    perc: 1.00,
    unit: 'min',
    color: 'limegreen'
  }, {
    name: 'Standing',
    value: 9,
    goal: 12,
    perc: 0.75,
    unit: 'h',
    color: 'turquoise'
  }
];

/* Define icons */
const icons = {
  moving: '↦',
  exercising: '↠',
  standing: '↟'
};

/* Chart configuration. */
const width = 450;
const height = 600;
const margin = 40;
const circleStroke = 50;
const circleSpace = 2;

/* Define Math.PI * 2 because I don't like typing it out. */
const fullCircle = Math.PI * 2;

/* Create the chart. */
const chart = d3.select('#activity').append('svg')
  .attr('width', width)
  .attr('height', height);

/* Generate the description. */
const generateDescription = () => {
  return stats.map((stat) => {
    return `${stat.name}: ${stat.perc * 100}%.`;
  }).join(' ');
}

/* Draw a ring with a given radius and angle. */
const drawRing = (parent, arc, color, opacity) => {
  return parent.append('path')
    .attr('d', d3.arc()
      .innerRadius(arc.inner)
      .outerRadius(arc.outer)
      .startAngle(arc.start)
      .endAngle(arc.end)
     )
    .attr('fill', color)
    .attr('opacity', opacity);
}

/* Set the radius. */
const radius = (width - margin) / 2;

/* Create a group for all 3 rings. */
const rings = chart.append('g')
    .attr('transform', `translate(${width / 2}, ${radius})`)
    .attr('role', 'img')
    .attr('aria-label', generateDescription); // SR support

/* Draw 3 rings */
stats.forEach((stat, index) => {
  /* Set the properties for the arc */
  const arc = {
    inner: radius - circleStroke * (index + 1) - circleSpace * index,
    outer: radius - circleStroke * index - circleSpace * index,
    start: 0,
    end: fullCircle
  };
 
  /* Add a group per ring */
  const group = rings.append('g');
  
  /* Draw the dark band */
  drawRing(
    group,
    {...arc, start: fullCircle * stat.perc },
    stat.color,
    0.25
  );
  
  /* Draw the active band */
  drawRing(
    group,
    {...arc, end: fullCircle * stat.perc},
    stat.color,
    1
  );
  
  /* Add the labels. */
  group.append('text')
    .text(icons[stat.name.toLowerCase()])
    .attr('fill', '#000')
    .attr('transform', `translate(${circleSpace}, -${(arc.outer + arc.inner) / 2 - circleSpace * (index + 2)})`)
    .attr('font-size', '1.5rem')
    .attr('aria-hidden', 'true');
  
  /* Add legend. */
  const legend = chart.append('text')
    .attr('text-anchor', 'middle')
    .attr('transform', `translate(${width / 2}, ${radius * 2 + 20 * (index + 2)})`)
    .attr('fill', stat.color);
  
  legend.append('tspan')
      .text(`${icons[stat.name.toLowerCase()]} `)
      .attr('aria-hidden', 'true');
  
  legend.append('tspan')
    .text(`${stat.name}: ${stat.value}/${stat.goal}${stat.unit} (${stat.perc * 100}%)`);
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.