{"spec_id":"horizon-basic","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// horizon-basic: Horizon Chart\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 92/100 | Created: 2026-08-18\n\n//# anyplot-orientation: landscape\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data: CPU load deviation from a 50% baseline, 10 fleet hosts over 24h --\n// deterministic Park-Miller LCG — no seeded RNG in the browser\nfunction makeLcg(seed) {\n  let state = seed % 2147483647;\n  if (state <= 0) state += 2147483646;\n  return function next() {\n    state = (state * 16807) % 2147483647;\n    return (state - 1) / 2147483646;\n  };\n}\n\nconst HOSTS = [\"api-1\", \"api-2\", \"api-3\", \"api-4\", \"worker-1\", \"worker-2\",\n               \"cache-1\", \"cache-2\", \"db-primary\", \"db-replica\"];\nconst POINTS = 144; // one reading every 10 minutes across 24h\nconst STEP_MS = 10 * 60 * 1000;\nconst START_MS = Date.UTC(2024, 5, 10, 0, 0, 0);\nconst BAND_HEIGHT = 15; // percentage points of CPU deviation folded per band\nconst BAND_COUNT = 3; // horizon bands per polarity (mirrored blue/red)\n\nconst hostSeries = HOSTS.map((name, hostIndex) => {\n  const rng = makeLcg(1000 + hostIndex * 97);\n  let level = (rng() - 0.5) * 20;\n  const drift = (rng() - 0.5) * 0.4;\n  const values = [];\n  for (let p = 0; p < POINTS; p += 1) {\n    level = (level + drift + (rng() - 0.5) * 8) * 0.96;\n    values.push(level);\n  }\n  return { name, values };\n});\n\n// --- Horizon folding: clamp the excess above each band threshold into 0..H -\nfunction foldBand(value, bandIndex, bandHeight) {\n  return Math.min(Math.max(value - bandIndex * bandHeight, 0), bandHeight);\n}\n\n// --- Band colors: interpolate the Imprint diverging stops (imprint_div) ----\nfunction lerpHex(hexA, hexB, fraction) {\n  const a = [1, 3, 5].map((i) => parseInt(hexA.slice(i, i + 2), 16));\n  const b = [1, 3, 5].map((i) => parseInt(hexB.slice(i, i + 2), 16));\n  const c = a.map((v, i) => Math.round(v + (b[i] - v) * fraction));\n  return `#${c.map((v) => v.toString(16).padStart(2, \"0\")).join(\"\")}`;\n}\n\nconst midColor = t.div[1];\nconst positiveColors = Array.from({ length: BAND_COUNT },\n  (_, i) => lerpHex(midColor, t.div[2], (i + 1) / BAND_COUNT));\nconst negativeColors = Array.from({ length: BAND_COUNT },\n  (_, i) => lerpHex(midColor, t.div[0], (i + 1) / BAND_COUNT));\n\n// --- Series: 2 x BAND_COUNT opaque area layers per host, stacked on top ----\n// so a deeper shade only shows through where the value actually reaches it.\nconst series = [];\nhostSeries.forEach((host, hostIndex) => {\n  for (let band = 0; band < BAND_COUNT; band += 1) {\n    series.push({\n      type: \"area\",\n      yAxis: hostIndex,\n      name: `${host.name} +band${band + 1}`,\n      color: positiveColors[band],\n      fillColor: positiveColors[band],\n      fillOpacity: 1,\n      lineWidth: 0,\n      threshold: 0,\n      marker: { enabled: false },\n      enableMouseTracking: false,\n      showInLegend: false,\n      data: host.values.map((v, p) => [START_MS + p * STEP_MS, foldBand(v, band, BAND_HEIGHT)]),\n    });\n    series.push({\n      type: \"area\",\n      yAxis: hostIndex,\n      name: `${host.name} -band${band + 1}`,\n      color: negativeColors[band],\n      fillColor: negativeColors[band],\n      fillOpacity: 1,\n      lineWidth: 0,\n      threshold: 0,\n      marker: { enabled: false },\n      enableMouseTracking: false,\n      showInLegend: false,\n      data: host.values.map((v, p) => [START_MS + p * STEP_MS, foldBand(-v, band, BAND_HEIGHT)]),\n    });\n  }\n});\n\n// --- Color key: one legend-only dummy series per band/polarity so the ------\n// blue/red-intensity encoding is self-explanatory without reading the subtitle.\nconst keySeries = [\n  { color: negativeColors[2], name: \"≥30pp below\" },\n  { color: negativeColors[1], name: \"15–30pp below\" },\n  { color: negativeColors[0], name: \"0–15pp below\" },\n  { color: positiveColors[0], name: \"0–15pp above\" },\n  { color: positiveColors[1], name: \"15–30pp above\" },\n  { color: positiveColors[2], name: \"≥30pp above\" },\n].map((item) => ({\n  type: \"area\",\n  yAxis: 0,\n  name: item.name,\n  color: item.color,\n  fillColor: item.color,\n  marker: { enabled: false },\n  enableMouseTracking: false,\n  showInLegend: true,\n  data: [],\n}));\n\n// --- Panels: one compact yAxis strip per host, sharing the datetime xAxis --\n// Row labels are drawn manually in chart.events.load (below) rather than via\n// yAxis.title: Highcharts reserves left-margin space for *every* stacked yAxis\n// title as if they were parallel side-by-side axes, ballooning the left gutter\n// and forcing later rows' titles increasingly off-canvas once that reservation\n// is overridden -- a plain SVG label per row sidesteps that layout entirely.\nconst rowHeightPct = 100 / HOSTS.length;\nconst yAxis = HOSTS.map((name, hostIndex) => ({\n  top: `${hostIndex * rowHeightPct}%`,\n  height: `${rowHeightPct - 1.4}%`,\n  min: 0,\n  max: BAND_HEIGHT,\n  gridLineWidth: 0,\n  tickLength: 0,\n  lineWidth: 0,\n  labels: { enabled: false },\n  title: { text: null },\n  plotLines: [{ value: 0, color: t.grid, width: 1, zIndex: 5 }],\n}));\n\n// --- Chart -------------------------------------------------------------------\nHighcharts.chart(\"container\", {\n  chart: { backgroundColor: \"transparent\", animation: false,\n           marginLeft: 110, marginRight: 40,\n           style: { fontFamily: \"inherit\" },\n           events: {\n             load() {\n               const chart = this;\n               HOSTS.forEach((name, hostIndex) => {\n                 const axis = chart.yAxis[hostIndex];\n                 chart.renderer.text(name, chart.plotLeft - 10, axis.top + axis.len / 2 + 4)\n                   .attr({ align: \"right\", zIndex: 6 })\n                   .css({ color: t.inkSoft, fontSize: \"13px\", fontWeight: \"500\" })\n                   .add();\n               });\n             },\n           } },\n  credits: { enabled: false },\n  colors: t.palette,\n  title: { text: \"horizon-basic · javascript · highcharts · anyplot.ai\",\n           style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" } },\n  subtitle: { text: \"CPU deviation from a 50% baseline, folded into 3 bands per host — deeper shade means a larger swing\",\n              style: { color: t.inkSoft, fontSize: \"14px\" } },\n  xAxis: {\n    type: \"datetime\",\n    lineColor: t.inkSoft, tickColor: t.inkSoft, gridLineWidth: 0,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n    title: { text: \"Time (UTC)\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n  },\n  yAxis,\n  legend: {\n    enabled: true,\n    layout: \"horizontal\",\n    align: \"center\",\n    verticalAlign: \"top\",\n    y: 46,\n    itemDistance: 14,\n    symbolWidth: 12,\n    symbolHeight: 12,\n    symbolRadius: 2,\n    itemStyle: { color: t.inkSoft, fontSize: \"12px\", fontWeight: \"normal\" },\n  },\n  tooltip: { enabled: false },\n  plotOptions: { series: { animation: false } },\n  series: [...series, ...keySeries],\n});\n"}