Agile Sprint Burndown Chart — Chart.js

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 Chart.js

JavaScript source (Chart.js)

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

const t = window.ANYPLOT_TOKENS;
const THEME = window.ANYPLOT_THEME;

// Sprint: 14 calendar days (Jul 7–20), 10 working days, 40 initial story points.
// Scope change on Jul 10 (Day 4): +9 story points added mid-sprint.
const labels = [
  "Jul 7", "Jul 8", "Jul 9", "Jul 10", "Jul 11", "Jul 12", "Jul 13",
  "Jul 14", "Jul 15", "Jul 16", "Jul 17", "Jul 18", "Jul 19", "Jul 20",
];

// Actual remaining story points at end of each day (step-wise; flat on weekends).
// Day 4 (Jul 10) jumps from 29→37: scope grew by 9 pts, 1 pt was burned that day.
const remaining = [40, 35, 29, 37, 31, 31, 31, 25, 18, 12, 5, 0, 0, 0];

// Ideal burndown: straight diagonal from 40 (day 0) to 0 (day 13).
const ideal = labels.map((_, i) => +(40 * (1 - i / 13)).toFixed(1));

// Weekend bands: Jul 12–13 and Jul 19–20 (half-unit offsets centre on the gaps).
const weekendRanges = [[4.5, 6.5], [11.5, 13.5]];
const scopeChangeIndex = 3; // Jul 10

// Plugin: shade non-working weekend columns behind the chart data.
const weekendPlugin = {
  id: "weekendBands",
  beforeDraw(chart) {
    const { ctx, scales: { x, y } } = chart;
    ctx.save();
    ctx.fillStyle = THEME === "light"
      ? "rgba(26,26,23,0.055)"
      : "rgba(240,239,232,0.055)";
    weekendRanges.forEach(([s, e]) => {
      const x0 = x.getPixelForValue(s);
      const x1 = Math.min(x.getPixelForValue(e), x.right);
      ctx.fillRect(x0, y.top, x1 - x0, y.bottom - y.top);
    });
    // Weekend label in first band
    const midX = (x.getPixelForValue(4.5) + x.getPixelForValue(6.5)) / 2;
    ctx.fillStyle = THEME === "light"
      ? "rgba(26,26,23,0.25)"
      : "rgba(240,239,232,0.25)";
    ctx.font = `13px ${Chart.defaults.font.family}`;
    ctx.textAlign = "center";
    ctx.fillText("Weekend", midX, y.bottom - 14);
    ctx.restore();
  },
};

// Plugin: vertical amber line + label at the scope change day.
const scopeChangePlugin = {
  id: "scopeChangeMarker",
  afterDatasetsDraw(chart) {
    const { ctx, scales: { x, y } } = chart;
    const xPos = x.getPixelForValue(scopeChangeIndex);
    ctx.save();
    ctx.setLineDash([5, 4]);
    ctx.strokeStyle = t.amber;
    ctx.lineWidth = 2.5;
    ctx.beginPath();
    ctx.moveTo(xPos, y.top);
    ctx.lineTo(xPos, y.bottom);
    ctx.stroke();
    ctx.setLineDash([]);
    ctx.fillStyle = t.amber;
    ctx.font = `bold 14px ${Chart.defaults.font.family}`;
    ctx.textAlign = "left";
    ctx.fillText("+9 pts", xPos + 7, y.top + 26);
    ctx.font = `13px ${Chart.defaults.font.family}`;
    ctx.fillText("scope added", xPos + 7, y.top + 44);
    ctx.restore();
  },
};

// Mount canvas into the harness container.
const canvas = document.createElement("canvas");
document.getElementById("container").appendChild(canvas);

new Chart(canvas, {
  type: "line",
  data: {
    labels,
    datasets: [
      {
        label: "Actual Remaining",
        data: remaining,
        borderColor: t.palette[0],     // Imprint brand green
        borderWidth: 3,
        pointRadius: 5,
        pointHoverRadius: 7,
        pointBackgroundColor: t.palette[0],
        pointBorderColor: t.pageBg,
        pointBorderWidth: 2,
        stepped: "after",
        // Red fill when actual > ideal (behind schedule); green when actual < ideal (ahead).
        // Dark mode uses higher opacity so fills remain visible on the near-black surface.
        fill: {
          target: 1,
          above: THEME === "dark" ? "rgba(174,48,48,0.22)" : "rgba(174,48,48,0.12)",
          below: THEME === "dark" ? "rgba(0,158,115,0.22)" : "rgba(0,158,115,0.12)",
        },
      },
      {
        label: "Ideal Burndown",
        data: ideal,
        borderColor: t.inkSoft,
        borderWidth: 2,
        borderDash: [8, 5],
        pointRadius: 0,
        tension: 0,
        fill: false,
      },
    ],
  },
  options: {
    responsive: true,
    maintainAspectRatio: false,
    animation: false,
    interaction: {
      mode: "index",
      intersect: false,
    },
    plugins: {
      title: {
        display: true,
        text: "burndown-sprint · javascript · chartjs · anyplot.ai",
        color: t.ink,
        font: { size: 22, weight: "bold" },
        padding: { top: 8, bottom: 16 },
      },
      legend: {
        position: "top",
        labels: {
          color: t.ink,
          font: { size: 16 },
          padding: 24,
          usePointStyle: true,
          pointStyleWidth: 20,
        },
      },
    },
    scales: {
      x: {
        ticks: {
          color: t.inkSoft,
          font: { size: 14 },
          maxRotation: 45,
          minRotation: 0,
        },
        grid: { color: t.grid },
        border: { display: false },
        title: {
          display: true,
          text: "Sprint Day",
          color: t.ink,
          font: { size: 16 },
          padding: { top: 8 },
        },
      },
      y: {
        beginAtZero: true,
        ticks: {
          color: t.inkSoft,
          font: { size: 14 },
        },
        grid: { color: t.grid },
        border: { display: false },
        title: {
          display: true,
          text: "Remaining Story Points",
          color: t.ink,
          font: { size: 16 },
          padding: { bottom: 8 },
        },
      },
    },
  },
  plugins: [weekendPlugin, scopeChangePlugin],
});

Part of Agile Sprint Burndown Chart on anyplot.ai.

Other implementations