{"spec_id":"eye-diagram-basic","library":"d3","language":"javascript","code":"// anyplot.ai\n// eye-diagram-basic: Signal Integrity Eye Diagram\n// Library: d3 7.9.0 | JavaScript 22.22.3\n// Quality: 93/100 | Created: 2026-06-18\n\nconst tok = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\nconst margin = { top: 80, right: 70, bottom: 90, left: 110 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// Deterministic LCG RNG\nlet _rng = 42;\nfunction rand() { _rng = (_rng * 1664525 + 1013904223) >>> 0; return _rng / 4294967296; }\nfunction randn() {\n  return Math.sqrt(-2 * Math.log(Math.max(rand(), 1e-9))) * Math.cos(2 * Math.PI * rand());\n}\n\nconst N_TRACES = 400;\nconst SAMPLES = 160;\nconst NOISE_SIGMA = 0.05;\nconst JITTER_SIGMA = 0.03;\nconst BW = 0.15;\nconst sigmoid = (x) => 1 / (1 + Math.exp(-x / BW));\n\n// Build trace points: 2-UI window, 3 random NRZ bits per trace\nconst allPoints = [];\nfor (let i = 0; i < N_TRACES; i++) {\n  const b0 = rand() > 0.5 ? 1 : 0;\n  const b1 = rand() > 0.5 ? 1 : 0;\n  const b2 = rand() > 0.5 ? 1 : 0;\n  const j0 = randn() * JITTER_SIGMA;\n  const j1 = randn() * JITTER_SIGMA;\n  for (let s = 0; s < SAMPLES; s++) {\n    const tm = (s / (SAMPLES - 1)) * 2;\n    const v =\n      b0 +\n      (b1 - b0) * sigmoid(tm - j0) +\n      (b2 - b1) * sigmoid(tm - 1 - j1) +\n      randn() * NOISE_SIGMA;\n    allPoints.push([tm, v]);\n  }\n}\n\n// 2D histogram for density heatmap\nconst BINS_X = 200;\nconst BINS_Y = 120;\nconst V_MIN = -0.25;\nconst V_MAX = 1.25;\nconst T_MIN = 0;\nconst T_MAX = 2;\n\nconst hist = new Int32Array(BINS_X * BINS_Y);\nfor (const [tm, v] of allPoints) {\n  const bx = Math.floor(((tm - T_MIN) / (T_MAX - T_MIN)) * BINS_X);\n  const by = Math.floor(((V_MAX - v) / (V_MAX - V_MIN)) * BINS_Y);\n  if (bx >= 0 && bx < BINS_X && by >= 0 && by < BINS_Y) {\n    hist[by * BINS_X + bx]++;\n  }\n}\nconst maxCount = Math.max(...hist);\n\n// Color scale: pageBg → Imprint green → Imprint blue\nconst colorScale = d3\n  .scaleSequential()\n  .domain([0, maxCount])\n  .interpolator(d3.interpolateRgbBasis([tok.pageBg, tok.seq[0], tok.seq[1]]));\n\n// Precompute per-bin RGBA for efficient bilinear interpolation\nconst binRgba = new Uint8Array(BINS_X * BINS_Y * 4);\nfor (let bi = 0; bi < BINS_X * BINS_Y; bi++) {\n  const { r, g, b } = d3.color(colorScale(hist[bi])).rgb();\n  binRgba[bi * 4] = r;\n  binRgba[bi * 4 + 1] = g;\n  binRgba[bi * 4 + 2] = b;\n  binRgba[bi * 4 + 3] = 255;\n}\n\n// --- Hybrid canvas + SVG layout -------------------------------------------\n// Canvas carries the heatmap using per-pixel ImageData with bilinear\n// interpolation between bin centers — eliminates both banding and pixelation.\n// SVG handles axes, reference lines, labels, and title on top.\nd3.select(\"#container\").style(\"position\", \"relative\");\n\nconst canvasEl = d3\n  .select(\"#container\")\n  .append(\"canvas\")\n  .attr(\"width\", iw)\n  .attr(\"height\", ih)\n  .style(\"position\", \"absolute\")\n  .style(\"left\", `${margin.left}px`)\n  .style(\"top\", `${margin.top}px`);\n\nconst ctx = canvasEl.node().getContext(\"2d\");\nconst imgData = ctx.createImageData(iw, ih);\nconst pixels = imgData.data;\n\n// Per-pixel bilinear interpolation: map each canvas pixel to fractional bin\n// coordinates and blend the four surrounding bin colors for smooth output.\nfor (let py = 0; py < ih; py++) {\n  const fy = (py + 0.5) * BINS_Y / ih - 0.5;\n  const by0 = Math.max(0, Math.floor(fy));\n  const by1 = Math.min(BINS_Y - 1, by0 + 1);\n  const wdy1 = Math.max(0, Math.min(1, fy - by0));\n  const wdy0 = 1 - wdy1;\n\n  for (let px = 0; px < iw; px++) {\n    const fx = (px + 0.5) * BINS_X / iw - 0.5;\n    const bx0 = Math.max(0, Math.floor(fx));\n    const bx1 = Math.min(BINS_X - 1, bx0 + 1);\n    const wdx1 = Math.max(0, Math.min(1, fx - bx0));\n    const wdx0 = 1 - wdx1;\n\n    const i00 = (by0 * BINS_X + bx0) * 4;\n    const i10 = (by0 * BINS_X + bx1) * 4;\n    const i01 = (by1 * BINS_X + bx0) * 4;\n    const i11 = (by1 * BINS_X + bx1) * 4;\n\n    const w00 = wdx0 * wdy0;\n    const w10 = wdx1 * wdy0;\n    const w01 = wdx0 * wdy1;\n    const w11 = wdx1 * wdy1;\n\n    const pi = (py * iw + px) * 4;\n    pixels[pi] = w00 * binRgba[i00] + w10 * binRgba[i10] + w01 * binRgba[i01] + w11 * binRgba[i11];\n    pixels[pi + 1] = w00 * binRgba[i00 + 1] + w10 * binRgba[i10 + 1] + w01 * binRgba[i01 + 1] + w11 * binRgba[i11 + 1];\n    pixels[pi + 2] = w00 * binRgba[i00 + 2] + w10 * binRgba[i10 + 2] + w01 * binRgba[i01 + 2] + w11 * binRgba[i11 + 2];\n    pixels[pi + 3] = 255;\n  }\n}\nctx.putImageData(imgData, 0, 0);\n\n// SVG layer for axes, reference lines, labels, and title\nconst svg = d3\n  .select(\"#container\")\n  .append(\"svg\")\n  .attr(\"width\", width)\n  .attr(\"height\", height)\n  .style(\"position\", \"absolute\")\n  .style(\"left\", \"0\")\n  .style(\"top\", \"0\");\n\nconst g = svg.append(\"g\").attr(\"transform\", `translate(${margin.left},${margin.top})`);\n\n// Linear scales for overlay elements\nconst xScale = d3.scaleLinear().domain([T_MIN, T_MAX]).range([0, iw]);\nconst yScale = d3.scaleLinear().domain([V_MIN, V_MAX]).range([ih, 0]);\n\n// Dashed reference lines at nominal NRZ voltage levels (0 V and 1 V)\nfor (const level of [0, 1]) {\n  g.append(\"line\")\n    .attr(\"x1\", 0)\n    .attr(\"x2\", iw)\n    .attr(\"y1\", yScale(level))\n    .attr(\"y2\", yScale(level))\n    .attr(\"stroke\", tok.inkSoft)\n    .attr(\"stroke-dasharray\", \"8,5\")\n    .attr(\"stroke-opacity\", 0.55)\n    .attr(\"stroke-width\", 1.5);\n}\n\n// Dashed vertical line at t = 1 UI (sampling instant / eye center)\ng.append(\"line\")\n  .attr(\"x1\", xScale(1))\n  .attr(\"x2\", xScale(1))\n  .attr(\"y1\", 0)\n  .attr(\"y2\", ih)\n  .attr(\"stroke\", tok.inkSoft)\n  .attr(\"stroke-dasharray\", \"8,5\")\n  .attr(\"stroke-opacity\", 0.55)\n  .attr(\"stroke-width\", 1.5);\n\n// Axes\nconst xAxis = g\n  .append(\"g\")\n  .attr(\"transform\", `translate(0,${ih})`)\n  .call(d3.axisBottom(xScale).ticks(5).tickFormat((d) => `${d} UI`));\nconst yAxis = g\n  .append(\"g\")\n  .call(d3.axisLeft(yScale).ticks(6).tickFormat((d) => d.toFixed(1) + \" V\"));\n\nfor (const ax of [xAxis, yAxis]) {\n  ax.selectAll(\"text\").attr(\"fill\", tok.inkSoft).style(\"font-size\", \"14px\");\n  ax.selectAll(\"line\").attr(\"stroke\", tok.inkSoft).attr(\"stroke-opacity\", 0.35);\n  ax.select(\".domain\").attr(\"stroke\", tok.inkSoft).attr(\"stroke-opacity\", 0.5);\n}\n\n// Axis labels\ng.append(\"text\")\n  .attr(\"x\", iw / 2)\n  .attr(\"y\", ih + 65)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", tok.ink)\n  .style(\"font-size\", \"16px\")\n  .text(\"Time (UI)\");\n\ng.append(\"text\")\n  .attr(\"transform\", \"rotate(-90)\")\n  .attr(\"x\", -ih / 2)\n  .attr(\"y\", -80)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", tok.ink)\n  .style(\"font-size\", \"16px\")\n  .text(\"Voltage (V)\");\n\n// Chart title\nsvg\n  .append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", 48)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", tok.ink)\n  .style(\"font-size\", \"22px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"eye-diagram-basic · javascript · d3 · anyplot.ai\");\n"}