{"spec_id":"burndown-sprint","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nburndown-sprint: Agile Sprint Burndown Chart\nLibrary: plotly 6.8.0 | Python 3.13.13\nQuality: 88/100 | Created: 2026-06-14\n\"\"\"\n\nimport os\nimport sys\n\n\n# Remove this script's directory from sys.path so 'plotly' resolves to the\n# installed package and not this file (which shares the package name).\n_script_dir = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if p != _script_dir]\n\nimport pandas as pd\nimport plotly.graph_objects as go\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\nWEEKEND_FILL = \"rgba(26,26,23,0.05)\" if THEME == \"light\" else \"rgba(240,239,232,0.05)\"\n\n# Imprint palette — first series always #009E73\nBRAND = \"#009E73\"  # actual remaining-work line\nSCOPE_COLOR = \"#AE3030\"  # scope change event (semantic red — risk/addition)\nANYPLOT_AMBER = \"#DDCC77\"  # behind-schedule indicator (Imprint warning anchor)\n\n# Data: 10-working-day sprint (Jan 6–17 2025), initial scope 40 SP\n# Scope addition of +8 on Jan 9 (working day 4) causes remaining to jump\nsprint_dates = pd.date_range(\"2025-01-06\", \"2025-01-17\", freq=\"D\").strftime(\"%Y-%m-%d\").tolist()\nremaining = [40, 36, 30, 38, 30, 30, 30, 22, 14, 8, 3, 0]\n\n# Ideal burndown: straight reference from (sprint start, 40) to (sprint end, 0)\nideal_dates = [sprint_dates[0], sprint_dates[-1]]\nideal_values = [40, 0]\n\n# Compute ideal at each sprint date for ahead/behind fill zones\nn = len(sprint_dates)\nideal_at_dates = [40 * (1 - i / (n - 1)) for i in range(n)]\n\n# Clip to create non-overlapping fill zones on each side of the ideal line\n# ahead_y = actual clipped to ≤ ideal (fills the green zone below ideal)\n# behind_y = actual clipped to ≥ ideal (fills the amber zone above ideal)\nahead_y = [min(r, idl) for r, idl in zip(remaining, ideal_at_dates, strict=True)]\nbehind_y = [max(r, idl) for r, idl in zip(remaining, ideal_at_dates, strict=True)]\n\nAHEAD_FILL = \"rgba(0,158,115,0.15)\"  # brand green tint — ahead of schedule\nBEHIND_FILL = \"rgba(221,204,119,0.28)\"  # amber tint — behind schedule\n\n# Build closed polygons for toself fill (traces the boundary of each zone)\nfill_x = sprint_dates + sprint_dates[::-1]\n# Ahead zone: bottom=ahead_y, top=ideal (going forward then back)\nahead_fill_y = ahead_y + ideal_at_dates[::-1]\n# Behind zone: bottom=ideal, top=behind_y (going forward then back)\nbehind_fill_y = behind_y + ideal_at_dates[::-1]\n\n# Title (46 chars — under 67-char baseline, no fontsize scaling needed)\ntitle = \"burndown-sprint · python · plotly · anyplot.ai\"\n\n# Figure\nfig = go.Figure()\n\n# Weekend shading (Jan 11 Sat – Jan 12 Sun)\nfig.add_vrect(\n    x0=\"2025-01-11\",\n    x1=\"2025-01-13\",\n    fillcolor=WEEKEND_FILL,\n    layer=\"below\",\n    line_width=0,\n    annotation_text=\"Weekend\",\n    annotation_position=\"top left\",\n    annotation_font={\"size\": 9, \"color\": INK_MUTED},\n)\n\n# Ahead-of-schedule fill: closed green polygon between actual and ideal (actual < ideal)\nfig.add_trace(\n    go.Scatter(\n        x=fill_x,\n        y=ahead_fill_y,\n        fill=\"toself\",\n        fillcolor=AHEAD_FILL,\n        mode=\"none\",\n        showlegend=False,\n        hoverinfo=\"none\",\n        line={\"color\": \"rgba(0,0,0,0)\"},\n    )\n)\n\n# Behind-schedule fill: closed amber polygon between ideal and actual (actual > ideal)\nfig.add_trace(\n    go.Scatter(\n        x=fill_x,\n        y=behind_fill_y,\n        fill=\"toself\",\n        fillcolor=BEHIND_FILL,\n        mode=\"none\",\n        showlegend=False,\n        hoverinfo=\"none\",\n        line={\"color\": \"rgba(0,0,0,0)\"},\n    )\n)\n\n# Ideal burndown reference line (dashed, theme-adaptive muted)\nfig.add_trace(\n    go.Scatter(\n        x=ideal_dates,\n        y=ideal_values,\n        mode=\"lines\",\n        name=\"Ideal burndown\",\n        line={\"color\": INK_SOFT, \"width\": 2, \"dash\": \"dash\"},\n    )\n)\n\n# Actual remaining work (step series — horizontal-then-vertical)\nfig.add_trace(\n    go.Scatter(\n        x=sprint_dates,\n        y=remaining,\n        mode=\"lines+markers\",\n        name=\"Remaining story points\",\n        line={\"color\": BRAND, \"width\": 3.5, \"shape\": \"hv\"},\n        marker={\"color\": BRAND, \"size\": 8, \"symbol\": \"circle\"},\n    )\n)\n\n# Scope change event on Jan 9 (working day 4, +8 SP added)\nfig.add_vline(\n    x=\"2025-01-09\",\n    line_width=1.5,\n    line_dash=\"dot\",\n    line_color=SCOPE_COLOR,\n    annotation_text=\"Scope +8 SP\",\n    annotation_position=\"top right\",\n    annotation_font={\"size\": 10, \"color\": SCOPE_COLOR},\n    annotation_bgcolor=ELEVATED_BG,\n    annotation_bordercolor=SCOPE_COLOR,\n    annotation_borderwidth=1,\n)\n\n# Layout\nfig.update_layout(\n    autosize=False,\n    margin={\"l\": 80, \"r\": 40, \"t\": 80, \"b\": 60},\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    title={\"text\": title, \"font\": {\"size\": 16, \"color\": INK}, \"x\": 0, \"xanchor\": \"left\", \"xref\": \"paper\"},\n    xaxis={\n        \"title\": {\"text\": \"Sprint Day\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"gridcolor\": GRID,\n        \"linecolor\": INK_SOFT,\n        \"zerolinecolor\": INK_SOFT,\n        \"tickformat\": \"%b %d\",\n        \"showgrid\": False,\n        \"showline\": True,\n        \"mirror\": False,\n    },\n    yaxis={\n        \"title\": {\"text\": \"Remaining Story Points\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"gridcolor\": GRID,\n        \"linecolor\": INK_SOFT,\n        \"zerolinecolor\": GRID,\n        \"rangemode\": \"tozero\",\n        \"showgrid\": True,\n        \"showline\": True,\n        \"mirror\": False,\n    },\n    legend={\n        \"bgcolor\": ELEVATED_BG,\n        \"borderwidth\": 0,\n        \"font\": {\"color\": INK_SOFT, \"size\": 10},\n        \"x\": 0.97,\n        \"y\": 0.97,\n        \"xanchor\": \"right\",\n        \"yanchor\": \"top\",\n    },\n    font={\"color\": INK},\n)\n\n# Save\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}