{"spec_id":"timeline-basic","library":"altair","language":"python","code":"\"\"\" anyplot.ai\ntimeline-basic: Event Timeline\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 96/100 | Updated: 2026-05-11\n\"\"\"\n\nimport os\n\nimport altair as alt\nimport pandas as pd\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\"\n\n# Okabe-Ito palette for categories\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n\n# Data: Software project milestones\ndata = pd.DataFrame(\n    {\n        \"date\": pd.to_datetime(\n            [\n                \"2024-01-15\",\n                \"2024-02-20\",\n                \"2024-03-10\",\n                \"2024-04-05\",\n                \"2024-05-01\",\n                \"2024-06-15\",\n                \"2024-07-20\",\n                \"2024-08-30\",\n                \"2024-09-15\",\n                \"2024-10-25\",\n                \"2024-11-10\",\n                \"2024-12-01\",\n            ]\n        ),\n        \"event\": [\n            \"Project Kickoff\",\n            \"Requirements Complete\",\n            \"Design Review\",\n            \"Development Start\",\n            \"Alpha Release\",\n            \"Beta Testing\",\n            \"Security Audit\",\n            \"Performance Testing\",\n            \"User Acceptance\",\n            \"Release Candidate\",\n            \"Documentation\",\n            \"Production Launch\",\n        ],\n        \"category\": [\n            \"Planning\",\n            \"Planning\",\n            \"Planning\",\n            \"Development\",\n            \"Development\",\n            \"Testing\",\n            \"Testing\",\n            \"Testing\",\n            \"Testing\",\n            \"Release\",\n            \"Release\",\n            \"Release\",\n        ],\n    }\n)\n\n# Alternate label positions to prevent overlap (above/below axis)\ndata[\"y_offset\"] = [1.5 if i % 2 == 0 else -1.5 for i in range(len(data))]\ndata[\"y_zero\"] = 0\ndata[\"y_label\"] = [2.4 if i % 2 == 0 else -2.4 for i in range(len(data))]\n\n# Color scale using Okabe-Ito palette\ncategory_colors = {\n    \"Planning\": IMPRINT[0],\n    \"Development\": IMPRINT[1],\n    \"Testing\": IMPRINT[2],\n    \"Release\": IMPRINT[3],\n}\ncolor_scale = alt.Scale(domain=list(category_colors.keys()), range=list(category_colors.values()))\n\n# Shared y scale\ny_scale = alt.Scale(domain=[-3.5, 3.5])\n\n# Vertical connector lines from axis to points\nconnectors = (\n    alt.Chart(data)\n    .mark_rule(strokeWidth=3, opacity=0.7)\n    .encode(\n        x=\"date:T\",\n        y=alt.Y(\"y_zero:Q\", scale=y_scale),\n        y2=\"y_offset:Q\",\n        color=alt.Color(\"category:N\", scale=color_scale, legend=None),\n    )\n)\n\n# Event markers on the timeline\npoints = (\n    alt.Chart(data)\n    .mark_circle(size=600, stroke=PAGE_BG, strokeWidth=3)\n    .encode(\n        x=alt.X(\n            \"date:T\",\n            axis=alt.Axis(\n                title=\"Date\",\n                format=\"%b %Y\",\n                labelFontSize=18,\n                titleFontSize=22,\n                labelAngle=-45,\n                grid=False,\n                labelColor=INK_SOFT,\n                titleColor=INK,\n            ),\n        ),\n        y=alt.Y(\"y_offset:Q\", scale=y_scale),\n        color=alt.Color(\"category:N\", scale=color_scale, legend=None),\n        tooltip=[\n            alt.Tooltip(\"date:T\", title=\"Date\", format=\"%B %d, %Y\"),\n            alt.Tooltip(\"event:N\", title=\"Event\"),\n            alt.Tooltip(\"category:N\", title=\"Phase\"),\n        ],\n    )\n)\n\n# Central timeline axis line using rule from min to max date\ntimeline_line = alt.Chart(data).mark_rule(color=INK_SOFT, strokeWidth=4).encode(y=alt.Y(\"y_zero:Q\", scale=y_scale))\n\n# Event labels positioned above/below points\nlabels = (\n    alt.Chart(data)\n    .mark_text(align=\"center\", fontSize=18, fontWeight=\"bold\")\n    .encode(x=\"date:T\", y=alt.Y(\"y_label:Q\", scale=y_scale), text=\"event:N\", color=alt.value(INK))\n)\n\n# Create inline legend using text and point marks\nlegend_data = pd.DataFrame(\n    {\n        \"category\": list(category_colors.keys()),\n        \"x_pos\": [\n            pd.Timestamp(\"2024-01-15\"),\n            pd.Timestamp(\"2024-04-05\"),\n            pd.Timestamp(\"2024-07-20\"),\n            pd.Timestamp(\"2024-10-25\"),\n        ],\n        \"y_pos\": [3.0, 3.0, 3.0, 3.0],\n    }\n)\n\nlegend_points = (\n    alt.Chart(legend_data)\n    .mark_circle(size=300, stroke=PAGE_BG, strokeWidth=2)\n    .encode(\n        x=alt.X(\"x_pos:T\"),\n        y=alt.Y(\"y_pos:Q\", scale=y_scale),\n        color=alt.Color(\"category:N\", scale=color_scale, legend=None),\n    )\n)\n\nlegend_labels = (\n    alt.Chart(legend_data)\n    .mark_text(align=\"left\", fontSize=16, fontWeight=\"bold\", dx=15)\n    .encode(x=alt.X(\"x_pos:T\"), y=alt.Y(\"y_pos:Q\", scale=y_scale), text=\"category:N\", color=alt.value(INK_SOFT))\n)\n\n# Combine all layers\nchart = (\n    alt.layer(timeline_line, connectors, points, labels, legend_points, legend_labels)\n    .properties(\n        width=1600,\n        height=900,\n        background=PAGE_BG,\n        title=alt.Title(\"timeline-basic · altair · anyplot.ai\", fontSize=28, anchor=\"middle\", color=INK),\n    )\n    .configure_view(strokeWidth=0, fill=PAGE_BG)\n    .configure_axisY(disable=True)\n    .interactive()\n)\n\n# Save outputs\nchart.save(f\"plot-{THEME}.png\", scale_factor=3.0)\nchart.save(f\"plot-{THEME}.html\")\n"}