{"spec_id":"stock-event-flags","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nstock-event-flags: Stock Chart with Event Flags\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 83/100 | Updated: 2026-05-27\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nimport pygal\nfrom pygal.style import Style\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Data — seed 999, Jul 2023 start differentiates from sibling implementations\nnp.random.seed(999)\nstart_date = pd.Timestamp(\"2023-07-03\")\ntrading_days = pd.bdate_range(start=start_date, periods=200)\ninitial_price = 240.0\nreturns = np.random.normal(0.0004, 0.016, len(trading_days))\nprices = initial_price * np.cumprod(1 + returns)\ndf = pd.DataFrame({\"date\": trading_days, \"close\": prices})\n\n# Events — corporate actions for a tech company over the period\nevents = [\n    {\"date\": pd.Timestamp(\"2023-07-27\"), \"type\": \"earnings\", \"label\": \"Q2 Beat\"},\n    {\"date\": pd.Timestamp(\"2023-08-17\"), \"type\": \"dividend\", \"label\": \"$0.22\"},\n    {\"date\": pd.Timestamp(\"2023-09-14\"), \"type\": \"news\", \"label\": \"Partnership\"},\n    {\"date\": pd.Timestamp(\"2023-10-26\"), \"type\": \"earnings\", \"label\": \"Q3 Miss\"},\n    {\"date\": pd.Timestamp(\"2023-11-16\"), \"type\": \"dividend\", \"label\": \"$0.24\"},\n    {\"date\": pd.Timestamp(\"2023-12-05\"), \"type\": \"news\", \"label\": \"FDA Approval\"},\n    {\"date\": pd.Timestamp(\"2024-01-25\"), \"type\": \"earnings\", \"label\": \"Q4 Beat\"},\n    {\"date\": pd.Timestamp(\"2024-02-15\"), \"type\": \"dividend\", \"label\": \"$0.26\"},\n    {\"date\": pd.Timestamp(\"2024-03-11\"), \"type\": \"split\", \"label\": \"2:1 Split\"},\n]\n\n# Event colors from Imprint palette (positions 2-5; position 1 reserved for price line)\nevent_type_colors = {\n    \"earnings\": \"#C475FD\",  # lavender\n    \"dividend\": \"#4467A3\",  # blue — steady income\n    \"news\": \"#BD8233\",  # ochre — announcements\n    \"split\": \"#AE3030\",  # red — significant corporate action\n}\n\n# Connector line width varies by event significance to create visual hierarchy\nevent_stroke_width = {\n    \"earnings\": 3,  # notable — quarterly results move prices\n    \"split\": 4,  # major corporate action, thickest connector\n    \"news\": 2,\n    \"dividend\": 2,\n}\n\n# Build full color sequence for pygal's cycling: price line + connectors + flag groups\nconnector_colors = [event_type_colors[e[\"type\"]] for e in events]\nflag_colors = [event_type_colors[t] for t in [\"earnings\", \"dividend\", \"news\", \"split\"]]\nall_colors = tuple([\"#009E73\"] + connector_colors + flag_colors)\n\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=all_colors,\n    title_font_size=66,\n    label_font_size=56,\n    major_label_font_size=44,\n    legend_font_size=44,\n    value_font_size=36,\n    stroke_width=3,\n)\n\nchart = pygal.XY(\n    width=3200,\n    height=1800,\n    style=custom_style,\n    title=\"Tech Stock 2023 · stock-event-flags · python · pygal · anyplot.ai\",\n    x_title=\"Date\",\n    y_title=\"Stock Price ($)\",\n    show_x_guides=False,\n    show_y_guides=True,\n    stroke=True,\n    fill=False,\n    show_legend=True,\n    legend_at_bottom=True,\n    legend_at_bottom_columns=5,\n    dots_size=3,\n    truncate_label=-1,\n    truncate_legend=-1,\n    margin=60,\n    spacing=30,\n    print_labels=True,\n)\n\n# Main stock price line (first series → #009E73 brand green)\nprice_points = [(i, df.iloc[i][\"close\"]) for i in range(len(df))]\nchart.add(\"Price\", price_points, dots_size=0, stroke_style={\"width\": 4})\n\n# Date labels at monthly boundaries — replaces numeric trading day index with dates\n# 9 months Jul '23 → Mar '24 over 200 trading days ≈ evenly distributed\nchart.x_labels = [\"Jul '23\", \"Aug '23\", \"Sep '23\", \"Oct '23\", \"Nov '23\", \"Dec '23\", \"Jan '24\", \"Feb '24\", \"Mar '24\"]\n\n# Flag positioning above the price range\nmin_price = df[\"close\"].min()\nmax_price = df[\"close\"].max()\nprice_range = max_price - min_price\n\nevent_heights = {\"earnings\": 0.12, \"dividend\": 0.20, \"news\": 0.28, \"split\": 0.36}\n\n# Connector lines — dashed verticals from price level to flag position\n# Width varies by event type to emphasize earnings and split events\nfor event in events:\n    idx = df[\"date\"].searchsorted(event[\"date\"])\n    if idx < len(df):\n        flag_y = max_price + price_range * event_heights[event[\"type\"]]\n        price_at_event = df.iloc[idx][\"close\"]\n        stroke_w = event_stroke_width[event[\"type\"]]\n        chart.add(\n            None,\n            [(idx, price_at_event), (idx, flag_y)],\n            stroke=True,\n            stroke_style={\"width\": stroke_w, \"dasharray\": \"6,4\"},\n            show_dots=False,\n        )\n\n# Flag markers grouped by event type (drives legend)\n# print_labels=True on chart makes event labels visible in the PNG render\nfor event_type in [\"earnings\", \"dividend\", \"news\", \"split\"]:\n    type_events = [e for e in events if e[\"type\"] == event_type]\n    flag_y = max_price + price_range * event_heights[event_type]\n    flag_points = [\n        {\"value\": (df[\"date\"].searchsorted(e[\"date\"]), flag_y), \"label\": e[\"label\"]}\n        for e in type_events\n        if df[\"date\"].searchsorted(e[\"date\"]) < len(df)\n    ]\n    chart.add(event_type.capitalize(), flag_points, stroke=False, show_dots=True, dots_size=18)\n\n# Save\nchart.render_to_png(f\"plot-{THEME}.png\")\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(chart.render())\n"}