{"spec_id":"eye-diagram-basic","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// eye-diagram-basic: Signal Integrity Eye Diagram\n// Library: chartjs 4.4.7 | JavaScript 22.22.3\n// Quality: 90/100 | Created: 2026-06-18\n\n//# anyplot-orientation: landscape\nconst t = window.ANYPLOT_TOKENS;\n\n// Deterministic LCG — no seeded Math.random in the browser\nlet _s = 42;\nfunction rand() {\n  _s = (_s * 1664525 + 1013904223) >>> 0;\n  return _s / 4294967296;\n}\n\n// Box-Muller transform for Gaussian samples\nfunction randn() {\n  const u = Math.max(rand(), 1e-12);\n  return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * rand());\n}\n\n// Bandwidth-limited sigmoid transition (models realistic rise/fall)\nfunction sigmoid(x) {\n  return 1 / (1 + Math.exp(-x));\n}\n\nconst NUM_TRACES = 300;\nconst SAMPLES_PER_UI = 100;\nconst NOISE_SIGMA = 0.05;\nconst JITTER_SIGMA = 0.03;\nconst BW = 15;\n\n// Dashed reference lines at nominal NRZ bit levels (0 V and 1 V)\nconst refLine = (yVal) => ({\n  data: [{ x: 0, y: yVal }, { x: 2, y: yVal }],\n  borderColor: t.inkSoft,\n  borderWidth: 1.5,\n  borderDash: [8, 5],\n  pointRadius: 0,\n  fill: false,\n  tension: 0,\n});\n\n// Generate all 300 NRZ traces and tag each with its bit-transition count.\n// Transition count determines density tier for the color ramp:\n//   0 transitions = trace always at 0 V or 1 V rail (highest local density) → t.seq[1] blue\n//   1 transition  = one level crossing (moderate density)                   → palette[0] green mid-alpha\n//   2-3 transitions = frequent crossing (lower point density)               → palette[0] green low-alpha\nconst rawTraces = [];\nfor (let tr = 0; tr < NUM_TRACES; tr++) {\n  const b = [rand(), rand(), rand(), rand()].map(v => (v > 0.5 ? 1 : 0));\n  const j = [randn() * JITTER_SIGMA, randn() * JITTER_SIGMA, randn() * JITTER_SIGMA];\n  const nTrans = Math.abs(b[1] - b[0]) + Math.abs(b[2] - b[1]) + Math.abs(b[3] - b[2]);\n\n  const pts = [];\n  for (let i = 0; i <= SAMPLES_PER_UI * 2; i++) {\n    const time = i / SAMPLES_PER_UI;\n    let v = b[0];\n    v += (b[1] - b[0]) * sigmoid(BW * (time - j[0]));\n    v += (b[2] - b[1]) * sigmoid(BW * (time - 1 - j[1]));\n    v += (b[3] - b[2]) * sigmoid(BW * (time - 2 - j[2]));\n    v += randn() * NOISE_SIGMA;\n    pts.push({ x: time, y: v });\n  }\n  rawTraces.push({ pts, nTrans });\n}\n\n// Measure eye opening at t = 0.5 UI (first sampling point, pts index 50).\n// 5th percentile of the 1 V group − 95th percentile of the 0 V group = practical eye height.\nconst voltagesAt05 = rawTraces.map(({ pts }) => pts[50].y);\nconst lowRailVs = voltagesAt05.filter(v => v < 0.5).sort((a, b) => a - b);\nconst highRailVs = voltagesAt05.filter(v => v >= 0.5).sort((a, b) => a - b);\nconst eyeLow  = lowRailVs[Math.floor(lowRailVs.length * 0.95)]  ?? 0.1;\nconst eyeHigh = highRailVs[Math.floor(highRailVs.length * 0.05)] ?? 0.9;\nconst eyeHeightVal = Math.max(0, eyeHigh - eyeLow);\n\n// Sort traces: high-transition (sparse, background) first → stable rail (dense) last.\n// This ensures dense rail regions accumulate the blue t.seq[1] color on top of the green base.\nrawTraces.sort((ra, rb) => rb.nTrans - ra.nTrans);\n\n// Density-to-color ramp: green (sparse) → blue (dense rail regions).\n// Stable traces (0 transitions) rendered last in t.seq[1] to accent high-density rails.\nconst traceDatasets = rawTraces.map(({ pts, nTrans }) => {\n  const color = nTrans === 0 ? t.seq[1] + \"20\"       // #4467A3 ~12.5% alpha — stable rail (dense)\n              : nTrans === 1 ? t.palette[0] + \"0e\"   // #009E73 ~5.5% alpha — one crossing\n              :                t.palette[0] + \"09\";  // #009E73 ~3.5% alpha — frequent crossing\n  return { data: pts, borderColor: color, borderWidth: 1.5, pointRadius: 0, fill: false, tension: 0 };\n});\n\n// afterDraw plugin: bracket annotations marking eye height at both sampling points (t = 0.5 and 1.5 UI).\n// Demonstrates Chart.js's custom plugin API; fulfills the spec's optional eye-metrics annotation.\nconst eyeAnnotationPlugin = {\n  id: \"eyeAnnotation\",\n  afterDraw(chart) {\n    const ctx = chart.ctx;\n    const xs = chart.scales.x;\n    const ys = chart.scales.y;\n    // Canvas y-coordinates: yTop < yBot because pixel origin is top-left\n    const yTop = ys.getPixelForValue(eyeHigh); // bottom edge of 1 V rail (high on screen)\n    const yBot = ys.getPixelForValue(eyeLow);  // top edge of 0 V rail (low on screen)\n    const tick = 8; // px half-width of bracket crossbars\n\n    ctx.save();\n    ctx.globalAlpha = 0.75;\n    ctx.strokeStyle = t.inkSoft;\n    ctx.lineWidth = 1.5;\n\n    [0.5, 1.5].forEach(tUI => {\n      const xC = xs.getPixelForValue(tUI);\n      // Dashed vertical span\n      ctx.setLineDash([4, 3]);\n      ctx.beginPath(); ctx.moveTo(xC, yTop); ctx.lineTo(xC, yBot); ctx.stroke();\n      // Solid horizontal crossbars\n      ctx.setLineDash([]);\n      ctx.beginPath(); ctx.moveTo(xC - tick, yTop); ctx.lineTo(xC + tick, yTop); ctx.stroke();\n      ctx.beginPath(); ctx.moveTo(xC - tick, yBot); ctx.lineTo(xC + tick, yBot); ctx.stroke();\n    });\n\n    ctx.setLineDash([]);\n    ctx.globalAlpha = 0.90;\n    ctx.fillStyle = t.ink;\n    ctx.font = \"500 12px sans-serif\";\n    ctx.textAlign = \"left\";\n    const label = `Eye H ≈ ${eyeHeightVal.toFixed(2)} V`;\n    [0.5, 1.5].forEach(tUI => {\n      const xC = xs.getPixelForValue(tUI);\n      ctx.fillText(label, xC + tick + 4, (yTop + yBot) / 2 + 4);\n    });\n\n    ctx.restore();\n  },\n};\n\n// Mount\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// Chart\nnew Chart(canvas, {\n  type: \"line\",\n  data: {\n    datasets: [refLine(0), refLine(1), ...traceDatasets],\n  },\n  plugins: [eyeAnnotationPlugin],\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    parsing: false,\n    layout: { padding: { top: 4, right: 24, bottom: 10, left: 8 } },\n    plugins: {\n      title: {\n        display: true,\n        text: \"eye-diagram-basic · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22, weight: \"500\" },\n        padding: { top: 8, bottom: 16 },\n      },\n      legend: { display: false },\n      tooltip: { enabled: false },\n    },\n    scales: {\n      x: {\n        type: \"linear\",\n        min: 0,\n        max: 2,\n        title: {\n          display: true,\n          text: \"Time (UI)\",\n          color: t.ink,\n          font: { size: 16, weight: \"500\" },\n        },\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 14 },\n          stepSize: 0.5,\n        },\n        grid: { color: t.grid },\n        border: { display: false },\n      },\n      y: {\n        min: -0.3,\n        max: 1.3,\n        title: {\n          display: true,\n          text: \"Voltage (V)\",\n          color: t.ink,\n          font: { size: 16, weight: \"500\" },\n        },\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 14 },\n          stepSize: 0.5,\n        },\n        grid: { color: t.grid },\n        border: { display: false },\n      },\n    },\n  },\n});\n"}