<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;
}
/* Data */
const data = [
  {
    id: 'moving',
    goal: 350,
    unit: 'kcal',
    values: [0,0,0,0,0,0,0,0,5,2,2,1,5,2,47,19,53,10,2],
  },
  {
    id: 'exercising',
    goal: 40,
    unit: 'min',
    values: [0,0,0,0,0,0,0,0,2,0,2,0,0,1,1,0,31,3,0],
  },
  {
    id: 'standing',
    goal: 12,
    unit: 'h',
    values: [0,0,0,0,0,0,0,0,1,1,1,0,1,1,1,0,1,1,1],
  },
];

/* Define color, labels, icons. */
const defs = {
  moving: {
    label: 'Moving',
    icon: '↦',
    color: 'hotpink'
  },
  exercising: {
    label: 'Exercising',
    icon: '↠',
    color: 'limegreen'
  },
  standing: {
    label: 'Standing',
    icon: '↟',
    color: 'turquoise'
  }
};

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

/* Generate statistics. */
const activity = data.map((row) => {
  return {
    ...row,
    total: d3.sum(row.values),
    perc: parseInt(d3.sum(row.values) / row.goal * 100) / 100,
  }
});

/* Generate the description. */
const generateDescription = () => {
  return activity.map((row) => {
    const percentage = row.perc >= 1 ? 'Complete' : `${row.perc * 100}%`;
    return `${defs[row.id].label}: ${percentage}.`;
  }).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);
}

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

/* 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})`);

/* Draw 3 rings */
activity.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')
    .attr('role', 'img') // SR support
    .attr('aria-labelledby', `activityTitle-${stat.id}`); // SR support

  /* Add title. */
  group.append('title')
    .text(`${defs[stat.id].label}: ${stat.perc * 100}%.`)
    .attr('id', `activityTitle-${stat.id}`);
  
  /* Draw the dark band */
  drawRing(
    group,
    {...arc, start: fullCircle * stat.perc },
    defs[stat.id].color,
    0.25
  );
  
  /* Draw the active band */
  drawRing(
    group,
    {...arc, end: fullCircle * stat.perc},
    defs[stat.id].color,
    1
  );
  
  /* Add the labels. */
  group.append('text')
    .text(defs[stat.id].icon)
    .attr('fill', '#000')
    .attr('transform', `translate(${circleSpace}, -${(arc.outer + arc.inner) / 2 - circleSpace * (index + 2)})`)
    .attr('font-size', '1.5rem');
  
  /* Add legend. */
  const legend = chart.append('text')
    .attr('text-anchor', 'middle')
    .attr('transform', `translate(${width / 2}, ${radius * 2 + 20 * (index + 2)})`)
    .attr('fill', defs[stat.id].color);
  
  legend.append('tspan')
    .text(`${defs[stat.id].icon} `)
    .attr('aria-hidden', 'true');
  
  legend.append('tspan')
    .text(`${defs[stat.id].label}: ${stat.total}/${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.