{"spec_id":"line-cycle-seasonal","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nline-cycle-seasonal: Cycle Plot (Seasonal Subseries)\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 88/100 | Created: 2026-06-15\n\"\"\"\n\nimport os\nimport sys\n\n\n# Remove this script's directory from sys.path so 'import seaborn' finds the installed\n# library rather than this file (Python adds the script dir to sys.path[0] automatically)\n_script_dir = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p or \".\") != _script_dir]\n\nimport matplotlib.lines as mlines\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\n\n\n# Theme tokens (Imprint palette — see prompts/default-style-guide.md)\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nBRAND = \"#009E73\"  # Imprint palette position 1 — always first series\n\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.15,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Data: monthly average temperatures (°C) over 24 years for a mid-latitude city\n# A warming trend of ~0.035 °C/year is embedded within each month's subseries\nnp.random.seed(42)\nyears = np.arange(2000, 2024)\nn_years = len(years)\n\nmonth_names = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"]\nbase_temps = np.array([1.8, 3.2, 7.5, 12.1, 17.0, 21.3, 24.1, 23.6, 18.8, 13.0, 6.8, 2.9])\n\nrows = []\nfor yi, year in enumerate(years):\n    for mi, (base_temp, month_name) in enumerate(zip(base_temps, month_names, strict=False)):\n        temp = base_temp + 0.035 * yi + np.random.normal(0, 0.7)\n        rows.append({\"year\": year, \"month_idx\": mi, \"month_name\": month_name, \"temp\": temp})\n\ndf = pd.DataFrame(rows)\n\n# Layout: 12 month groups along the shared x-axis; years spread within each group\ngroup_width = 1.0\ngap = 0.28\ngroup_starts = np.arange(12) * (group_width + gap)\ngroup_centers = group_starts + group_width / 2\nyear_offsets = np.linspace(0, group_width, n_years)\n\n# Compute absolute x-positions per row for seaborn plotting\ndf[\"x_pos\"] = df.apply(lambda row: group_starts[int(row[\"month_idx\"])] + year_offsets[int(row[\"year\"]) - 2000], axis=1)\n\n# Plot\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# Chronological subseries lines via seaborn lineplot — one line per month (units='month_idx')\n# estimator=None draws raw observations; each month's 24 years form a connected line\nsns.lineplot(\n    data=df,\n    x=\"x_pos\",\n    y=\"temp\",\n    units=\"month_idx\",\n    estimator=None,\n    color=BRAND,\n    linewidth=1.5,\n    alpha=0.75,\n    ax=ax,\n    zorder=2,\n)\n\n# Horizontal reference lines at each group's mean (key visual for seasonal comparison)\nfor mi in range(12):\n    month_data = df[df[\"month_idx\"] == mi]\n    mean_val = month_data[\"temp\"].mean()\n    ax.hlines(\n        mean_val, group_starts[mi], group_starts[mi] + group_width, colors=INK, linewidth=2.0, alpha=0.85, zorder=3\n    )\n\n# Vertical dividers between seasonal groups (alpha=0.4 for clear but subtle separation)\nfor mi in range(1, 12):\n    x_div = group_starts[mi] - gap / 2\n    ax.axvline(x_div, color=INK_MUTED, linewidth=0.6, alpha=0.4, zorder=1)\n\n# Warming trend annotation on July — the within-season upward slope tells the climate story\njuly_df = df[df[\"month_idx\"] == 6].sort_values(\"year\")\njuly_warming = july_df.iloc[-1][\"temp\"] - july_df.iloc[0][\"temp\"]\njuly_y_max = july_df[\"temp\"].max()\nax.text(\n    group_centers[6],\n    july_y_max + 0.8,\n    f\"+{july_warming:.1f}°C (2000→2023)\",\n    ha=\"center\",\n    va=\"bottom\",\n    fontsize=6.5,\n    color=INK_SOFT,\n    style=\"italic\",\n)\n\n# Style\nax.set_xticks(group_centers)\nax.set_xticklabels(month_names, fontsize=8, color=INK_SOFT)\nax.tick_params(axis=\"x\", length=0)\nax.tick_params(axis=\"y\", labelsize=8, colors=INK_SOFT)\nax.set_xlim(group_starts[0] - 0.18, group_starts[-1] + group_width + 0.18)\n\nax.set_xlabel(\"Month\", fontsize=10, color=INK)\nax.set_ylabel(\"Avg Temperature (°C)\", fontsize=10, color=INK)\n\ntitle = \"line-cycle-seasonal · python · seaborn · anyplot.ai\"\nax.set_title(title, fontsize=12, fontweight=\"medium\", color=INK, pad=10)\n\nsns.despine(ax=ax, top=True, right=True)\nax.spines[\"left\"].set_color(INK_SOFT)\nax.spines[\"bottom\"].set_color(INK_SOFT)\n\nax.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK, zorder=0)\nax.set_axisbelow(True)\n\n# Legend for the two visual elements\ntrend_handle = mlines.Line2D([], [], color=BRAND, linewidth=1.5, label=\"Yearly values\")\nmean_handle = mlines.Line2D([], [], color=INK, linewidth=2.0, label=\"Monthly mean\")\nax.legend(\n    handles=[trend_handle, mean_handle],\n    fontsize=8,\n    loc=\"upper left\",\n    frameon=True,\n    facecolor=ELEVATED_BG,\n    edgecolor=INK_SOFT,\n    labelcolor=INK_SOFT,\n)\n\nfig.subplots_adjust(left=0.08, right=0.97, top=0.90, bottom=0.12)\n\n# Save — no bbox_inches='tight' (would trim canvas away from exact 3200×1800 target)\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\nplt.close()\n"}