{"spec_id":"horizon-basic","library":"echarts","language":"javascript","code":"// anyplot.ai\n// horizon-basic: Horizon Chart\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 91/100 | Created: 2026-08-18\n\n//# anyplot-orientation: landscape\nconst t = window.ANYPLOT_TOKENS;\nconst size = window.ANYPLOT_SIZE;\n\n// --- Data (in-memory, deterministic LCG) ------------------------------------\nconst sectorNames = [\n  \"Technology\", \"Energy\", \"Healthcare\", \"Financials\", \"Industrials\",\n  \"Materials\", \"Utilities\", \"Consumer\", \"Real Estate\", \"Telecom\",\n];\nconst N = sectorNames.length;\nconst T = 60;\n\nlet seed = 42;\nconst rand = () => {\n  seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n};\n\nconst rawValues = sectorNames.map(() => {\n  let value = (rand() - 0.5) * 4;\n  const drift = (rand() - 0.5) * 0.08;\n  const volatility = 0.6 + rand() * 0.9;\n  const reversion = 0.05 + rand() * 0.03; // pulls sustained runs back toward baseline\n  const values = [];\n  for (let i = 0; i < T; i++) {\n    value += drift + (rand() - 0.5) * volatility - reversion * value;\n    value = Math.max(-18, Math.min(18, value));\n    values.push(Math.round(value * 10) / 10);\n  }\n  return values;\n});\n\n// Sort rows by mean deviation (descending) so the strongest/weakest sectors\n// cluster at top/bottom and the pattern is visible at a glance.\nconst order = sectorNames\n  .map((_, i) => i)\n  .sort((a, b) => {\n    const meanA = rawValues[a].reduce((s, v) => s + v, 0) / T;\n    const meanB = rawValues[b].reduce((s, v) => s + v, 0) / T;\n    return meanB - meanA;\n  });\nconst rowNames = order.map((i) => sectorNames[i]);\nconst seriesValues = order.map((i) => rawValues[i]);\n\nconst startDate = new Date(2024, 0, 2);\nconst dayLabels = Array.from({ length: T }, (_, i) => {\n  const d = new Date(startDate);\n  d.setDate(d.getDate() + i);\n  return `${d.getMonth() + 1}/${d.getDate()}`;\n});\n\n// --- Horizon folding: K overlapping bands per row, opacity encodes magnitude\nconst K = 3;\nconst BAND_H = 5; // percentage points per band; 3 bands saturate at +/-15\nconst BAND_OPACITY = [0.35, 0.65, 1.0];\nconst POS_COLOR = t.div[2]; // blue — above sector average\nconst NEG_COLOR = t.div[0]; // red — below sector average\n\nfunction bandValue(v, k) {\n  return Math.min(BAND_H, Math.max(0, v - k * BAND_H));\n}\n\n// --- Layout (derived from the mount size so it holds for any canvas) -------\nconst W = size.width;\nconst H = size.height;\nconst titleH = Math.round(H * 0.08);\nconst legendH = Math.round(H * 0.05);\nconst bottomAxisH = Math.round(H * 0.06);\nconst leftMargin = Math.round(W * 0.115);\nconst rightMargin = Math.round(W * 0.03);\nconst rowGap = Math.round(H * 0.009);\nconst rowsTop = titleH + legendH;\nconst rowsAvail = H - bottomAxisH - rowsTop;\nconst rowH = Math.floor((rowsAvail - (N - 1) * rowGap) / N);\nconst rowTop = (i) => rowsTop + i * (rowH + rowGap);\n\n// --- Init ---------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Grids, axes, series (one row per sector) -------------------------------\nconst grid = [];\nconst xAxis = [];\nconst yAxis = [];\nconst series = [];\nconst graphic = [];\n\nfor (let i = 0; i < N; i++) {\n  grid.push({\n    left: leftMargin,\n    right: rightMargin,\n    top: rowTop(i),\n    height: rowH,\n  });\n\n  const isLastRow = i === N - 1;\n  xAxis.push({\n    type: \"category\",\n    data: dayLabels,\n    gridIndex: i,\n    boundaryGap: false,\n    axisLabel: {\n      show: isLastRow,\n      color: t.inkSoft,\n      fontSize: 14,\n      interval: Math.round(T / 6),\n    },\n    axisTick: { show: false },\n    axisLine: { show: true, lineStyle: { color: t.grid } },\n    splitLine: { show: false },\n  });\n\n  yAxis.push({\n    type: \"value\",\n    min: 0,\n    max: BAND_H,\n    gridIndex: i,\n    axisLabel: { show: false },\n    axisTick: { show: false },\n    axisLine: { show: false },\n    splitLine: { show: false },\n  });\n\n  for (let k = 0; k < K; k++) {\n    series.push({\n      type: \"line\",\n      xAxisIndex: i,\n      yAxisIndex: i,\n      data: seriesValues[i].map((v) => bandValue(v, k)),\n      showSymbol: false,\n      silent: true,\n      clip: true,\n      lineStyle: { width: 0 },\n      areaStyle: { color: POS_COLOR, opacity: BAND_OPACITY[k] },\n      z: k + 1,\n    });\n    series.push({\n      type: \"line\",\n      xAxisIndex: i,\n      yAxisIndex: i,\n      data: seriesValues[i].map((v) => bandValue(-v, k)),\n      showSymbol: false,\n      silent: true,\n      clip: true,\n      lineStyle: { width: 0 },\n      areaStyle: { color: NEG_COLOR, opacity: BAND_OPACITY[k] },\n      z: k + 1,\n    });\n  }\n\n  graphic.push({\n    type: \"text\",\n    left: 16,\n    top: rowTop(i) + rowH / 2 - 8,\n    style: {\n      text: rowNames[i],\n      fill: t.inkSoft,\n      font: \"500 15px sans-serif\",\n    },\n  });\n}\n\n// --- Diverging legend bar (top right) ---------------------------------------\nconst legendW = Math.round(W * 0.16);\nconst legendX = W - rightMargin - legendW;\nconst legendY = titleH + Math.round(legendH * 0.35);\ngraphic.push(\n  {\n    type: \"text\",\n    left: legendX,\n    top: titleH + 2,\n    style: {\n      text: \"Deviation from sector avg (%)\",\n      fill: t.inkSoft,\n      font: \"14px sans-serif\",\n    },\n  },\n  {\n    type: \"rect\",\n    left: legendX,\n    top: legendY,\n    shape: { width: legendW, height: 12 },\n    style: {\n      fill: {\n        type: \"linear\",\n        x: 0, y: 0, x2: 1, y2: 0,\n        colorStops: [\n          { offset: 0, color: NEG_COLOR },\n          { offset: 0.5, color: t.pageBg },\n          { offset: 1, color: POS_COLOR },\n        ],\n      },\n    },\n  },\n  {\n    type: \"text\",\n    left: legendX,\n    top: legendY + 17,\n    style: { text: `-${K * BAND_H}%`, fill: t.inkSoft, font: \"13px sans-serif\" },\n  },\n  {\n    type: \"text\",\n    left: legendX + legendW / 2 - 7,\n    top: legendY + 17,\n    style: { text: \"0\", fill: t.inkSoft, font: \"13px sans-serif\" },\n  },\n  {\n    type: \"text\",\n    left: legendX + legendW - 30,\n    top: legendY + 17,\n    style: { text: `+${K * BAND_H}%`, fill: t.inkSoft, font: \"13px sans-serif\" },\n  },\n);\n\n// --- Option -------------------------------------------------------------\nchart.setOption({\n  animation: false,\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"Sector Return Deviation · horizon-basic · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    top: Math.round(titleH * 0.28),\n    textStyle: { color: t.ink, fontSize: 26, fontWeight: 500 },\n  },\n  grid,\n  xAxis,\n  yAxis,\n  series,\n  graphic,\n});\n"}