{"spec_id":"ohlc-bar","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// ohlc-bar: OHLC Bar Chart\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 93/100 | Created: 2026-09-02\n\n// The core bundle (no highcharts-more / modules/stock) has no \"ohlc\" series —\n// that type ships in modules/ohlc.js / modules/stock.js, neither of which is\n// loaded. Each bar is built from a \"line\" series with null-separated strokes\n// (high-low range, left open tick, right close tick) drawn per point on a\n// datetime x-axis — pure core series types, no add-on module, no\n// cross-library workaround.\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic LCG) ------------------------------------\nlet seed = 42;\nfunction lcg() {\n  seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n}\n\nconst upColor = t.palette[0]; // #009E73 brand green — bullish (profit/up)\nconst downColor = t.palette[4]; // #AE3030 matte red — bearish (loss/down), finance semantic anchor\n\nconst dayMs = 24 * 3600 * 1000;\nconst numDays = 42;\nconst dates = [];\nlet cursor = Date.UTC(2024, 1, 1); // Thu 1 Feb 2024\nwhile (dates.length < numDays) {\n  const weekday = new Date(cursor).getUTCDay();\n  if (weekday !== 0 && weekday !== 6) dates.push(cursor);\n  cursor += dayMs;\n}\n\nlet price = 78.4; // WTI crude oil, USD per barrel\nconst bars = dates.map((time) => {\n  const open = price;\n  const drift = (lcg() - 0.5) * 2.6;\n  const close = Math.max(20, open + drift);\n  const high = Math.max(open, close) + lcg() * 1.5;\n  const low = Math.max(10, Math.min(open, close) - lcg() * 1.5);\n  price = close;\n  return {\n    time,\n    open: +open.toFixed(2),\n    high: +high.toFixed(2),\n    low: +low.toFixed(2),\n    close: +close.toFixed(2),\n    bullish: close >= open,\n  };\n});\n\nconst allLows = bars.map((b) => b.low);\nconst allHighs = bars.map((b) => b.high);\nconst pad = (Math.max(...allHighs) - Math.min(...allLows)) * 0.08;\nconst yMin = Math.floor(Math.min(...allLows) - pad);\nconst yMax = Math.ceil(Math.max(...allHighs) + pad);\n\n// The sharpest single-day move becomes the data-storytelling focal point\n// (a dashed xAxis.plotLine + label — core-bundle, no annotations module).\nlet sharpest = bars[0];\nfor (const b of bars) {\n  if (Math.abs(b.close - b.open) > Math.abs(sharpest.close - sharpest.open)) sharpest = b;\n}\nconst sharpestPct = ((sharpest.close - sharpest.open) / sharpest.open) * 100;\n\n// Each bar draws 3 strokes: the high-low range, a left tick at open, a right\n// tick at close — separated by null points so Highcharts breaks the line.\n// The close tick also carries a triangle marker (▲ up / ▼ down) so direction\n// reads from shape, not solely from the red/green hue.\nconst tickOffset = dayMs * 0.32;\nfunction barStrokes(bar) {\n  const left = bar.time - tickOffset;\n  const mid = bar.time;\n  const right = bar.time + tickOffset;\n  const color = bar.bullish ? upColor : downColor;\n  return [\n    { x: mid, y: bar.low, custom: bar },\n    { x: mid, y: bar.high, custom: bar },\n    { x: mid, y: null },\n    { x: left, y: bar.open, custom: bar },\n    { x: mid, y: bar.open, custom: bar },\n    { x: mid, y: null },\n    { x: mid, y: bar.close, custom: bar },\n    {\n      x: right,\n      y: bar.close,\n      custom: bar,\n      marker: {\n        enabled: true,\n        symbol: bar.bullish ? \"triangle\" : \"triangle-down\",\n        radius: 5,\n        fillColor: color,\n        lineWidth: 0,\n      },\n    },\n    { x: mid, y: null },\n  ];\n}\n\nconst upData = bars.filter((b) => b.bullish).flatMap(barStrokes);\nconst downData = bars.filter((b) => !b.bullish).flatMap(barStrokes);\n\n// --- Chart -------------------------------------------------------------------\nHighcharts.chart(\"container\", {\n  chart: {\n    type: \"line\",\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n  },\n  credits: { enabled: false },\n  colors: t.palette,\n  title: {\n    text: \"Crude Oil Futures · ohlc-bar · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  xAxis: {\n    type: \"datetime\",\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n    title: { text: \"Trading Date\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n    crosshair: { width: 1, color: t.inkSoft, dashStyle: \"ShortDot\" },\n    plotLines: [\n      {\n        value: sharpest.time,\n        color: t.inkSoft,\n        dashStyle: \"Dash\",\n        width: 1,\n        zIndex: 5,\n        label: {\n          text: `Sharpest move: ${sharpestPct >= 0 ? \"+\" : \"\"}${sharpestPct.toFixed(1)}%`,\n          style: { color: t.inkSoft, fontSize: \"13px\" },\n          rotation: 0,\n          y: -10,\n        },\n      },\n    ],\n  },\n  yAxis: {\n    min: yMin,\n    max: yMax,\n    title: { text: \"Price per Barrel (USD)\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n    gridLineColor: t.grid,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n  },\n  legend: {\n    enabled: true,\n    itemStyle: { color: t.inkSoft, fontSize: \"14px\" },\n    itemHoverStyle: { color: t.ink },\n    symbolWidth: 22,\n    itemDistance: 24,\n    padding: 12,\n  },\n  tooltip: {\n    outside: false,\n    formatter: function () {\n      const c = this.point.custom;\n      if (!c) return false;\n      return (\n        `<b>${Highcharts.dateFormat(\"%b %e, %Y\", c.time)}</b><br/>` +\n        `Open: $${c.open.toFixed(2)}<br/>High: $${c.high.toFixed(2)}<br/>` +\n        `Low: $${c.low.toFixed(2)}<br/>Close: $${c.close.toFixed(2)}`\n      );\n    },\n  },\n  plotOptions: {\n    series: { animation: false, marker: { enabled: false }, lineWidth: 3 },\n  },\n  series: [\n    {\n      name: \"Up (Close ≥ Open)\",\n      data: upData,\n      color: upColor,\n      showInLegend: true,\n    },\n    {\n      name: \"Down (Close < Open)\",\n      data: downData,\n      color: downColor,\n      showInLegend: true,\n    },\n  ],\n});\n"}