{"spec_id":"line-stock-comparison","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nline-stock-comparison: Stock Price Comparison Chart\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 84/100 | Updated: 2026-05-23\n\"\"\"\n\nimport datetime\nimport os\nimport sys\n\n\n# Remove script dir from sys.path to avoid shadowing the pygal package\n_script_dir = sys.path[0] if sys.path else \"\"\nif _script_dir in sys.path:\n    sys.path.remove(_script_dir)\n\nimport numpy as np\nimport pygal\nfrom pygal.style import Style\n\n\nif _script_dir:\n    sys.path.insert(0, _script_dir)\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\nIMPRINT = (\"#009E73\", \"#C475FD\", \"#AE3030\", \"#4467A3\", \"#99B314\", \"#954477\", \"#BD8233\")\n\n# Extended palette: data series use positions 1-4, INK_MUTED reserved for the reference line\nPALETTE_WITH_REF = IMPRINT[:4] + (INK_MUTED,)\n\n# Data\nnp.random.seed(42)\nn_days = 252\n\nstart_date = datetime.date(2024, 1, 2)\ndates = []\ncurrent_date = start_date\nwhile len(dates) < n_days:\n    if current_date.weekday() < 5:\n        dates.append(current_date)\n    current_date += datetime.timedelta(days=1)\n\nreturns_aapl = np.random.normal(0.0012, 0.018, n_days)\nreturns_googl = np.random.normal(0.0015, 0.022, n_days)\nreturns_msft = np.random.normal(-0.0002, 0.016, n_days)\nreturns_spy = np.random.normal(0.0004, 0.010, n_days)\n\nprice_aapl = 100 * np.cumprod(1 + returns_aapl)\nprice_googl = 100 * np.cumprod(1 + returns_googl)\nprice_msft = 100 * np.cumprod(1 + returns_msft)\nprice_spy = 100 * np.cumprod(1 + returns_spy)\n\nrebased_aapl = price_aapl / price_aapl[0] * 100\nrebased_googl = price_googl / price_googl[0] * 100\nrebased_msft = price_msft / price_msft[0] * 100\nrebased_spy = price_spy / price_spy[0] * 100\n\n# Year-end returns for legend labels (data storytelling)\nfinal_returns = {\n    \"AAPL\": rebased_aapl[-1] - 100,\n    \"GOOGL\": rebased_googl[-1] - 100,\n    \"MSFT\": rebased_msft[-1] - 100,\n    \"SPY\": rebased_spy[-1] - 100,\n}\n\n\ndef fmt_ret(r):\n    sign = \"+\" if r >= 0 else \"\"\n    return f\"{sign}{r:.1f}%\"\n\n\n# Best performer gets a thicker stroke for visual emphasis\nbest = max(final_returns, key=final_returns.get)\n\n\ndef series_stroke(name):\n    return {\"width\": 6} if name == best else {\"width\": 3}\n\n\n# Style\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=PALETTE_WITH_REF,\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=4,\n    font_family=\"sans-serif\",\n)\n\n# X-axis labels — monthly markers only\nx_labels_all = [d.strftime(\"%b %Y\") if i == 0 or d.month != dates[i - 1].month else \"\" for i, d in enumerate(dates)]\nx_labels_major = [label for label in x_labels_all if label]\n\n# Chart\nchart = pygal.Line(\n    width=3200,\n    height=1800,\n    style=custom_style,\n    title=\"line-stock-comparison · python · pygal · anyplot.ai\",\n    x_title=\"Date\",\n    y_title=\"Rebased Price (Start = 100)\",\n    show_x_guides=False,\n    show_y_guides=True,\n    dots_size=2,\n    legend_at_bottom=True,\n    legend_at_bottom_columns=5,\n    x_label_rotation=45,\n    show_minor_x_labels=False,\n    x_labels_major=x_labels_major,\n    margin_bottom=120,\n)\n\nchart.x_labels = x_labels_all\n\nchart.add(\n    f\"AAPL ({fmt_ret(final_returns['AAPL'])})\",\n    [\n        {\"value\": val, \"label\": f\"AAPL | {dates[i].strftime('%Y-%m-%d')} | {val:.1f}\"}\n        for i, val in enumerate(rebased_aapl.tolist())\n    ],\n    stroke_style=series_stroke(\"AAPL\"),\n)\nchart.add(\n    f\"GOOGL ({fmt_ret(final_returns['GOOGL'])})\",\n    [\n        {\"value\": val, \"label\": f\"GOOGL | {dates[i].strftime('%Y-%m-%d')} | {val:.1f}\"}\n        for i, val in enumerate(rebased_googl.tolist())\n    ],\n    stroke_style=series_stroke(\"GOOGL\"),\n)\nchart.add(\n    f\"MSFT ({fmt_ret(final_returns['MSFT'])})\",\n    [\n        {\"value\": val, \"label\": f\"MSFT | {dates[i].strftime('%Y-%m-%d')} | {val:.1f}\"}\n        for i, val in enumerate(rebased_msft.tolist())\n    ],\n    stroke_style=series_stroke(\"MSFT\"),\n)\nchart.add(\n    f\"SPY ({fmt_ret(final_returns['SPY'])})\",\n    [\n        {\"value\": val, \"label\": f\"SPY | {dates[i].strftime('%Y-%m-%d')} | {val:.1f}\"}\n        for i, val in enumerate(rebased_spy.tolist())\n    ],\n    stroke_style=series_stroke(\"SPY\"),\n)\n\n# Horizontal reference line at y=100 anchors the starting baseline visually\nchart.add(\"Baseline (100)\", [100.0] * n_days, show_dots=False, stroke_style={\"width\": 2, \"dasharray\": \"6,4\"})\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"}