{"spec_id":"line-annotated-events","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nline-annotated-events: Annotated Line Plot with Event Markers\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 93/100 | Updated: 2026-05-16\n\"\"\"\n\nimport os\nimport sys\n\n\nsys.path.pop(0)\n\nimport altair as alt\nimport numpy as np\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\nBRAND = \"#009E73\"  # Okabe-Ito position 1 (bluish green)\nEVENT_COLOR = \"#C475FD\"  # Okabe-Ito position 2 (vermillion)\n\n# Data\nnp.random.seed(42)\n\n# Generate daily stock price data for one year\ndates = pd.date_range(start=\"2024-01-01\", periods=250, freq=\"B\")  # Business days\nbase_price = 150\nreturns = np.random.normal(0.0005, 0.015, len(dates))\nprices = base_price * np.exp(np.cumsum(returns))\n\ndf = pd.DataFrame({\"date\": dates, \"price\": prices})\n\n# Define key events (quarterly earnings and other milestones)\nevents = pd.DataFrame(\n    {\n        \"event_date\": pd.to_datetime(\n            [\"2024-02-15\", \"2024-04-25\", \"2024-06-10\", \"2024-07-24\", \"2024-09-18\", \"2024-11-05\"]\n        ),\n        \"event_label\": [\n            \"Q4 Earnings\",\n            \"Q1 Earnings\",\n            \"Product Launch\",\n            \"Q2 Earnings\",\n            \"Analyst Upgrade\",\n            \"Q3 Earnings\",\n        ],\n        \"event_type\": [\"Earnings\", \"Earnings\", \"Product\", \"Earnings\", \"Analyst\", \"Earnings\"],\n    }\n)\n\n# Get y positions for event labels (alternating heights to avoid overlap)\nevents[\"y_position\"] = [\n    prices.max() * 1.08,\n    prices.max() * 1.02,\n    prices.max() * 1.08,\n    prices.max() * 1.02,\n    prices.max() * 1.08,\n    prices.max() * 1.02,\n]\n\n# Base line chart for stock price\nline = (\n    alt.Chart(df)\n    .mark_line(strokeWidth=3, color=BRAND)\n    .encode(\n        x=alt.X(\"date:T\", title=\"Date\"),\n        y=alt.Y(\"price:Q\", title=\"Stock Price ($)\", scale=alt.Scale(domain=[prices.min() * 0.95, prices.max() * 1.12])),\n    )\n)\n\n# Get the price range for dynamic rule scaling\nprice_min = prices.min() * 0.95\nprice_max = prices.max() * 1.12\n\n# Vertical rule marks for events (dynamic y2 based on price range)\nrules = (\n    alt.Chart(events)\n    .mark_rule(strokeWidth=2, strokeDash=[6, 4], color=EVENT_COLOR, opacity=0.7)\n    .encode(x=alt.X(\"event_date:T\"), y=alt.value(price_min), y2=alt.value(price_max))\n)\n\n# Event markers (points at the top)\nmarkers = (\n    alt.Chart(events)\n    .mark_point(size=300, filled=True, color=EVENT_COLOR, stroke=INK, strokeWidth=2)\n    .encode(x=alt.X(\"event_date:T\"), y=alt.Y(\"y_position:Q\"), tooltip=[\"event_label:N\", \"event_type:N\", \"event_date:T\"])\n)\n\n# Event labels\nlabels = (\n    alt.Chart(events)\n    .mark_text(align=\"center\", baseline=\"bottom\", fontSize=14, fontWeight=\"bold\", color=INK, dy=-15)\n    .encode(x=alt.X(\"event_date:T\"), y=alt.Y(\"y_position:Q\"), text=\"event_label:N\")\n)\n\n# Combine all layers\nchart = (\n    alt.layer(line, rules, markers, labels)\n    .properties(\n        width=1600,\n        height=900,\n        background=PAGE_BG,\n        title=alt.Title(\"line-annotated-events · altair · anyplot.ai\", fontSize=28, anchor=\"middle\"),\n    )\n    .configure_view(fill=PAGE_BG, stroke=INK_SOFT, strokeWidth=0)\n    .configure_axis(\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        gridColor=INK,\n        gridOpacity=0.10,\n        labelColor=INK_SOFT,\n        labelFontSize=18,\n        titleColor=INK,\n        titleFontSize=22,\n    )\n    .configure_title(color=INK, fontSize=28)\n    .configure_legend(\n        fillColor=ELEVATED_BG,\n        strokeColor=INK_SOFT,\n        labelColor=INK_SOFT,\n        labelFontSize=16,\n        titleColor=INK,\n        titleFontSize=18,\n    )\n)\n\n# Save as PNG and HTML\nchart.save(f\"plot-{THEME}.png\", scale_factor=3.0)\nchart.interactive().save(f\"plot-{THEME}.html\")\n"}