{"spec_id":"line-cycle-seasonal","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// line-cycle-seasonal: Cycle Plot (Seasonal Subseries)\n// Library: highcharts 12.6.0 | JavaScript 22.22.3\n// Quality: 90/100 | Created: 2026-06-15\n\n//# anyplot-orientation: landscape\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (deterministic) ---\nconst MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];\nconst N_YEARS = 20;\nconst START_YEAR = 2004;\n\n// Base monthly mean temperatures (°C) for a temperate Northern European city\nconst BASE_TEMPS = [-1.5, -0.5, 3.5, 8.5, 14.0, 18.5, 20.5, 19.5, 14.5, 8.5, 3.0, 0.0];\nconst WARMING_RATE = 0.065; // °C per year warming trend\n\n// Deterministic pseudo-noise via sin-based hash (±1.4 °C amplitude)\nfunction fakeNoise(seed) {\n    const x = Math.sin(seed * 127.1 + 311.7) * 43758.5453;\n    return (x - Math.floor(x) - 0.5) * 2.8;\n}\n\n// temps[m][y] = temperature for month m, year y offset from START_YEAR\nconst temps = BASE_TEMPS.map((base, m) =>\n    Array.from({ length: N_YEARS }, (_, y) =>\n        parseFloat((base + WARMING_RATE * y + fakeNoise(m * 23 + y)).toFixed(1))\n    )\n);\n\n// Monthly means across all years\nconst means = temps.map(ys =>\n    parseFloat((ys.reduce((a, b) => a + b, 0) / ys.length).toFixed(2))\n);\n\n// --- Layout constants ---\nconst GAP = 1;                  // gap units between month groups on x-axis\nconst GROUP_W = N_YEARS + GAP;  // 21 x-units per month group\n\n// --- Colors ---\nconst TREND_COLOR = t.palette[0]; // #009E73 — brand green, first categorical series\nconst MEAN_COLOR  = t.palette[2]; // #4467A3 — blue for mean reference lines\n\n// Theme-adaptive band color for alternating group separators\nconst BAND_COLOR = window.ANYPLOT_THEME === 'dark'\n    ? 'rgba(255,255,255,0.05)'\n    : 'rgba(0,0,0,0.04)';\n\n// --- Series ---\n// 12 subseries: one connected trend line per month, plotted chronologically\nconst trendSeries = MONTHS.map((name, m) => ({\n    type: 'line',\n    name: 'Year-to-year trend',\n    color: TREND_COLOR,\n    lineWidth: 1.5,\n    opacity: 0.85,\n    marker: { radius: 4, symbol: 'circle', enabled: true },\n    data: temps[m].map((val, y) => ({ x: m * GROUP_W + y, y: val })),\n    showInLegend: m === 0,\n    zIndex: 1,\n}));\n\n// 12 horizontal mean reference lines: one per month, spanning the group's x-range\nconst meanSeries = MONTHS.map((name, m) => ({\n    type: 'line',\n    name: 'Monthly mean',\n    color: MEAN_COLOR,\n    lineWidth: 3,\n    marker: { enabled: false },\n    data: [\n        { x: m * GROUP_W,               y: means[m] },\n        { x: m * GROUP_W + N_YEARS - 1, y: means[m] },\n    ],\n    showInLegend: m === 0,\n    enableMouseTracking: false,\n    zIndex: 2,\n}));\n\n// x-axis tick positions at the midpoint of each month group\nconst tickPositions = MONTHS.map((_, m) => m * GROUP_W + (N_YEARS - 1) / 2);\n\n// Alternating background bands to visually separate month groups\nconst plotBands = MONTHS.map((_, m) => ({\n    from:  m * GROUP_W - 0.5,\n    to:    m * GROUP_W + N_YEARS - 0.5,\n    color: m % 2 === 0 ? BAND_COLOR : 'transparent',\n}));\n\n// Total warming to annotate\nconst totalWarming = (WARMING_RATE * (N_YEARS - 1)).toFixed(1);\n\n// --- Chart ---\nconst chart = Highcharts.chart('container', {\n    chart: {\n        type: 'line',\n        backgroundColor: 'transparent',\n        animation: false,\n        style: { fontFamily: 'inherit' },\n    },\n    credits: { enabled: false },\n    colors: t.palette,\n\n    title: {\n        text: 'line-cycle-seasonal · javascript · highcharts · anyplot.ai',\n        style: { color: t.ink, fontSize: '22px', fontWeight: '600' },\n    },\n    subtitle: {\n        text: 'Average monthly temperature by season, ' + START_YEAR + '–' + (START_YEAR + N_YEARS - 1) + ' — Northern European city (°C)',\n        style: { color: t.inkSoft, fontSize: '14px' },\n    },\n\n    xAxis: {\n        tickPositions,\n        labels: {\n            formatter: function () {\n                const m = Math.round(this.value / GROUP_W);\n                return MONTHS[Math.min(m, 11)];\n            },\n            style: { color: t.inkSoft, fontSize: '14px' },\n        },\n        lineColor: t.inkSoft,\n        tickColor: 'transparent',\n        gridLineColor: 'transparent',\n        plotBands,\n    },\n\n    yAxis: {\n        title: {\n            text: 'Temperature (°C)',\n            style: { color: t.inkSoft, fontSize: '16px' },\n        },\n        gridLineColor: t.grid,\n        labels: { style: { color: t.inkSoft, fontSize: '14px' } },\n        lineColor: t.inkSoft,\n        tickColor: t.inkSoft,\n    },\n\n    legend: {\n        enabled: true,\n        itemStyle: { color: t.inkSoft, fontSize: '14px' },\n        itemHoverStyle: { color: t.ink },\n    },\n\n    tooltip: { enabled: false },\n\n    plotOptions: {\n        series: { animation: false },\n        line: { connectNulls: false },\n    },\n\n    series: [...trendSeries, ...meanSeries],\n});\n\n// Annotation: callout on July group (hottest month) highlighting the warming trend\nconst JUL = 6; // July index (0-based)\nconst annDataX = JUL * GROUP_W + (N_YEARS - 1) / 2; // midpoint of July group\nconst annPx  = chart.xAxis[0].toPixels(annDataX, false);\nconst annPy  = chart.plotTop + 18; // near the top of the plot area\n\nchart.renderer.text(\n    `↑ +${totalWarming} °C warming (Jul, 20 yrs)`,\n    annPx,\n    annPy\n).css({\n    color: TREND_COLOR,\n    fontSize: '11px',\n    fontWeight: '700',\n}).attr({\n    align: 'center',\n    zIndex: 6,\n}).add();\n"}