{"spec_id":"line-cycle-seasonal","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nline-cycle-seasonal: Cycle Plot (Seasonal Subseries)\nLibrary: plotly 6.8.0 | Python 3.13.13\nQuality: 88/100 | Created: 2026-06-15\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nimport plotly.graph_objects as go\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\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\n\n# Imprint palette — first series always #009E73\nBRAND = \"#009E73\"  # position 1 — annual subseries lines\nBLUE = \"#4467A3\"  # position 3 — monthly mean reference lines\n\n# Data: monthly average temperature (°C) at a northern-hemisphere city, 2000–2019\nnp.random.seed(42)\nMONTHS = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"]\nN_YEARS = 20\nBASE_TEMPS = [-3.0, -1.0, 4.5, 11.0, 16.5, 21.0, 23.5, 22.0, 16.5, 10.0, 4.0, -1.5]\nWARMING_RATE = 0.05  # °C per year — slight long-term warming trend\n\nrows = []\nfor m, month in enumerate(MONTHS):\n    for y_idx in range(N_YEARS):\n        temp = BASE_TEMPS[m] + WARMING_RATE * y_idx + np.random.normal(0, 1.2)\n        rows.append({\"month\": month, \"m\": m, \"y_idx\": y_idx, \"temp\": temp})\ndf = pd.DataFrame(rows)\n\n# X-axis layout: each of 12 month-groups spans N_YEARS positions, with GAP between them\nGAP = 4\nGROUP_W = N_YEARS\n\n# Plot\nfig = go.Figure()\n\nfor m, _month in enumerate(MONTHS):\n    mdf = df[df[\"m\"] == m].sort_values(\"y_idx\")\n    x_vals = [m * (GROUP_W + GAP) + y for y in range(N_YEARS)]\n    y_vals = mdf[\"temp\"].values\n    mean_val = float(y_vals.mean())\n\n    # Within-season chronological subseries line\n    years = np.array([2000 + y for y in range(N_YEARS)])\n    fig.add_trace(\n        go.Scatter(\n            x=x_vals,\n            y=y_vals,\n            mode=\"lines+markers\",\n            line={\"color\": BRAND, \"width\": 1.5},\n            marker={\"color\": BRAND, \"size\": 5},\n            opacity=0.7,\n            customdata=years,\n            hovertemplate=\"Year: %{customdata}<br>Temp: %{y:.1f}°C<extra></extra>\",\n            showlegend=(m == 0),\n            name=\"Annual values\",\n        )\n    )\n\n    # Horizontal mean reference line — the key visual for comparing seasonal levels\n    fig.add_trace(\n        go.Scatter(\n            x=[x_vals[0], x_vals[-1]],\n            y=[mean_val, mean_val],\n            mode=\"lines\",\n            line={\"color\": BLUE, \"width\": 4.5},\n            hovertemplate=f\"{_month} mean: %{{y:.1f}}°C<extra></extra>\",\n            showlegend=(m == 0),\n            name=\"Monthly mean\",\n        )\n    )\n\n# Tick marks at center of each month group\ntick_vals = [m * (GROUP_W + GAP) + GROUP_W // 2 for m in range(12)]\n\n# Subtle vertical dividers between month groups\nshapes = [\n    {\n        \"type\": \"line\",\n        \"x0\": m * (GROUP_W + GAP) - GAP / 2,\n        \"x1\": m * (GROUP_W + GAP) - GAP / 2,\n        \"y0\": 0,\n        \"y1\": 1,\n        \"yref\": \"paper\",\n        \"line\": {\"color\": INK_SOFT, \"width\": 1, \"dash\": \"dot\"},\n    }\n    for m in range(1, 12)\n]\n\ntitle = \"line-cycle-seasonal · python · plotly · anyplot.ai\"\n\nfig.update_layout(\n    autosize=False,\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    margin={\"l\": 80, \"r\": 40, \"t\": 80, \"b\": 60},\n    title={\"text\": title, \"font\": {\"size\": 16, \"color\": INK}, \"x\": 0.01, \"xanchor\": \"left\"},\n    xaxis={\n        \"tickvals\": tick_vals,\n        \"ticktext\": MONTHS,\n        \"title\": {\"text\": \"Month\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"showgrid\": False,\n        \"linecolor\": INK_SOFT,\n        \"zeroline\": False,\n    },\n    yaxis={\n        \"title\": {\"text\": \"Temperature (°C)\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"gridcolor\": GRID,\n        \"linecolor\": INK_SOFT,\n        \"zerolinecolor\": GRID,\n        \"showgrid\": True,\n    },\n    legend={\n        \"bgcolor\": ELEVATED_BG,\n        \"bordercolor\": INK_SOFT,\n        \"borderwidth\": 1,\n        \"font\": {\"color\": INK_SOFT, \"size\": 10},\n        \"x\": 0.01,\n        \"y\": 0.99,\n        \"xanchor\": \"left\",\n        \"yanchor\": \"top\",\n    },\n    shapes=shapes,\n)\n\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}