{"spec_id":"line-cycle-seasonal","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nline-cycle-seasonal: Cycle Plot (Seasonal Subseries)\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 82/100 | Created: 2026-06-15\n\"\"\"\n\nimport os\nimport sys\n\nimport numpy as np\n\n\n# Prevent the local pygal.py from shadowing the installed pygal package\n_script_dir = os.path.dirname(os.path.abspath(__file__))\nif _script_dir in sys.path:\n    sys.path.remove(_script_dir)\n\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\nTREND_COLOR = \"#009E73\"  # Imprint palette position 1 — monthly subseries\nMEAN_COLOR = \"#AE3030\"  # Imprint palette position 5 — seasonal mean reference\n\n# Data: Monthly average temperatures (°C), temperate Northern European city, 2015–2024\nnp.random.seed(42)\nyears = list(range(2015, 2025))  # 10 years\nn_years = len(years)\nmonths = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"]\nn_months = 12\n\n# Base monthly temperatures inspired by Berlin climate\nmonth_base = np.array([1.3, 2.7, 6.5, 11.5, 16.0, 19.2, 21.3, 21.0, 16.2, 10.8, 5.6, 2.2])\nwarming_rate = 0.08  # °C per year — slight but visible warming over 10 years\n\ntemps = np.zeros((n_years, n_months))\nfor y in range(n_years):\n    for m in range(n_months):\n        temps[y, m] = month_base[m] + y * warming_rate + np.random.normal(0, 1.1)\n\nmonth_means = temps.mean(axis=0)\n\n# XY approach: month m occupies x-range [m, m+1).\n# Within each month: 10 year points at x = m + y/n_years (0.0 to 0.9 within group).\n# This gives only 12 x-label positions → slot width ~267px → labels fit at large font.\n\n# Title with font size scaled to length (floor: 44)\ntitle = \"Monthly Temperature Cycle · line-cycle-seasonal · python · pygal · anyplot.ai\"\nn_ch = len(title)\ntitle_font = max(44, round(66 * 67 / n_ch)) if n_ch > 67 else 66\n\n# Palette: 12 × TREND_COLOR first, then 12 × MEAN_COLOR\npalette_colors = tuple([TREND_COLOR] * n_months + [MEAN_COLOR] * n_months)\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=palette_colors,\n    title_font_size=title_font,\n    label_font_size=56,\n    major_label_font_size=44,\n    legend_font_size=44,\n    value_font_size=36,\n    stroke_width=2.5,\n)\n\n# XY chart: x-axis is numeric, groups are [m, m+1) for month m\nchart = pygal.XY(\n    style=custom_style,\n    width=3200,\n    height=1800,\n    title=title,\n    x_title=\"Month\",\n    y_title=\"Average Temperature (°C)\",\n    show_dots=True,\n    dots_size=3,\n    show_legend=False,\n    show_x_guides=False,\n    show_y_guides=True,\n)\n\n# x_labels: place month names at equal-interval positions across x range\nchart.x_labels = months\n\n# Trend line series — one per month, brand green (Imprint position 1)\n# Within-group x: 0.0, 0.1, ..., 0.9 → 10 year points connected chronologically\nfor m in range(n_months):\n    data = [(m + y / n_years, round(float(temps[y, m]), 2)) for y in range(n_years)]\n    chart.add(months[m], data)\n\n# Seasonal mean reference lines — one per month, matte red (Imprint position 5)\n# dasharray \"12, 6\" gives strong CVD-robust distinction from solid trend lines\nfor m in range(n_months):\n    mv = round(float(month_means[m]), 2)\n    chart.add(f\"Mean {months[m]}\", [(m, mv), (m + 0.9, mv)], stroke_style={\"width\": 5, \"dasharray\": \"12, 6\"})\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"}