{"spec_id":"eye-diagram-basic","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\neye-diagram-basic: Signal Integrity Eye Diagram\nLibrary: plotly 6.8.0 | Python 3.13.14\nQuality: 89/100 | Updated: 2026-06-18\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\n\n# Imprint sequential colorscale for density — page bg (zero density) → green → blue\nimprint_seq = [[0.0, PAGE_BG], [0.35, \"#009E73\"], [1.0, \"#4467A3\"]]\n\n# Data\nnp.random.seed(42)\n\nn_traces = 400\nsamples_per_ui = 150\nn_ui = 2\ntotal_samples = n_ui * samples_per_ui\n\n# Time axis normalized to unit intervals\ntime = np.linspace(0, n_ui, total_samples)\n\n# Generate random NRZ bit sequences (4 bits to cover 2 UI with transitions)\nn_bits = 4\nbit_sequences = np.random.randint(0, 2, size=(n_traces, n_bits))\n\n# Build smooth NRZ waveforms with raised-cosine transitions (vectorized)\nrolloff = 0.15  # transition sharpness (fraction of UI)\nnoise_sigma = 0.05  # 5% of amplitude\njitter_sigma = 0.03  # 3% of UI\n\njitter = np.random.normal(0, jitter_sigma, (n_traces, n_bits))\n\n# Broadcast time across traces: shape (n_traces, total_samples)\ntime_broadcast = np.broadcast_to(time, (n_traces, total_samples))\n\n# Start from first bit level\nvoltage = np.full((n_traces, total_samples), bit_sequences[:, 0:1], dtype=float)\n\n# Apply transitions for each bit boundary using vectorized tanh\nfor b in range(1, n_bits):\n    transition_time = (b - 1.0) + jitter[:, b : b + 1]  # shape (n_traces, 1)\n    transition = 0.5 * (1 + np.tanh((time_broadcast - transition_time) / rolloff))\n    voltage = voltage * (1 - transition) + bit_sequences[:, b : b + 1] * transition\n\n# Add Gaussian noise\nvoltage += np.random.normal(0, noise_sigma, voltage.shape)\n\nall_time = np.tile(time, n_traces)\nall_voltage = voltage.ravel()\n\n# Compute eye height at first eye center (t ≈ 0.5 UI)\ncenter_mask = (all_time > 0.35) & (all_time < 0.65)\nhigh_at_center = all_voltage[center_mask & (all_voltage > 0.5)]\nlow_at_center = all_voltage[center_mask & (all_voltage < 0.5)]\nmean_high = float(np.mean(high_at_center))\nmean_low = float(np.mean(low_at_center))\neye_height = mean_high - mean_low\n\n# Compute eye width: largest time gap where no traces cross the 0.5 V midpoint\n# (first eye only, 0–1 UI)\nmidband_mask = (all_voltage > 0.4) & (all_voltage < 0.6) & (all_time > 0.0) & (all_time < 1.0)\ntransition_times = np.sort(all_time[midband_mask])\nif len(transition_times) > 1:\n    gaps = np.diff(transition_times)\n    max_gap_idx = int(np.argmax(gaps))\n    eye_open_start = float(transition_times[max_gap_idx])\n    eye_open_end = float(transition_times[max_gap_idx + 1])\n    eye_width_ui = eye_open_end - eye_open_start\nelse:\n    eye_open_start, eye_open_end = 0.2, 0.8\n    eye_width_ui = 0.6\n\n# Plot — density heatmap with smoothing to reduce graininess\nfig = go.Figure(\n    data=go.Histogram2d(\n        x=all_time,\n        y=all_voltage,\n        nbinsx=500,\n        nbinsy=350,\n        zsmooth=\"best\",\n        colorscale=imprint_seq,\n        colorbar={\n            \"title\": {\"text\": \"Trace Density\", \"font\": {\"size\": 12, \"color\": INK_SOFT}},\n            \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n            \"thickness\": 14,\n            \"len\": 0.7,\n            \"outlinewidth\": 0,\n            \"bgcolor\": \"rgba(0,0,0,0)\",\n            \"borderwidth\": 0,\n            \"y\": 0.5,\n        },\n        hovertemplate=\"Time: %{x:.2f} UI<br>Voltage: %{y:.3f} V<br>Density: %{z}<extra></extra>\",\n    )\n)\n\nACCENT = \"#009E73\"\n\n# Faint horizontal reference lines at NRZ nominal logic levels (0 V and 1 V)\nfor logic_v in [0.0, 1.0]:\n    fig.add_shape(\n        type=\"line\",\n        x0=0,\n        x1=n_ui,\n        y0=logic_v,\n        y1=logic_v,\n        line={\"color\": INK_SOFT, \"width\": 1, \"dash\": \"dash\"},\n        opacity=0.35,\n    )\n\n# Vertical bracket line marking the eye height span at t=0.5 UI\nfig.add_shape(type=\"line\", x0=0.5, x1=0.5, y0=mean_low, y1=mean_high, line={\"color\": ACCENT, \"width\": 2, \"dash\": \"dot\"})\n\n# Horizontal bracket line marking the eye width span at y=0.5 V\nfig.add_shape(\n    type=\"line\", x0=eye_open_start, x1=eye_open_end, y0=0.5, y1=0.5, line={\"color\": ACCENT, \"width\": 2, \"dash\": \"dot\"}\n)\n\n# Combined signal quality annotation at center of first eye\nfig.add_annotation(\n    x=0.5,\n    y=0.5,\n    text=f\"Eye Height: {eye_height:.2f} V<br>Eye Width:  {eye_width_ui:.2f} UI\",\n    showarrow=False,\n    font={\"size\": 10, \"color\": ACCENT, \"family\": \"monospace\"},\n    bgcolor=ELEVATED_BG,\n    bordercolor=ACCENT,\n    borderwidth=1,\n    borderpad=6,\n    align=\"left\",\n)\n\n# Title\ntitle = \"eye-diagram-basic · python · plotly · anyplot.ai\"\n\nfig.update_layout(\n    autosize=False,\n    title={\n        \"text\": title,\n        \"font\": {\"size\": 16, \"color\": INK},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n        \"y\": 0.97,\n        \"yanchor\": \"top\",\n    },\n    xaxis={\n        \"title\": {\"text\": \"Time (UI)\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"tickvals\": [0, 0.5, 1.0, 1.5, 2.0],\n        \"gridcolor\": GRID,\n        \"gridwidth\": 1,\n        \"zeroline\": False,\n        \"showline\": True,\n        \"linecolor\": INK_SOFT,\n        \"linewidth\": 1,\n        \"mirror\": False,\n    },\n    yaxis={\n        \"title\": {\"text\": \"Voltage (V)\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"gridcolor\": GRID,\n        \"gridwidth\": 1,\n        \"zeroline\": False,\n        \"showline\": True,\n        \"linecolor\": INK_SOFT,\n        \"linewidth\": 1,\n        \"mirror\": False,\n    },\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK},\n    legend={\"bgcolor\": ELEVATED_BG, \"bordercolor\": INK_SOFT, \"borderwidth\": 1, \"font\": {\"color\": INK_SOFT}},\n    margin={\"l\": 80, \"r\": 60, \"t\": 80, \"b\": 65},\n)\n\n# Save — landscape 3200×1800 (800×450 scale=4)\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}