{"spec_id":"range-interval","library":"echarts","language":"javascript","code":"// anyplot.ai\n// range-interval: Range Interval Chart\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 89/100 | Created: 2026-09-02\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic): Berlin monthly temperature range (°C) -\nconst months = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\",\n                \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"];\nconst lowTemps = [-3, -2, 1, 4, 9, 12, 14, 13, 10, 6, 2, -1];\nconst highTemps = [3, 5, 10, 15, 20, 23, 25, 24, 20, 14, 7, 4];\n\n// Widest low-to-high span gets a callout label — a focal point beyond plain\n// chronological ordering.\nconst rangeWidths = months.map((_, i) => highTemps[i] - lowTemps[i]);\nconst widestIndex = rangeWidths.indexOf(Math.max(...rangeWidths));\n\n// --- Init -------------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Range bar geometry (custom renderItem — draws [low, high] directly) ----\n// A stacked-bar \"invisible base\" trick breaks once low is negative, because\n// ECharts accumulates positive and negative stack values separately instead\n// of letting a negative base shift a positive bar downward. Drawing the rect\n// explicitly from the low to the high coordinate avoids that edge case.\nfunction renderRangeBar(params, api) {\n  const categoryIndex = api.value(0);\n  const low = api.coord([categoryIndex, api.value(1)]);\n  const high = api.coord([categoryIndex, api.value(2)]);\n  const halfWidth = api.size([1, 0])[0] * 0.225;\n  const shape = echarts.graphic.clipRectByRect(\n    { x: low[0] - halfWidth, y: high[1], width: halfWidth * 2, height: low[1] - high[1] },\n    { x: params.coordSys.x, y: params.coordSys.y, width: params.coordSys.width, height: params.coordSys.height }\n  );\n  if (!shape) return;\n  const rect = { type: \"rect\", shape, style: api.style() };\n  if (params.dataIndex !== widestIndex) return rect;\n  const label = {\n    type: \"text\",\n    style: {\n      text: `Widest range (${rangeWidths[widestIndex]}°C)`,\n      x: low[0],\n      y: high[1] - 14,\n      fill: t.inkSoft,\n      fontSize: 12,\n      align: \"center\",\n      verticalAlign: \"bottom\",\n    },\n    z2: 10,\n  };\n  return { type: \"group\", children: [rect, label] };\n}\n\n// --- Option -------------------------------------------------------------------\nchart.setOption({\n  animation: false,\n  color: t.palette,\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"Berlin Monthly Temperature Range · range-interval · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    textStyle: { color: t.ink, fontSize: 17, fontWeight: 500 },\n  },\n  grid: { left: 90, right: 60, top: 100, bottom: 70 },\n  xAxis: {\n    type: \"category\",\n    data: months,\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    axisTick: { show: false },\n    splitLine: { show: false },\n  },\n  yAxis: {\n    type: \"value\",\n    name: \"Temperature (°C)\",\n    nameLocation: \"middle\",\n    nameGap: 55,\n    nameTextStyle: { color: t.ink, fontSize: 14 },\n    axisLabel: { color: t.inkSoft, fontSize: 14, formatter: \"{value}°\" },\n    axisLine: { show: false },\n    axisTick: { show: false },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  series: [\n    {\n      name: \"Temperature range\",\n      type: \"custom\",\n      renderItem: renderRangeBar,\n      data: months.map((_, i) => [i, lowTemps[i], highTemps[i]]),\n      itemStyle: { color: t.palette[0], borderRadius: 3 },\n      z: 2,\n    },\n    {\n      name: \"Low / high\",\n      type: \"scatter\",\n      data: months.flatMap((_, i) => [\n        [i, lowTemps[i]],\n        [i, highTemps[i]],\n      ]),\n      symbolSize: 9,\n      itemStyle: { color: t.ink, borderColor: t.pageBg, borderWidth: 1.5 },\n      z: 3,\n    },\n  ],\n});\n"}