{"spec_id":"climograph-walter-lieth","library":"echarts","language":"javascript","code":"// anyplot.ai\n// climograph-walter-lieth: Walter-Lieth Climate Diagram\n// Library: echarts 5.5.1 | JavaScript 22.22.3\n// Quality: 91/100 | Created: 2026-06-15\n\nconst t = window.ANYPLOT_TOKENS;\nconst isDark = window.ANYPLOT_THEME === 'dark';\n\n// Barcelona, Spain — Mediterranean climate normal 1991–2020\nconst months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];\nconst temp   = [9.3, 10.2, 12.2, 14.4, 17.7, 21.4, 24.3, 24.5, 21.5, 17.3, 12.8, 9.7];\nconst precip = [46, 41, 47, 48, 52, 32, 20, 60, 79, 91, 62, 47];\n\n// Walter-Lieth 1:2 axis convention: 10°C ↔ 20 mm (normal zone 0–100 mm)\n// Above 100 mm the perhumid zone is compressed 1:10 (10°C ↔ 100 mm)\nconst PERHUMID_MM = 100;\nconst PERHUMID_Y  = PERHUMID_MM / 2; // 50 in temperature-axis units\n\n// Transform precipitation to temperature-equivalent units for plotting\nconst precipNorm = precip.map(p =>\n  p <= PERHUMID_MM ? p / 2 : PERHUMID_Y + (p - PERHUMID_MM) / 10\n);\n\nconst annualTemp   = (temp.reduce((a, b) => a + b, 0) / 12).toFixed(1);\nconst annualPrecip = precip.reduce((a, b) => a + b, 0);\n\n// Dynamic y-axis max: at least 50, extended if any perhumid months\nconst yMax = Math.max(50, ...precipNorm.map(p => Math.ceil(p / 10) * 10));\n\n// Band fill data: humid where P_norm > T (blue), arid where T > P_norm (red)\nconst humidBase = temp.map((T, i) => Math.min(T, precipNorm[i]));\nconst humidFill = temp.map((T, i) => Math.max(0, precipNorm[i] - T));\nconst aridBase  = precipNorm.map((P, i) => Math.min(temp[i], P));\nconst aridFill  = temp.map((T, i) => Math.max(0, T - precipNorm[i]));\n\n// Perhumid zone fill: solid blue for the compressed zone above 100 mm\nconst perhumidBase = precipNorm.map(p => Math.min(p, PERHUMID_Y));\nconst perhumidFill = precip.map((p, i) => p > PERHUMID_MM ? precipNorm[i] - PERHUMID_Y : 0);\n\n// Theme-adaptive fill opacities — increase in dark mode to compensate for low contrast on #1A1A17\nconst HUMID_OPACITY = isDark ? 0.45 : 0.28;\nconst ARID_OPACITY  = isDark ? 0.55 : 0.28;\n\n// Imprint palette — semantic exception: temperature = matte-red, precipitation = blue\nconst TEMP_COLOR   = '#AE3030'; // Imprint palette[4] matte-red — thermal/heat convention\nconst PRECIP_COLOR = '#4467A3'; // Imprint palette[2] blue — water/precipitation convention\n\n// Frost indicator: months where mean temp < 0°C get a blue band in the -10 to 0 zone\n// Barcelona has no frost months (min 9.3°C) — the mechanism is required by the spec\nconst frostAreas = months.reduce((acc, m, i) => {\n  if (temp[i] < 0) {\n    acc.push([\n      { xAxis: m, yAxis: -10 },\n      { xAxis: m, yAxis: 0 }\n    ]);\n  }\n  return acc;\n}, []);\n\nconst chart = echarts.init(document.getElementById('container'));\n\nchart.setOption({\n  animation: false,\n  backgroundColor: 'transparent',\n\n  title: {\n    text: 'climograph-walter-lieth · javascript · echarts · anyplot.ai',\n    left: 'center',\n    top: 16,\n    textStyle: { color: t.ink, fontSize: 22, fontWeight: 'bold' }\n  },\n\n  graphic: [\n    {\n      type: 'group',\n      left: '8%',\n      top: 66,\n      children: [\n        {\n          type: 'text',\n          style: { text: 'Barcelona', fontSize: 22, fontWeight: 'bold', fill: t.ink }\n        },\n        {\n          type: 'text',\n          top: 30,\n          style: {\n            text: `5 m a.s.l.  ·  ${annualTemp} °C  ·  ${annualPrecip} mm yr⁻¹`,\n            fontSize: 15,\n            fill: t.inkSoft\n          }\n        }\n      ]\n    }\n  ],\n\n  grid: { left: 95, right: 100, top: 135, bottom: 65 },\n\n  xAxis: {\n    type: 'category',\n    data: months,\n    boundaryGap: false,\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    axisTick: { show: false },\n    splitLine: { show: false }\n  },\n\n  yAxis: [\n    {\n      // Left axis: temperature scale (°C)\n      type: 'value',\n      name: 'Temperature (°C)',\n      nameLocation: 'middle',\n      nameGap: 60,\n      nameTextStyle: { color: TEMP_COLOR, fontSize: 14 },\n      position: 'left',\n      min: -10, max: yMax, interval: 10,\n      axisLabel: {\n        color: TEMP_COLOR,\n        fontSize: 13,\n        formatter: v => v + '°'\n      },\n      axisLine: { show: true, lineStyle: { color: TEMP_COLOR } },\n      axisTick: { lineStyle: { color: TEMP_COLOR } },\n      splitLine: { lineStyle: { color: t.grid, type: 'solid' } }\n    },\n    {\n      // Right axis: precipitation scale (mm), two-zone: normal 0–100 mm, compressed above 100 mm\n      type: 'value',\n      name: 'Precipitation (mm)',\n      nameLocation: 'middle',\n      nameGap: 68,\n      nameTextStyle: { color: PRECIP_COLOR, fontSize: 14 },\n      position: 'right',\n      min: -10, max: yMax, interval: 10,\n      axisLabel: {\n        color: PRECIP_COLOR,\n        fontSize: 13,\n        formatter: v => {\n          if (v < 0) return '';\n          if (v <= PERHUMID_Y) return (v * 2) + ' mm';\n          return (PERHUMID_MM + (v - PERHUMID_Y) * 10) + ' mm';\n        }\n      },\n      axisLine: { show: true, lineStyle: { color: PRECIP_COLOR } },\n      axisTick: { lineStyle: { color: PRECIP_COLOR } },\n      splitLine: { show: false }\n    }\n  ],\n\n  legend: {\n    data: [\n      { name: 'Temperature', icon: 'roundRect', itemStyle: { color: TEMP_COLOR } },\n      { name: 'Precipitation', icon: 'roundRect', itemStyle: { color: PRECIP_COLOR } }\n    ],\n    top: 70,\n    right: '8%',\n    textStyle: { color: t.ink, fontSize: 14 }\n  },\n\n  tooltip: {\n    trigger: 'axis',\n    backgroundColor: t.elevatedBg,\n    borderColor: t.grid,\n    textStyle: { color: t.ink, fontSize: 13 },\n    formatter: params => {\n      const month  = params[0] ? params[0].axisValue : '';\n      const tParam = params.find(p => p.seriesName === 'Temperature');\n      const pParam = params.find(p => p.seriesName === 'Precipitation');\n      let html = `<b>${month}</b><br/>`;\n      if (tParam) html += `${tParam.marker} Temp: ${tParam.value} °C<br/>`;\n      if (pParam) {\n        const mmVal = pParam.value <= PERHUMID_Y\n          ? (pParam.value * 2).toFixed(0)\n          : (PERHUMID_MM + (pParam.value - PERHUMID_Y) * 10).toFixed(0);\n        html += `${pParam.marker} Precip: ${mmVal} mm`;\n      }\n      return html;\n    }\n  },\n\n  series: [\n    // Humid fill baseline — transparent area from 0 up to min(T, P_norm)\n    {\n      name: '_humid_base',\n      type: 'line',\n      data: humidBase,\n      yAxisIndex: 0,\n      stack: 'humid',\n      symbol: 'none',\n      lineStyle: { width: 0 },\n      areaStyle: { color: 'rgba(0,0,0,0)' },\n      legendHoverLink: false,\n      silent: true\n    },\n    // Humid fill — blue where precipitation exceeds temperature curve (humid period)\n    {\n      name: '_humid_fill',\n      type: 'line',\n      data: humidFill,\n      yAxisIndex: 0,\n      stack: 'humid',\n      symbol: 'none',\n      lineStyle: { width: 0 },\n      areaStyle: { color: `rgba(68,103,163,${HUMID_OPACITY})` },\n      legendHoverLink: false,\n      silent: true\n    },\n    // Arid fill baseline — transparent area from 0 up to min(T, P_norm)\n    {\n      name: '_arid_base',\n      type: 'line',\n      data: aridBase,\n      yAxisIndex: 0,\n      stack: 'arid',\n      symbol: 'none',\n      lineStyle: { width: 0 },\n      areaStyle: { color: 'rgba(0,0,0,0)' },\n      legendHoverLink: false,\n      silent: true\n    },\n    // Arid fill — red where temperature curve exceeds precipitation (arid/drought period)\n    {\n      name: '_arid_fill',\n      type: 'line',\n      data: aridFill,\n      yAxisIndex: 0,\n      stack: 'arid',\n      symbol: 'none',\n      lineStyle: { width: 0 },\n      areaStyle: { color: `rgba(174,48,48,${ARID_OPACITY})` },\n      legendHoverLink: false,\n      silent: true\n    },\n    // Perhumid zone baseline — transparent up to 100 mm (PERHUMID_Y on temp scale)\n    {\n      name: '_perhumid_base',\n      type: 'line',\n      data: perhumidBase,\n      yAxisIndex: 0,\n      stack: 'perhumid',\n      symbol: 'none',\n      lineStyle: { width: 0 },\n      areaStyle: { color: 'rgba(0,0,0,0)' },\n      legendHoverLink: false,\n      silent: true\n    },\n    // Perhumid zone fill — solid blue above 100 mm (compressed scale zone)\n    {\n      name: '_perhumid_fill',\n      type: 'line',\n      data: perhumidFill,\n      yAxisIndex: 0,\n      stack: 'perhumid',\n      symbol: 'none',\n      lineStyle: { width: 0 },\n      areaStyle: { color: 'rgba(68,103,163,0.85)' },\n      legendHoverLink: false,\n      silent: true\n    },\n    // Temperature line (red, left axis) with frost indicator and 0°C reference line\n    {\n      name: 'Temperature',\n      type: 'line',\n      data: temp,\n      yAxisIndex: 0,\n      lineStyle: { color: TEMP_COLOR, width: 3 },\n      itemStyle: { color: TEMP_COLOR },\n      symbol: 'circle',\n      symbolSize: 7,\n      z: 10,\n      markLine: {\n        silent: true,\n        symbol: ['none', 'none'],\n        data: [{ yAxis: 0 }],\n        lineStyle: { color: t.inkSoft, type: 'dashed', width: 1, opacity: 0.4 },\n        label: { show: false }\n      },\n      // Frost indicator: solid blue band in -10 to 0 zone for months with mean temp < 0°C\n      markArea: {\n        silent: true,\n        data: frostAreas,\n        itemStyle: { color: 'rgba(68,103,163,0.6)', borderWidth: 0 },\n        label: { show: false }\n      }\n    },\n    // Precipitation line (blue, plotted as P_norm on temperature axis per Walter-Lieth convention)\n    {\n      name: 'Precipitation',\n      type: 'line',\n      data: precipNorm,\n      yAxisIndex: 0,\n      lineStyle: { color: PRECIP_COLOR, width: 3 },\n      itemStyle: { color: PRECIP_COLOR },\n      symbol: 'circle',\n      symbolSize: 7,\n      z: 10\n    }\n  ]\n});\n"}