{"spec_id":"depth-order-book","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// depth-order-book: Order Book Depth Chart\n// Library: chartjs 4.4.7 | JavaScript 22.22.3\n// Quality: 89/100 | Created: 2026-06-15\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data: BTC/USD order book snapshot, mid price $60,000 ---\nconst MID = 60000;\nconst BEST_BID = 59995;\nconst BEST_ASK = 60005;\nconst SPREAD = BEST_ASK - BEST_BID;\nconst TICK = 10;\nconst N = 50;\n\n// XORshift LCG — deterministic, seed 42\nlet _s = 42;\nfunction rng() {\n  _s ^= _s << 13;\n  _s ^= _s >> 17;\n  _s ^= _s << 5;\n  return (_s >>> 0) / 4294967296;\n}\n\n// Bid levels: index 0 = worst bid (lowest price), index N-1 = best bid\nconst bidLevels = [];\nfor (let i = 0; i < N; i++) {\n  const price = BEST_BID - (N - 1 - i) * TICK;\n  const distTicks = N - 1 - i; // 0 at best bid, N-1 at worst\n  const base = 0.25 + (distTicks / N) * 1.8;\n  const wall = (distTicks === 15 || distTicks === 32) ? 3.0 + rng() * 2.0 : 0;\n  bidLevels.push({ price, qty: base * (0.6 + rng() * 0.7) + wall });\n}\n\n// Cumulate bid from best bid outward (index N-1 → 0)\nlet cBid = 0;\nfor (let i = N - 1; i >= 0; i--) {\n  cBid += bidLevels[i].qty;\n  bidLevels[i].cum = cBid;\n}\nconst bidData = bidLevels.map(l => ({ x: l.price, y: l.cum }));\n\n// Ask levels: index 0 = best ask (lowest price), index N-1 = worst ask\nconst askLevels = [];\nfor (let i = 0; i < N; i++) {\n  const price = BEST_ASK + i * TICK;\n  const distTicks = i; // 0 at best ask, N-1 at worst\n  const base = 0.25 + (distTicks / N) * 1.8;\n  const wall = (distTicks === 15 || distTicks === 32) ? 3.0 + rng() * 2.0 : 0;\n  askLevels.push({ price, qty: base * (0.6 + rng() * 0.7) + wall });\n}\n\n// Cumulate ask from best ask outward (index 0 → N-1)\nlet cAsk = 0;\nfor (let i = 0; i < N; i++) {\n  cAsk += askLevels[i].qty;\n  askLevels[i].cum = cAsk;\n}\nconst askData = askLevels.map(l => ({ x: l.price, y: l.cum }));\n\n// --- Mount ---\nconst canvas = document.createElement('canvas');\ndocument.getElementById('container').appendChild(canvas);\n\n// --- Plugins ---\nconst bgPlugin = {\n  id: 'bgPlugin',\n  beforeDraw(chart) {\n    chart.ctx.save();\n    chart.ctx.fillStyle = t.pageBg;\n    chart.ctx.fillRect(0, 0, chart.width, chart.height);\n    chart.ctx.restore();\n  },\n};\n\nconst midLinePlugin = {\n  id: 'midLinePlugin',\n  afterDatasetsDraw(chart) {\n    const { ctx, scales: { x: xs, y: ys } } = chart;\n    const mx = xs.getPixelForValue(MID);\n\n    // Dashed vertical line at mid price\n    ctx.save();\n    ctx.setLineDash([10, 7]);\n    ctx.strokeStyle = t.inkSoft;\n    ctx.lineWidth = 1.5;\n    ctx.beginPath();\n    ctx.moveTo(mx, ys.top);\n    ctx.lineTo(mx, ys.bottom);\n    ctx.stroke();\n    ctx.restore();\n\n    // Mid price and spread labels with opaque background to avoid line intersection\n    ctx.save();\n    ctx.textAlign = 'center';\n    ctx.textBaseline = 'middle';\n    const pad = 6;\n\n    ctx.font = 'bold 14px sans-serif';\n    const midText = '$' + MID.toLocaleString();\n    const midW = ctx.measureText(midText).width;\n    const midY = ys.top + 22;\n    ctx.fillStyle = t.pageBg;\n    ctx.fillRect(mx - midW / 2 - pad, midY - 9, midW + pad * 2, 18);\n    ctx.fillStyle = t.ink;\n    ctx.fillText(midText, mx, midY);\n\n    ctx.font = '13px sans-serif';\n    const spreadText = 'Spread: $' + SPREAD.toFixed(2);\n    const spreadW = ctx.measureText(spreadText).width;\n    const spreadY = ys.top + 42;\n    ctx.fillStyle = t.pageBg;\n    ctx.fillRect(mx - spreadW / 2 - pad, spreadY - 8, spreadW + pad * 2, 16);\n    ctx.fillStyle = t.inkSoft;\n    ctx.fillText(spreadText, mx, spreadY);\n\n    ctx.restore();\n  },\n};\n\n// --- Chart ---\nnew Chart(canvas, {\n  type: 'line',\n  data: {\n    datasets: [\n      {\n        label: 'Bids (Buy)',\n        data: bidData,\n        stepped: 'before',\n        fill: 'origin',\n        backgroundColor: 'rgba(0,158,115,0.28)',\n        borderColor: t.palette[0],\n        borderWidth: 2.5,\n        pointRadius: 0,\n      },\n      {\n        label: 'Asks (Sell)',\n        data: askData,\n        stepped: 'before',\n        fill: 'origin',\n        backgroundColor: 'rgba(174,48,48,0.28)',\n        borderColor: t.palette[4],\n        borderWidth: 2.5,\n        pointRadius: 0,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    parsing: false,\n    plugins: {\n      title: {\n        display: true,\n        text: 'depth-order-book · javascript · chartjs · anyplot.ai',\n        color: t.ink,\n        font: { size: 22, weight: '500' },\n        padding: { top: 20, bottom: 16 },\n      },\n      legend: {\n        labels: {\n          color: t.ink,\n          font: { size: 16 },\n          boxWidth: 24,\n          padding: 20,\n        },\n      },\n    },\n    scales: {\n      x: {\n        type: 'linear',\n        border: { display: false },\n        min: BEST_BID - (N - 1) * TICK - 60,\n        max: BEST_ASK + (N - 1) * TICK + 60,\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 14 },\n          callback: v => '$' + Number(v).toLocaleString(),\n          maxTicksLimit: 11,\n        },\n        grid: { color: t.grid },\n        title: {\n          display: true,\n          text: 'Price (USD)',\n          color: t.ink,\n          font: { size: 16 },\n          padding: { top: 10 },\n        },\n      },\n      y: {\n        beginAtZero: true,\n        border: { display: false },\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 14 },\n          callback: v => Number(v).toFixed(1) + ' BTC',\n          maxTicksLimit: 8,\n        },\n        grid: { color: t.grid },\n        title: {\n          display: true,\n          text: 'Cumulative Volume (BTC)',\n          color: t.ink,\n          font: { size: 16 },\n          padding: { bottom: 10 },\n        },\n      },\n    },\n  },\n  plugins: [bgPlugin, midLinePlugin],\n});\n"}