{"spec_id":"depth-order-book","library":"echarts","language":"javascript","code":"// anyplot.ai\n// depth-order-book: Order Book Depth Chart\n// Library: echarts 5.5.1 | JavaScript 22.22.3\n// Quality: 93/100 | Created: 2026-06-15\n\n//# anyplot-orientation: landscape\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data -------------------------------------------------------------------\n// BTC/USD order book snapshot, mid price ~60,000\nconst MID_PRICE = 60000;\nconst SPREAD = 12; // 12 USD spread\n\n// Fixed-seed LCG for reproducible synthetic data\nfunction lcg(seed) {\n  let s = seed;\n  return () => { s = (1664525 * s + 1013904223) & 0xffffffff; return (s >>> 0) / 0xffffffff; };\n}\nconst rand = lcg(42);\n\n// Generate 50 bid levels (below mid, best bid = MID_PRICE - SPREAD/2)\nconst BID_COUNT = 50;\nconst ASK_COUNT = 50;\nconst HALF_SPREAD = SPREAD / 2;\n\n// Bids: price levels from best bid down to ~59,400\n// Quantities grow away from mid (wall effect)\nconst bidLevels = [];\nfor (let i = 0; i < BID_COUNT; i++) {\n  const price = Math.round((MID_PRICE - HALF_SPREAD - i * 12 - rand() * 4) * 10) / 10;\n  const distFactor = 1 + i * 0.04 + rand() * 0.5;\n  const qty = Math.round((0.15 + distFactor * 0.12 + rand() * 0.3) * 100) / 100;\n  bidLevels.push({ price, qty });\n}\n\n// Asks: price levels from best ask up to ~60,600\nconst askLevels = [];\nfor (let i = 0; i < ASK_COUNT; i++) {\n  const price = Math.round((MID_PRICE + HALF_SPREAD + i * 12 + rand() * 4) * 10) / 10;\n  const distFactor = 1 + i * 0.04 + rand() * 0.5;\n  const qty = Math.round((0.15 + distFactor * 0.12 + rand() * 0.3) * 100) / 100;\n  askLevels.push({ price, qty });\n}\n\n// Cumulative quantities (from mid outward)\nlet bidCum = 0;\nconst bidData = bidLevels.map(({ price, qty }) => {\n  bidCum += qty;\n  return [price, Math.round(bidCum * 100) / 100];\n});\n\nlet askCum = 0;\nconst askData = askLevels.map(({ price, qty }) => {\n  askCum += qty;\n  return [price, Math.round(askCum * 100) / 100];\n});\n\n// Bids are stored high→low (best bid first), ECharts needs low→high for xAxis\nbidData.reverse();\n\n// Mid price and spread annotation\nconst midPriceLabel = `$${MID_PRICE.toLocaleString()}`;\nconst spreadLabel = `Spread: $${SPREAD}`;\n\n// X-axis range\nconst xMin = bidData[0][0] - 20;\nconst xMax = askData[askData.length - 1][0] + 20;\n\n// Y-axis max (rounded to avoid float overflow tick)\nconst yMax = Math.ceil(Math.max(bidCum, askCum) * 1.08);\n\n// --- Init -------------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option -----------------------------------------------------------------\nchart.setOption({\n  animation: false,\n  color: t.palette,\n  backgroundColor: \"transparent\",\n\n  title: {\n    text: \"BTC/USD Order Book · depth-order-book · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    top: 18,\n    textStyle: { color: t.ink, fontSize: 18, fontWeight: \"500\" },\n  },\n\n  grid: { left: 90, right: 60, top: 80, bottom: 80 },\n\n  tooltip: {\n    trigger: \"axis\",\n    axisPointer: { type: \"line\", lineStyle: { color: t.inkSoft, width: 1, type: \"dashed\" } },\n    backgroundColor: t.elevatedBg,\n    borderColor: t.grid,\n    textStyle: { color: t.ink, fontSize: 13 },\n    formatter: (params) => {\n      if (!params || params.length === 0) return \"\";\n      const p = params[0].data[0];\n      const rows = params.map(\n        (s) => `<span style=\"display:inline-block;width:10px;height:10px;border-radius:50%;background:${s.color};margin-right:6px;\"></span>${s.seriesName}: <b>${s.data[1].toFixed(2)} BTC</b>`\n      );\n      return `<div style=\"padding:4px 8px\"><b>$${p.toLocaleString()}</b><br/>${rows.join(\"<br/>\")}</div>`;\n    },\n  },\n\n  xAxis: {\n    type: \"value\",\n    min: xMin,\n    max: xMax,\n    name: \"Price (USD)\",\n    nameLocation: \"middle\",\n    nameGap: 48,\n    nameTextStyle: { color: t.inkSoft, fontSize: 14 },\n    axisLabel: {\n      color: t.inkSoft,\n      fontSize: 13,\n      formatter: (v) => `$${(v / 1000).toFixed(1)}k`,\n    },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { show: false },\n  },\n\n  yAxis: {\n    type: \"value\",\n    name: \"Cumulative Volume (BTC)\",\n    nameLocation: \"middle\",\n    nameGap: 65,\n    nameTextStyle: { color: t.inkSoft, fontSize: 14 },\n    min: 0,\n    max: yMax,\n    axisLabel: { color: t.inkSoft, fontSize: 13, formatter: (v) => v.toFixed(0) },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { lineStyle: { color: t.grid, width: 1 } },\n  },\n\n  // Mid price vertical line + spread annotation\n  markLine: {},\n\n  series: [\n    {\n      name: \"Bids\",\n      type: \"line\",\n      step: \"end\",\n      data: bidData,\n      symbol: \"none\",\n      lineStyle: { color: \"#009E73\", width: 2 },\n      areaStyle: { color: \"#009E73\", opacity: 0.25 },\n      markLine: {\n        symbol: \"none\",\n        silent: true,\n        lineStyle: { type: \"dashed\", color: t.inkSoft, width: 1.5, opacity: 0.7 },\n        label: {\n          show: true,\n          position: \"end\",\n          rotate: 0,\n          formatter: `${midPriceLabel}  ${spreadLabel}`,\n          color: t.ink,\n          fontSize: 13,\n          fontWeight: \"500\",\n          backgroundColor: t.elevatedBg,\n          padding: [4, 8],\n          borderRadius: 4,\n          borderColor: t.grid,\n          borderWidth: 1,\n        },\n        data: [{ xAxis: MID_PRICE }],\n      },\n    },\n    {\n      name: \"Asks\",\n      type: \"line\",\n      step: \"start\",\n      data: askData,\n      symbol: \"none\",\n      lineStyle: { color: \"#AE3030\", width: 2 },\n      areaStyle: { color: \"#AE3030\", opacity: 0.25 },\n    },\n  ],\n\n  legend: {\n    top: 50,\n    right: 60,\n    textStyle: { color: t.ink, fontSize: 15 },\n    itemWidth: 20,\n    itemHeight: 12,\n    data: [\n      { name: \"Bids\", itemStyle: { color: \"#009E73\" } },\n      { name: \"Asks\", itemStyle: { color: \"#AE3030\" } },\n    ],\n  },\n});\n"}