{"spec_id":"climograph-walter-lieth","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// climograph-walter-lieth: Walter-Lieth Climate Diagram\n// Library: highcharts 12.6.0 | JavaScript 22.22.3\n// Quality: 85/100 | Created: 2026-06-15\n\n//# anyplot-orientation: landscape\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data: Seville climate normals (1991–2020) --------------------------------\n// Classic Mediterranean station with pronounced summer arid period\nconst months = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\",\n                \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"];\n\nconst temperature   = [10.3, 12.0, 14.5, 16.5, 20.3, 25.0, 28.2, 28.4, 25.3, 20.2, 14.7, 11.2];\nconst precipitation = [66,   61,   49,   53,   34,   11,    1,    5,   21,   70,   68,   72];\n\nconst stationName       = \"Seville (Aeropuerto)\";\nconst elevation         = 34;\nconst annualMeanTemp    = 18.9;\nconst annualPrecipTotal = 511;\n\n// Walter-Lieth scaling: 10 °C ≡ 20 mm → right-axis max = 2 × left-axis max\n// Humid:  P > T×2  →  lighter-blue top stacked segment (humid excess above T×2 line)\n// Arid:   T×2 > P  →  red SVG fill between temperature line and precipitation bar top\n\n// Semantic colors follow universal meteorological convention for Walter-Lieth diagrams\nconst PRECIP_COLOR = t.palette[2];  // \"#4467A3\" — Imprint blue (precipitation)\nconst TEMP_COLOR   = t.palette[4];  // \"#AE3030\" — Imprint matte-red (temperature)\n\nconst titleText = \"Seville Climate · climograph-walter-lieth · javascript · highcharts · anyplot.ai\";\nconst titleFs   = Math.max(14, Math.round(22 * 67 / titleText.length));\n\n// Split precipitation into stacked columns that expose the humid/arid boundary.\n// Bottom segment: the portion up to the T×2 threshold (present in all months).\n// Top segment: the \"humid excess\" above T×2 (non-zero only in humid months).\nconst precipBase  = temperature.map((temp, i) => Math.min(precipitation[i], temp * 2));\nconst precipHumid = precipitation.map((p, i)  => Math.max(0, p - temperature[i] * 2));\n\n// Draw arid SVG fills (red, between temperature line and small bars in summer)\n// and frost-period indicators at the 0 °C baseline.\n// Arid fills render in the empty space above the summer bars — no z-index issues.\nfunction drawOverlays(chart) {\n  if (chart.wlOverlayGroup) chart.wlOverlayGroup.destroy();\n  chart.wlOverlayGroup = chart.renderer.g().attr({ zIndex: 2 }).add();\n\n  const xAxis      = chart.xAxis[0];\n  const tempAxis   = chart.yAxis[0];   // left: 0–40 °C\n  const precipAxis = chart.yAxis[1];   // right: 0–80 mm\n\n  // Pixel step between adjacent category centers (equals the category cell width)\n  const step = xAxis.toPixels(1, false) - xAxis.toPixels(0, false);\n\n  // Arid fills: red rectangle from precipitation bar top up to temperature line.\n  // Both axes share proportional range (0–40 °C ↔ 0–80 mm), so pixel Y is comparable.\n  for (let i = 0; i < 12; i++) {\n    if (precipitation[i] >= temperature[i] * 2) continue;  // skip humid months\n    const cx  = xAxis.toPixels(i, false);\n    const tY  = tempAxis.toPixels(temperature[i], false);   // temperature line (higher on screen)\n    const pY  = precipAxis.toPixels(precipitation[i], false); // bar top (lower on screen)\n    chart.renderer\n      .path(['M', cx - step / 2, tY, 'L', cx + step / 2, tY,\n             'L', cx + step / 2, pY, 'L', cx - step / 2, pY, 'Z'])\n      .attr({ fill: TEMP_COLOR, 'fill-opacity': 0.28 })\n      .add(chart.wlOverlayGroup);\n  }\n\n  // Frost-period indicators: solid colored band at the 0 °C baseline for months\n  // with mean temperature below 0 °C. Seville has none; structure required by spec.\n  const baseY = tempAxis.toPixels(0, false);\n  for (let i = 0; i < 12; i++) {\n    if (temperature[i] >= 0) continue;\n    const cx = xAxis.toPixels(i, false);\n    chart.renderer\n      .rect(cx - step / 2, baseY, step, 12)\n      .attr({ fill: PRECIP_COLOR, 'fill-opacity': 0.65 })\n      .add(chart.wlOverlayGroup);\n  }\n}\n\n// --- Chart -------------------------------------------------------------------\nHighcharts.chart(\"container\", {\n  chart: {\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n    marginTop: 100,\n    marginBottom: 65,\n    marginLeft: 80,\n    marginRight: 90,\n    plotBorderWidth: 0,\n    events: {\n      render: function () { drawOverlays(this); },\n    },\n  },\n  credits: { enabled: false },\n\n  title: {\n    text: titleText,\n    align: \"left\",\n    x: 80,\n    style: { color: t.ink, fontSize: `${titleFs}px`, fontWeight: \"600\" },\n  },\n\n  subtitle: {\n    text: `${stationName}  ·  ${elevation} m a.s.l.  ·  T̄: ${annualMeanTemp}°C  ·  P: ${annualPrecipTotal} mm/yr`,\n    align: \"left\",\n    x: 80,\n    y: 44,\n    style: { color: t.inkSoft, fontSize: \"14px\" },\n  },\n\n  xAxis: {\n    categories: months,\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n    crosshair: false,\n  },\n\n  yAxis: [\n    // Left: Temperature (°C)\n    {\n      id: \"temp\",\n      title: {\n        text: \"Temperature (°C)\",\n        style: { color: TEMP_COLOR, fontSize: \"16px\" },\n      },\n      labels: {\n        format: \"{value} °C\",\n        style: { color: TEMP_COLOR, fontSize: \"14px\" },\n      },\n      tickPositions: [0, 10, 20, 30, 40],\n      min: 0,\n      max: 40,\n      lineColor: TEMP_COLOR,\n      lineWidth: 1,\n      gridLineColor: t.grid,\n      gridLineWidth: 1,\n    },\n    // Right: Precipitation (mm) — 2:1 scale relative to temperature axis\n    {\n      id: \"precip\",\n      title: {\n        text: \"Precipitation (mm)\",\n        style: { color: PRECIP_COLOR, fontSize: \"16px\" },\n      },\n      labels: {\n        format: \"{value} mm\",\n        style: { color: PRECIP_COLOR, fontSize: \"14px\" },\n      },\n      tickPositions: [0, 20, 40, 60, 80],\n      min: 0,\n      max: 80,\n      opposite: true,\n      lineColor: PRECIP_COLOR,\n      lineWidth: 1,\n      gridLineWidth: 0,\n    },\n  ],\n\n  legend: {\n    enabled: true,\n    align: \"right\",\n    verticalAlign: \"top\",\n    layout: \"vertical\",\n    x: -10,\n    y: 110,\n    itemStyle: { color: t.inkSoft, fontSize: \"14px\" },\n    itemHoverStyle: { color: t.ink },\n    symbolWidth: 28,\n    backgroundColor: t.elevatedBg,\n    borderColor: t.grid,\n    borderWidth: 1,\n    padding: 10,\n  },\n\n  plotOptions: {\n    series: { animation: false },\n    column: {\n      stacking: \"normal\",\n      borderWidth: 0,\n      groupPadding: 0.05,\n      pointPadding: 0.02,\n    },\n    spline: {\n      lineWidth: 3,\n      marker: {\n        enabled: true,\n        radius: 4,\n        symbol: \"circle\",\n        lineWidth: 1.5,\n        lineColor: \"transparent\",\n      },\n    },\n  },\n\n  series: [\n    // Bottom segment: precipitation up to the T×2 threshold (always present)\n    {\n      type: \"column\",\n      name: \"Precipitation\",\n      data: precipBase,\n      color: PRECIP_COLOR,\n      opacity: 0.85,\n      yAxis: 1,\n      zIndex: 1,\n    },\n    // Top segment: humid excess above the T×2 line (non-zero only in humid months)\n    // Shown at lower opacity so the two segments are visually distinct\n    {\n      type: \"column\",\n      name: \"Humid excess\",\n      data: precipHumid,\n      color: PRECIP_COLOR,\n      opacity: 0.42,\n      yAxis: 1,\n      zIndex: 1,\n      showInLegend: false,\n      linkedTo: \":previous\",\n    },\n    {\n      type: \"spline\",\n      name: \"Temperature\",\n      data: temperature,\n      color: TEMP_COLOR,\n      yAxis: 0,\n      zIndex: 3,\n      marker: { fillColor: TEMP_COLOR, lineColor: \"transparent\" },\n    },\n  ],\n});\n"}