{"spec_id":"burndown-sprint","library":"echarts","language":"javascript","code":"// anyplot.ai\n// burndown-sprint: Agile Sprint Burndown Chart\n// Library: echarts 5.5.1 | JavaScript 22.22.3\n// Quality: 91/100 | Created: 2026-06-14\n//# anyplot-orientation: landscape\n\nconst t = window.ANYPLOT_TOKENS;\n\n// Sprint: June 1–13 2025, 10 working days, initial scope 40 story points\n// Scope change: +8 points added on Thu Jun 5 (Day 4)\nconst ms = (s) => new Date(s).getTime();\n\nconst actual = [\n  [ms('2025-06-01'), 40], // Sprint kick-off (committed scope)\n  [ms('2025-06-02'), 37], // Mon Day 1: burned 3\n  [ms('2025-06-03'), 33], // Tue Day 2: burned 4\n  [ms('2025-06-04'), 30], // Wed Day 3: burned 3\n  [ms('2025-06-05'), 34], // Thu Day 4: burned 4, +8 scope change added\n  [ms('2025-06-06'), 30], // Fri Day 5: burned 4\n  [ms('2025-06-09'), 25], // Mon Day 6: burned 5 (weekend flat at 30)\n  [ms('2025-06-10'), 20], // Tue Day 7: burned 5\n  [ms('2025-06-11'), 14], // Wed Day 8: burned 6\n  [ms('2025-06-12'),  8], // Thu Day 9: burned 6\n  [ms('2025-06-13'),  3], // Fri Day 10: burned 5 — sprint ends, 3 pts remain\n];\n\n// Ideal burndown: straight line from committed scope (40) to zero\nconst ideal = Array.from({ length: 13 }, (_, i) => {\n  const day = new Date('2025-06-01');\n  day.setDate(day.getDate() + i);\n  return [day.getTime(), Math.round(40 * (12 - i) / 12)];\n});\n\nconst chart = echarts.init(document.getElementById('container'));\n\nchart.setOption({\n  animation: false,\n  color: t.palette,\n  backgroundColor: 'transparent',\n\n  title: {\n    text: 'burndown-sprint · javascript · echarts · anyplot.ai',\n    left: 'center',\n    top: 20,\n    textStyle: { color: t.ink, fontSize: 22, fontWeight: 'bold' },\n  },\n\n  legend: {\n    top: 60,\n    textStyle: { color: t.inkSoft, fontSize: 14 },\n    icon: 'roundRect',\n  },\n\n  tooltip: {\n    trigger: 'axis',\n    backgroundColor: t.elevatedBg,\n    borderColor: t.grid,\n    textStyle: { color: t.ink, fontSize: 13 },\n    axisPointer: { lineStyle: { color: t.inkSoft } },\n    formatter: (params) => {\n      const day = new Date(params[0].data[0]);\n      const label = day.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' });\n      const rows = params\n        .filter((p) => p.data && p.data.length > 1 && p.data[1] != null)\n        .map((p) => `${p.marker} ${p.seriesName}: <b>${p.data[1]} pts</b>`);\n      return `${label}<br/>${rows.join('<br/>')}`;\n    },\n  },\n\n  grid: { left: 120, right: 130, top: 120, bottom: 80 },\n\n  xAxis: {\n    type: 'time',\n    min: ms('2025-06-01'),\n    max: ms('2025-06-13'),\n    axisLabel: {\n      color: t.inkSoft,\n      fontSize: 13,\n      formatter: (val) =>\n        new Date(val).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),\n    },\n    axisLine: { show: false },\n    splitLine: { show: false },\n  },\n\n  yAxis: {\n    type: 'value',\n    name: 'Story Points Remaining',\n    nameLocation: 'middle',\n    nameRotate: 90,\n    nameGap: 60,\n    nameTextStyle: { color: t.inkSoft, fontSize: 14 },\n    min: 0,\n    max: 50,\n    axisLabel: { color: t.inkSoft, fontSize: 13 },\n    axisLine: { show: false },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n\n  series: [\n    {\n      name: 'Actual Remaining',\n      type: 'line',\n      step: 'end',\n      data: actual,\n      itemStyle: { color: t.palette[0] },\n      lineStyle: { color: t.palette[0], width: 4 },\n      symbol: 'circle',\n      symbolSize: 8,\n      areaStyle: { color: t.palette[0], opacity: 0.07 },\n      // Sprint outcome callout — tells the story at a glance\n      endLabel: {\n        show: true,\n        formatter: '3 pts remaining',\n        color: t.ink,\n        fontSize: 12,\n        fontWeight: 'bold',\n        distance: 8,\n        backgroundColor: t.elevatedBg,\n        borderColor: t.grid,\n        borderWidth: 1,\n        padding: [4, 8],\n        borderRadius: 4,\n      },\n      markArea: {\n        silent: true,\n        itemStyle: { color: t.inkSoft, opacity: 0.10 },\n        label: {\n          show: true,\n          position: 'insideTop',\n          color: t.inkSoft,\n          fontSize: 12,\n          formatter: 'Weekend',\n        },\n        data: [[{ xAxis: ms('2025-06-07') }, { xAxis: ms('2025-06-09') }]],\n      },\n      markLine: {\n        silent: true,\n        symbol: ['none', 'none'],\n        lineStyle: { color: t.amber, width: 2, type: 'dashed' },\n        // Amber badge (dark ink on amber bg) — high contrast on both light and dark themes\n        label: {\n          show: true,\n          formatter: '{badge|+8 pts  Scope Change}',\n          rich: {\n            badge: {\n              color: '#1A1A17',\n              backgroundColor: '#DDCC77',\n              borderRadius: 4,\n              padding: [3, 8, 3, 8],\n              fontSize: 12,\n            },\n          },\n          position: 'end',\n        },\n        data: [{ xAxis: ms('2025-06-05'), name: 'Scope Change' }],\n      },\n    },\n    {\n      name: 'Ideal Burndown',\n      type: 'line',\n      data: ideal,\n      itemStyle: { color: t.inkSoft },\n      lineStyle: { color: t.inkSoft, width: 1.5, type: 'dashed' },\n      symbol: 'none',\n    },\n  ],\n});\n"}