{"spec_id":"climograph-walter-lieth","library":"muix","language":"javascript","code":"// anyplot.ai\n// climograph-walter-lieth: Walter-Lieth Climate Diagram\n// Library: muix 7.29.1 | JavaScript 22.22.3\n// Quality: 89/100 | Created: 2026-06-15\n\nimport { ChartContainer } from \"@mui/x-charts/ChartContainer\";\nimport { LinePlot } from \"@mui/x-charts/LineChart\";\nimport { ChartsXAxis } from \"@mui/x-charts/ChartsXAxis\";\nimport { ChartsYAxis } from \"@mui/x-charts/ChartsYAxis\";\nimport { ChartsGrid } from \"@mui/x-charts/ChartsGrid\";\nimport { useDrawingArea, useXScale, useYScale } from \"@mui/x-charts/hooks\";\nimport Box from \"@mui/material/Box\";\nimport Typography from \"@mui/material/Typography\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// Station: Lisbon, Portugal — Mediterranean climate (1991–2020 normals)\nconst ELEVATION = 77;\nconst months = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"];\nconst temperature = [11.3, 12.4, 14.2, 16.0, 18.5, 21.7, 24.2, 24.5, 22.3, 18.6, 14.8, 11.7];\nconst precipitation = [77, 62, 54, 42, 36, 13, 4, 5, 25, 65, 93, 91];\n\n// Walter-Lieth 2:1 scaling: precipitation/2 plotted on temperature axis\n// This makes 10°C align with 20 mm so the curves are directly comparable\nconst precipScaled = precipitation.map((p) => p / 2);\n\nconst annualMeanTemp = (temperature.reduce((a, b) => a + b, 0) / 12).toFixed(1);\nconst annualTotalPrecip = precipitation.reduce((a, b) => a + b, 0);\n\nconst TEMP_AXIS_ID = \"tempY\";\n// Imprint palette: blue (#4467A3) for humid, matte red (#AE3030) for arid\n// These follow semantic conventions: water=blue, heat/drought=red\nconst HUMID_FILL = \"#4467A3\";\nconst ARID_FILL = \"#AE3030\";\n// Dark blue for frost month indicator bands (mean temp < 0°C)\nconst FROST_COLOR = \"#2B4B8C\";\n\n// Draws blue/red fills between the temperature and precipitation curves,\n// plus frost month indicator bands below the x-axis for months with mean temp < 0°C\nfunction WalterLiethFill() {\n  const { left, top, height } = useDrawingArea();\n  const xScale = useXScale();\n  const yScale = useYScale(TEMP_AXIS_ID);\n\n  if (!xScale || !yScale || !xScale.bandwidth) return null;\n\n  const bw = xScale.bandwidth();\n  const getX = (i) => (xScale(months[i]) ?? 0) + bw / 2;\n  const getTY = (i) => yScale(temperature[i]) ?? 0;\n  const getPY = (i) => yScale(precipScaled[i]) ?? 0;\n\n  const elems = [];\n\n  // Humid/arid fills between temperature and precipitation curves\n  for (let i = 0; i < months.length - 1; i++) {\n    const x0 = getX(i);\n    const x1 = getX(i + 1);\n    const ty0 = getTY(i);\n    const ty1 = getTY(i + 1);\n    const py0 = getPY(i);\n    const py1 = getPY(i + 1);\n    const h0 = precipScaled[i] > temperature[i];\n    const h1 = precipScaled[i + 1] > temperature[i + 1];\n\n    if (h0 === h1) {\n      elems.push(\n        <polygon\n          key={`f${i}`}\n          points={`${x0},${py0} ${x1},${py1} ${x1},${ty1} ${x0},${ty0}`}\n          fill={h0 ? HUMID_FILL : ARID_FILL}\n          fillOpacity={0.38}\n        />\n      );\n    } else {\n      // Curves cross — compute intersection by linear interpolation in SVG space\n      const d = (py1 - py0) - (ty1 - ty0);\n      const frac = Math.abs(d) > 0.01 ? (ty0 - py0) / d : 0.5;\n      const xc = x0 + (x1 - x0) * frac;\n      const yc = ty0 + (ty1 - ty0) * frac;\n      elems.push(\n        <polygon\n          key={`f${i}a`}\n          points={`${x0},${py0} ${xc},${yc} ${x0},${ty0}`}\n          fill={h0 ? HUMID_FILL : ARID_FILL}\n          fillOpacity={0.38}\n        />,\n        <polygon\n          key={`f${i}b`}\n          points={`${xc},${yc} ${x1},${py1} ${x1},${ty1}`}\n          fill={h1 ? HUMID_FILL : ARID_FILL}\n          fillOpacity={0.38}\n        />\n      );\n    }\n  }\n\n  // Frost month indicator: solid band just below x-axis for months with mean temp < 0°C\n  const frostBandH = 6;\n  const frostGap = 2;\n  for (let i = 0; i < months.length; i++) {\n    if (temperature[i] < 0) {\n      const xi = getX(i);\n      elems.push(\n        <rect\n          key={`frost${i}`}\n          x={xi - bw / 2}\n          y={height + frostGap}\n          width={bw}\n          height={frostBandH}\n          fill={FROST_COLOR}\n          fillOpacity={0.85}\n        />\n      );\n    }\n  }\n\n  return <g transform={`translate(${left},${top})`}>{elems}</g>;\n}\n\nconst CHART_SERIES = [\n  {\n    id: \"temp\",\n    label: \"Temperature (°C)\",\n    data: temperature,\n    type: \"line\",\n    color: \"#AE3030\",\n    yAxisId: TEMP_AXIS_ID,\n    showMark: false,\n    curve: \"catmullRom\",\n  },\n  {\n    id: \"precip\",\n    label: \"Precipitation (mm)\",\n    data: precipScaled,\n    type: \"line\",\n    color: \"#4467A3\",\n    yAxisId: TEMP_AXIS_ID,\n    showMark: false,\n    curve: \"catmullRom\",\n  },\n];\n\nexport default function Chart() {\n  const W = window.ANYPLOT_SIZE.width;\n  const H = window.ANYPLOT_SIZE.height;\n\n  const title = \"climograph-walter-lieth · javascript · muix · anyplot.ai\";\n  const titleFontSize = Math.max(11, Math.round(22 * Math.min(1, 67 / title.length)));\n\n  const chartH = H - 158;\n  const chartW = W - 48;\n\n  const hasFrostMonths = temperature.some((t) => t < 0);\n  const legendItems = [\n    { color: \"#AE3030\", isLine: true, label: \"Temperature (°C)\" },\n    { color: \"#4467A3\", isLine: true, label: \"Precipitation (mm)\" },\n    { color: \"rgba(68,103,163,0.38)\", border: \"#4467A3\", label: \"Humid period\" },\n    { color: \"rgba(174,48,48,0.38)\", border: \"#AE3030\", label: \"Arid period\" },\n    ...(hasFrostMonths\n      ? [{ color: \"rgba(43,75,140,0.85)\", border: FROST_COLOR, label: \"Frost month\" }]\n      : []),\n  ];\n\n  return (\n    <Box\n      sx={{\n        width: W,\n        height: H,\n        bgcolor: t.pageBg,\n        display: \"flex\",\n        flexDirection: \"column\",\n        px: \"24px\",\n        pt: \"18px\",\n        pb: \"14px\",\n        boxSizing: \"border-box\",\n      }}\n    >\n      {/* Station header */}\n      <Box sx={{ textAlign: \"center\", mb: \"10px\" }}>\n        <Typography\n          sx={{ color: t.ink, fontWeight: 700, fontSize: 20, lineHeight: 1.3 }}\n        >\n          Lisbon, Portugal — {ELEVATION} m a.s.l.\n        </Typography>\n        <Typography sx={{ color: t.inkSoft, fontSize: 13, mt: \"3px\" }}>\n          Annual mean {annualMeanTemp} °C · Annual total {annualTotalPrecip} mm · 1991–2020 climate normals\n        </Typography>\n      </Box>\n\n      {/* Chart */}\n      <ChartContainer\n        width={chartW}\n        height={chartH}\n        series={CHART_SERIES}\n        xAxis={[\n          {\n            id: \"x\",\n            scaleType: \"band\",\n            data: months,\n            tickLabelStyle: { fontSize: 13 },\n          },\n        ]}\n        yAxis={[\n          {\n            id: TEMP_AXIS_ID,\n            min: 0,\n            max: 50,\n            tickInterval: 10,\n            tickLabelStyle: { fontSize: 13 },\n          },\n          {\n            id: \"precipR\",\n            position: \"right\",\n            min: 0,\n            max: 50,\n            tickInterval: 10,\n            valueFormatter: (v) => `${v * 2}`,\n            tickLabelStyle: { fontSize: 13 },\n          },\n        ]}\n        margin={{ top: 15, right: 90, bottom: 48, left: 78 }}\n        skipAnimation\n        sx={{\n          // Suppress right spine — keep left + bottom L-shape only\n          \"& .MuiChartsAxis-right .MuiChartsAxis-line\": { display: \"none\" },\n        }}\n      >\n        <ChartsGrid horizontal />\n        <WalterLiethFill />\n        <LinePlot />\n        <ChartsXAxis axisId=\"x\" position=\"bottom\" />\n        <ChartsYAxis\n          axisId={TEMP_AXIS_ID}\n          position=\"left\"\n          label=\"Temperature (°C)\"\n          labelStyle={{ fontSize: 14 }}\n        />\n        <ChartsYAxis\n          axisId=\"precipR\"\n          position=\"right\"\n          label=\"Precipitation (mm)\"\n          labelStyle={{ fontSize: 14 }}\n          disableLine\n        />\n      </ChartContainer>\n\n      {/* Legend */}\n      <Box\n        sx={{\n          display: \"flex\",\n          justifyContent: \"center\",\n          alignItems: \"center\",\n          gap: \"28px\",\n          mt: \"6px\",\n          mb: \"6px\",\n          flexWrap: \"wrap\",\n        }}\n      >\n        {legendItems.map((item) => (\n          <Box\n            key={item.label}\n            sx={{ display: \"flex\", alignItems: \"center\", gap: \"7px\" }}\n          >\n            {item.isLine ? (\n              <Box\n                sx={{\n                  width: 26,\n                  height: 3,\n                  bgcolor: item.color,\n                  borderRadius: \"2px\",\n                  flexShrink: 0,\n                }}\n              />\n            ) : (\n              <Box\n                sx={{\n                  width: 22,\n                  height: 14,\n                  bgcolor: item.color,\n                  border: `1.5px solid ${item.border}`,\n                  borderRadius: \"3px\",\n                  flexShrink: 0,\n                }}\n              />\n            )}\n            <Typography sx={{ fontSize: 13, color: t.inkSoft }}>\n              {item.label}\n            </Typography>\n          </Box>\n        ))}\n      </Box>\n\n      {/* Plot title */}\n      <Typography\n        sx={{ color: t.inkSoft, textAlign: \"center\", fontSize: titleFontSize, lineHeight: 1.3 }}\n      >\n        {title}\n      </Typography>\n    </Box>\n  );\n}\n"}