{"spec_id":"line-cycle-seasonal","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nline-cycle-seasonal: Cycle Plot (Seasonal Subseries)\nLibrary: plotnine 0.15.7 | Python 3.13.13\nQuality: 90/100 | Created: 2026-06-15\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent this file from shadowing the installed plotnine package\nsys.path = [p for p in sys.path if p not in (\"\", os.path.dirname(os.path.abspath(__file__)))]\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_line,\n    geom_segment,\n    geom_vline,\n    ggplot,\n    labs,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n)\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint palette — theme-independent\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\nBRAND = IMPRINT_PALETTE[0]  # always first series — subseries lines\nMEAN_COLOR = IMPRINT_PALETTE[2]  # blue — mean reference lines\n\n# Data: monthly avg temperature (°C), temperate mid-latitude city, 2000–2024\nnp.random.seed(42)\n\nYEARS = np.arange(2000, 2025)\nN_YEARS = len(YEARS)\nMONTH_NAMES = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"]\n\n# Layout constants: each month occupies GROUP_WIDTH, separated by GAP\nGROUP_WIDTH = 1.0\nGAP = 0.35\nSTEP = GROUP_WIDTH + GAP  # 1.35 units per month slot\n\nrecords = []\nfor mi, mname in enumerate(MONTH_NAMES):\n    # Northern-hemisphere seasonal baseline — cosine peak July (mi=6)\n    seasonal = 13.5 + 11.5 * np.cos(np.pi * (mi - 6) / 6)\n    for yi, year in enumerate(YEARS):\n        temp = seasonal + 0.035 * yi + np.random.normal(0, 0.9)\n        # Spread years left-to-right across GROUP_WIDTH within each month slot\n        x_pos = mi * STEP + (yi / (N_YEARS - 1)) * GROUP_WIDTH\n        records.append({\"month\": mi + 1, \"month_name\": mname, \"year\": year, \"temp\": temp, \"x_pos\": x_pos})\n\ndf = pd.DataFrame(records)\n\n# Monthly mean reference segments\nmean_rows = []\nfor mi in range(12):\n    mean_val = df[df[\"month\"] == mi + 1][\"temp\"].mean()\n    mean_rows.append({\"x_start\": mi * STEP - 0.05, \"x_end\": mi * STEP + GROUP_WIDTH + 0.05, \"mean_temp\": mean_val})\nmean_df = pd.DataFrame(mean_rows)\n\n# X-axis: tick labels at the centre of each month slot\nx_breaks = [mi * STEP + GROUP_WIDTH / 2 for mi in range(12)]\n\n# Vertical dividers sit mid-gap between consecutive month slots\nvline_x = [mi * STEP - GAP / 2 for mi in range(1, 12)]\n\n# Title with length-aware fontsize scaling\nTITLE = \"line-cycle-seasonal · python · plotnine · anyplot.ai\"\nn_chars = len(TITLE)\ntitle_size = max(8, round(12 * (67 / n_chars if n_chars > 67 else 1.0)))\n\n# Theme\nanyplot_theme = theme(\n    figure_size=(8, 4.5),\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG),\n    panel_grid_major_y=element_line(color=INK, size=0.3, alpha=0.12),\n    panel_grid_major_x=element_blank(),\n    panel_grid_minor=element_blank(),\n    panel_border=element_blank(),\n    axis_line_x=element_line(color=INK_SOFT, size=0.5),\n    axis_line_y=element_line(color=INK_SOFT, size=0.5),\n    axis_ticks=element_blank(),\n    axis_title_x=element_blank(),\n    axis_title_y=element_text(color=INK, size=10),\n    axis_text=element_text(color=INK_SOFT, size=8),\n    plot_title=element_text(color=INK, size=title_size),\n    legend_position=\"none\",\n)\n\n# Plot\nplot = (\n    ggplot(df, aes(x=\"x_pos\", y=\"temp\"))\n    # Subtle vertical dividers between month groups\n    + geom_vline(xintercept=vline_x, color=INK_SOFT, size=0.3, alpha=0.35)\n    # Within-month chronological subseries lines (one line per month, years left→right)\n    + geom_line(aes(group=\"month\"), color=BRAND, size=0.6, alpha=0.8)\n    # Horizontal mean reference lines — the primary seasonal comparison signal\n    + geom_segment(\n        data=mean_df,\n        mapping=aes(x=\"x_start\", xend=\"x_end\", y=\"mean_temp\", yend=\"mean_temp\"),\n        color=MEAN_COLOR,\n        size=1.8,\n        alpha=0.9,\n    )\n    + scale_x_continuous(breaks=x_breaks, labels=MONTH_NAMES, expand=(0.01, 0))\n    + scale_y_continuous(expand=(0.05, 0))\n    + labs(y=\"Avg Temperature (°C)\", title=TITLE)\n    + anyplot_theme\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=8, height=4.5, units=\"in\")\n"}