{"spec_id":"line-annotated-events","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nline-annotated-events: Annotated Line Plot with Event Markers\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 89/100 | Updated: 2026-05-16\n\"\"\"\n\nif __name__ == \"__main__\":\n    import os\n    import time\n    from pathlib import Path\n\n    import numpy as np\n    import pandas as pd\n    from bokeh.io import output_file, save\n    from bokeh.models import ColumnDataSource, Label, Span\n    from bokeh.plotting import figure\n    from bokeh.resources import CDN\n    from selenium import webdriver\n    from selenium.webdriver.chrome.options import Options\n\n    THEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\n    PAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\n    ELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\n    INK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\n    INK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n    np.random.seed(42)\n    dates = pd.date_range(\"2024-01-01\", periods=365, freq=\"D\")\n    base_trend = np.linspace(100, 180, 365)\n    seasonal = 15 * np.sin(np.arange(365) * 2 * np.pi / 365)\n    noise = np.cumsum(np.random.randn(365) * 0.8)\n    values = base_trend + seasonal + noise\n\n    event_dates = pd.to_datetime([\"2024-02-15\", \"2024-05-01\", \"2024-07-20\", \"2024-09-10\", \"2024-11-25\"])\n    event_labels = [\"Product Launch\", \"Feature Update\", \"Server Upgrade\", \"API v2 Release\", \"Mobile App Launch\"]\n    event_heights = [0.92, 0.84, 0.92, 0.84, 0.92]\n\n    source = ColumnDataSource(data={\"date\": dates, \"value\": values})\n\n    p = figure(\n        width=4800,\n        height=2700,\n        title=\"line-annotated-events · bokeh · anyplot.ai\",\n        x_axis_type=\"datetime\",\n        x_axis_label=\"Date\",\n        y_axis_label=\"Active Users (thousands)\",\n    )\n\n    p.line(\"date\", \"value\", source=source, line_width=4, color=\"#009E73\", legend_label=\"Daily Active Users\")\n\n    for event_date, label, h in zip(event_dates, event_labels, event_heights, strict=True):\n        vline = Span(location=event_date, dimension=\"height\", line_color=\"#FFD43B\", line_width=3, line_dash=\"dashed\")\n        p.add_layout(vline)\n\n        y_range = values.max() - values.min()\n        y_pos = values.min() + y_range * h\n        event_label = Label(\n            x=event_date,\n            y=y_pos,\n            text=label,\n            text_font_size=\"16pt\",\n            text_color=INK,\n            text_font_style=\"bold\",\n            x_offset=5,\n            y_offset=0,\n        )\n        p.add_layout(event_label)\n\n    p.title.text_font_size = \"28pt\"\n    p.title.text_color = INK\n    p.xaxis.axis_label_text_font_size = \"22pt\"\n    p.xaxis.axis_label_text_color = INK\n    p.yaxis.axis_label_text_font_size = \"22pt\"\n    p.yaxis.axis_label_text_color = INK\n    p.xaxis.major_label_text_font_size = \"18pt\"\n    p.xaxis.major_label_text_color = INK_SOFT\n    p.yaxis.major_label_text_font_size = \"18pt\"\n    p.yaxis.major_label_text_color = INK_SOFT\n    p.xaxis.axis_line_color = INK_SOFT\n    p.yaxis.axis_line_color = INK_SOFT\n    p.xaxis.major_tick_line_color = INK_SOFT\n    p.yaxis.major_tick_line_color = INK_SOFT\n\n    p.xgrid.grid_line_color = INK\n    p.ygrid.grid_line_color = INK\n    p.xgrid.grid_line_alpha = 0.10\n    p.ygrid.grid_line_alpha = 0.10\n\n    p.legend.label_text_font_size = \"18pt\"\n    p.legend.location = \"bottom_right\"\n    p.legend.background_fill_color = ELEVATED_BG\n    p.legend.border_line_color = INK_SOFT\n    p.legend.label_text_color = INK_SOFT\n\n    p.background_fill_color = PAGE_BG\n    p.border_fill_color = PAGE_BG\n    p.outline_line_color = INK_SOFT\n\n    p.toolbar_location = None\n\n    output_file(f\"plot-{THEME}.html\")\n    save(p, resources=CDN, title=\"line-annotated-events · bokeh · anyplot.ai\")\n\n    W, H = 4800, 2700\n    opts = Options()\n    for arg in (\n        \"--headless=new\",\n        \"--no-sandbox\",\n        \"--disable-dev-shm-usage\",\n        \"--disable-gpu\",\n        f\"--window-size={W},{H}\",\n        \"--hide-scrollbars\",\n    ):\n        opts.add_argument(arg)\n    driver = webdriver.Chrome(options=opts)\n    driver.set_window_size(W, H)\n    driver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\n    time.sleep(3)\n    driver.save_screenshot(f\"plot-{THEME}.png\")\n    driver.quit()\n"}