Agile Sprint Burndown Chart — Apache ECharts

A sprint burndown chart tracks the amount of remaining work in an agile sprint over time, plotted as a step (or line) series that descends as tasks are completed. A straight ideal-burndown guideline runs diagonally from the total committed scope on day one down to zero on the final day, giving the team an at-a-glance reference for whether they are ahead of or behind schedule. It is one of the most widely used project-management visualizations in software development, surfacing scope creep, stalled progress, and end-of-sprint risk early enough to act on.

Agile Sprint Burndown Chart rendered with Apache ECharts

JavaScript source (Apache ECharts)

// anyplot.ai
// burndown-sprint: Agile Sprint Burndown Chart
// Library: echarts 5.5.1 | JavaScript 22.22.3
// Quality: 91/100 | Created: 2026-06-14
//# anyplot-orientation: landscape

const t = window.ANYPLOT_TOKENS;

// Sprint: June 1–13 2025, 10 working days, initial scope 40 story points
// Scope change: +8 points added on Thu Jun 5 (Day 4)
const ms = (s) => new Date(s).getTime();

const actual = [
  [ms('2025-06-01'), 40], // Sprint kick-off (committed scope)
  [ms('2025-06-02'), 37], // Mon Day 1: burned 3
  [ms('2025-06-03'), 33], // Tue Day 2: burned 4
  [ms('2025-06-04'), 30], // Wed Day 3: burned 3
  [ms('2025-06-05'), 34], // Thu Day 4: burned 4, +8 scope change added
  [ms('2025-06-06'), 30], // Fri Day 5: burned 4
  [ms('2025-06-09'), 25], // Mon Day 6: burned 5 (weekend flat at 30)
  [ms('2025-06-10'), 20], // Tue Day 7: burned 5
  [ms('2025-06-11'), 14], // Wed Day 8: burned 6
  [ms('2025-06-12'),  8], // Thu Day 9: burned 6
  [ms('2025-06-13'),  3], // Fri Day 10: burned 5 — sprint ends, 3 pts remain
];

// Ideal burndown: straight line from committed scope (40) to zero
const ideal = Array.from({ length: 13 }, (_, i) => {
  const day = new Date('2025-06-01');
  day.setDate(day.getDate() + i);
  return [day.getTime(), Math.round(40 * (12 - i) / 12)];
});

const chart = echarts.init(document.getElementById('container'));

chart.setOption({
  animation: false,
  color: t.palette,
  backgroundColor: 'transparent',

  title: {
    text: 'burndown-sprint · javascript · echarts · anyplot.ai',
    left: 'center',
    top: 20,
    textStyle: { color: t.ink, fontSize: 22, fontWeight: 'bold' },
  },

  legend: {
    top: 60,
    textStyle: { color: t.inkSoft, fontSize: 14 },
    icon: 'roundRect',
  },

  tooltip: {
    trigger: 'axis',
    backgroundColor: t.elevatedBg,
    borderColor: t.grid,
    textStyle: { color: t.ink, fontSize: 13 },
    axisPointer: { lineStyle: { color: t.inkSoft } },
    formatter: (params) => {
      const day = new Date(params[0].data[0]);
      const label = day.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' });
      const rows = params
        .filter((p) => p.data && p.data.length > 1 && p.data[1] != null)
        .map((p) => `${p.marker} ${p.seriesName}: <b>${p.data[1]} pts</b>`);
      return `${label}<br/>${rows.join('<br/>')}`;
    },
  },

  grid: { left: 120, right: 130, top: 120, bottom: 80 },

  xAxis: {
    type: 'time',
    min: ms('2025-06-01'),
    max: ms('2025-06-13'),
    axisLabel: {
      color: t.inkSoft,
      fontSize: 13,
      formatter: (val) =>
        new Date(val).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
    },
    axisLine: { show: false },
    splitLine: { show: false },
  },

  yAxis: {
    type: 'value',
    name: 'Story Points Remaining',
    nameLocation: 'middle',
    nameRotate: 90,
    nameGap: 60,
    nameTextStyle: { color: t.inkSoft, fontSize: 14 },
    min: 0,
    max: 50,
    axisLabel: { color: t.inkSoft, fontSize: 13 },
    axisLine: { show: false },
    splitLine: { lineStyle: { color: t.grid } },
  },

  series: [
    {
      name: 'Actual Remaining',
      type: 'line',
      step: 'end',
      data: actual,
      itemStyle: { color: t.palette[0] },
      lineStyle: { color: t.palette[0], width: 4 },
      symbol: 'circle',
      symbolSize: 8,
      areaStyle: { color: t.palette[0], opacity: 0.07 },
      // Sprint outcome callout — tells the story at a glance
      endLabel: {
        show: true,
        formatter: '3 pts remaining',
        color: t.ink,
        fontSize: 12,
        fontWeight: 'bold',
        distance: 8,
        backgroundColor: t.elevatedBg,
        borderColor: t.grid,
        borderWidth: 1,
        padding: [4, 8],
        borderRadius: 4,
      },
      markArea: {
        silent: true,
        itemStyle: { color: t.inkSoft, opacity: 0.10 },
        label: {
          show: true,
          position: 'insideTop',
          color: t.inkSoft,
          fontSize: 12,
          formatter: 'Weekend',
        },
        data: [[{ xAxis: ms('2025-06-07') }, { xAxis: ms('2025-06-09') }]],
      },
      markLine: {
        silent: true,
        symbol: ['none', 'none'],
        lineStyle: { color: t.amber, width: 2, type: 'dashed' },
        // Amber badge (dark ink on amber bg) — high contrast on both light and dark themes
        label: {
          show: true,
          formatter: '{badge|+8 pts  Scope Change}',
          rich: {
            badge: {
              color: '#1A1A17',
              backgroundColor: '#DDCC77',
              borderRadius: 4,
              padding: [3, 8, 3, 8],
              fontSize: 12,
            },
          },
          position: 'end',
        },
        data: [{ xAxis: ms('2025-06-05'), name: 'Scope Change' }],
      },
    },
    {
      name: 'Ideal Burndown',
      type: 'line',
      data: ideal,
      itemStyle: { color: t.inkSoft },
      lineStyle: { color: t.inkSoft, width: 1.5, type: 'dashed' },
      symbol: 'none',
    },
  ],
});

Part of Agile Sprint Burndown Chart on anyplot.ai.

Other implementations