{"spec_id":"eye-diagram-basic","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\neye-diagram-basic: Signal Integrity Eye Diagram\nLibrary: bokeh 3.9.1 | Python 3.13.14\nQuality: 90/100 | Updated: 2026-06-18\n\"\"\"\n\nimport os\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import BoxAnnotation, ColorBar, Label, LinearColorMapper, NumeralTickFormatter, Span\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\n# Theme tokens (Imprint palette — theme-adaptive chrome)\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\"\nBRAND = \"#009E73\"  # Imprint palette position 1 — first series / eye-opening highlight\n\n# Imprint sequential palette for continuous density (green → blue)\n_c0 = np.array([0x00, 0x9E, 0x73])  # #009E73\n_c1 = np.array([0x44, 0x67, 0xA3])  # #4467A3\nANYPLOT_SEQ256 = [\"#{:02X}{:02X}{:02X}\".format(*np.round(_c0 + (_c1 - _c0) * t / 255).astype(int)) for t in range(256)]\n\n# Data — simulate NRZ eye diagram\nnp.random.seed(42)\nn_traces = 400\nsamples_per_ui = 150\nn_bits = 3\ntotal_samples = samples_per_ui * n_bits\nnoise_sigma = 0.05\njitter_sigma = 0.03\n\nall_time = []\nall_voltage = []\n\nfor _ in range(n_traces):\n    bits = np.random.randint(0, 2, n_bits + 2)\n    signal = np.zeros(total_samples)\n\n    for i in range(n_bits):\n        prev_bit = bits[i]\n        curr_bit = bits[i + 1]\n        t_local = np.linspace(0, 1, samples_per_ui)\n        jitter = np.random.normal(0, jitter_sigma)\n\n        if prev_bit != curr_bit:\n            transition_point = 0.0 + jitter\n            steepness = 12\n            transition = 1 / (1 + np.exp(-steepness * (t_local - transition_point)))\n            if prev_bit > curr_bit:\n                segment = 1 - transition\n            else:\n                segment = transition\n        else:\n            segment = np.full(samples_per_ui, float(curr_bit))\n\n        signal[i * samples_per_ui : (i + 1) * samples_per_ui] = segment\n\n    signal += np.random.normal(0, noise_sigma, total_samples)\n\n    # Extract 2-UI window centered on the middle bit\n    start = samples_per_ui // 2\n    end = start + 2 * samples_per_ui\n    window_time = np.linspace(0, 2, end - start)\n    window_voltage = signal[start:end]\n\n    all_time.append(window_time)\n    all_voltage.append(window_voltage)\n\nall_time = np.array(all_time)\nall_voltage = np.array(all_voltage)\n\n# Build 2D histogram for density heatmap\ntime_bins = 300\nvoltage_bins = 200\ntime_edges = np.linspace(0, 2, time_bins + 1)\nvoltage_edges = np.linspace(-0.3, 1.3, voltage_bins + 1)\n\nhistogram, _, _ = np.histogram2d(all_time.ravel(), all_voltage.ravel(), bins=[time_edges, voltage_edges])\n\n# Log-scale for contrast; mask empty bins so background shows through\nhistogram = np.log1p(histogram).T\nhistogram = np.where(histogram > 0, histogram, np.nan)\n\n# Measure eye opening from the density data\nvoltage_centers = 0.5 * (voltage_edges[:-1] + voltage_edges[1:])\ntime_centers = 0.5 * (time_edges[:-1] + time_edges[1:])\n\n# Eye height — vertical slice at center UI\ncenter_col = time_bins // 2\ncenter_slice = np.where(np.isnan(histogram[:, center_col]), 0.0, histogram[:, center_col])\nthreshold = 0.3 * center_slice.max()\n\nmid_idx = voltage_bins // 2\nlow_mask = center_slice < threshold\nlow_indices = np.where(low_mask)[0]\neye_region = low_indices[(low_indices >= mid_idx - voltage_bins // 4) & (low_indices <= mid_idx + voltage_bins // 4)]\nlower_eye = voltage_centers[eye_region[0]] if len(eye_region) > 0 else voltage_centers[mid_idx - 10]\nupper_eye = voltage_centers[eye_region[-1]] if len(eye_region) > 0 else voltage_centers[mid_idx + 10]\neye_height = upper_eye - lower_eye\n\n# Eye width — horizontal slice at midpoint voltage\nmid_v_idx = np.searchsorted(voltage_centers, (lower_eye + upper_eye) / 2)\nmid_slice = np.where(np.isnan(histogram[mid_v_idx, :]), 0.0, histogram[mid_v_idx, :])\nlow_time_mask = mid_slice < threshold\n\ncenter_time_idx = time_bins // 2\nlow_time_indices = np.where(low_time_mask)[0]\neye_time_region = low_time_indices[\n    (low_time_indices >= center_time_idx - time_bins // 4) & (low_time_indices <= center_time_idx + time_bins // 4)\n]\neye_left = time_centers[eye_time_region[0]] if len(eye_time_region) > 0 else time_centers[center_time_idx - 20]\neye_right = time_centers[eye_time_region[-1]] if len(eye_time_region) > 0 else time_centers[center_time_idx + 20]\neye_width = eye_right - eye_left\n\n# Plot\nTITLE = \"eye-diagram-basic · python · bokeh · anyplot.ai\"\np = figure(\n    width=3200,\n    height=1800,\n    title=TITLE,\n    x_axis_label=\"Time (UI)\",\n    y_axis_label=\"Voltage (V)\",\n    x_range=(0, 2),\n    y_range=(-0.3, 1.3),\n    toolbar_location=None,\n    min_border_bottom=160,\n    min_border_left=180,\n    min_border_top=110,\n    min_border_right=80,\n)\n\ncolor_mapper = LinearColorMapper(\n    palette=ANYPLOT_SEQ256, low=float(np.nanmin(histogram)), high=float(np.nanmax(histogram)), nan_color=PAGE_BG\n)\n\np.image(image=[histogram], x=0, y=-0.3, dw=2, dh=1.6, color_mapper=color_mapper)\n\n# Reference lines at 0 V and 1 V signal levels\np.add_layout(\n    Span(location=0.0, dimension=\"width\", line_color=INK_SOFT, line_width=3, line_alpha=0.6, line_dash=\"dashed\")\n)\np.add_layout(\n    Span(location=1.0, dimension=\"width\", line_color=INK_SOFT, line_width=3, line_alpha=0.6, line_dash=\"dashed\")\n)\n\n# Eye opening highlight box\np.add_layout(\n    BoxAnnotation(\n        left=eye_left,\n        right=eye_right,\n        bottom=lower_eye,\n        top=upper_eye,\n        fill_color=BRAND,\n        fill_alpha=0.05,\n        line_color=BRAND,\n        line_width=3,\n        line_alpha=0.9,\n        line_dash=\"solid\",\n    )\n)\n\n# Eye height annotation — vertical line with end caps\ncap_w = 0.018\np.line(x=[1.0, 1.0], y=[lower_eye, upper_eye], line_color=BRAND, line_width=3, line_alpha=0.9)\nfor cap_y in [lower_eye, upper_eye]:\n    p.line(x=[1.0 - cap_w, 1.0 + cap_w], y=[cap_y, cap_y], line_color=BRAND, line_width=3, line_alpha=0.9)\n\n# Eye width annotation — horizontal line with end caps\neye_mid_v = (upper_eye + lower_eye) / 2\ncap_h = 0.018\np.line(x=[eye_left, eye_right], y=[eye_mid_v, eye_mid_v], line_color=BRAND, line_width=3, line_alpha=0.9)\nfor cap_x in [eye_left, eye_right]:\n    p.line(x=[cap_x, cap_x], y=[eye_mid_v - cap_h, eye_mid_v + cap_h], line_color=BRAND, line_width=3, line_alpha=0.9)\n\n# Eye measurement labels\np.add_layout(\n    Label(\n        x=1.04,\n        y=(upper_eye + lower_eye) / 2,\n        text=f\"Eye Height: {eye_height:.2f} V\",\n        text_color=BRAND,\n        text_font_size=\"26pt\",\n        text_font_style=\"bold\",\n        background_fill_color=ELEVATED_BG,\n        background_fill_alpha=0.90,\n    )\n)\np.add_layout(\n    Label(\n        x=(eye_left + eye_right) / 2,\n        y=lower_eye - 0.10,\n        text=f\"Eye Width: {eye_width:.2f} UI\",\n        text_color=BRAND,\n        text_font_size=\"26pt\",\n        text_font_style=\"bold\",\n        text_align=\"center\",\n        background_fill_color=ELEVATED_BG,\n        background_fill_alpha=0.90,\n    )\n)\n\n# Signal level labels\np.add_layout(\n    Label(\n        x=0.06,\n        y=1.07,\n        text=\"Logic 1 (1.0 V)\",\n        text_color=INK,\n        text_font_size=\"26pt\",\n        background_fill_color=ELEVATED_BG,\n        background_fill_alpha=0.85,\n    )\n)\np.add_layout(\n    Label(\n        x=0.06,\n        y=-0.22,\n        text=\"Logic 0 (0.0 V)\",\n        text_color=INK,\n        text_font_size=\"26pt\",\n        background_fill_color=ELEVATED_BG,\n        background_fill_alpha=0.85,\n    )\n)\n\n# ColorBar — larger fonts to address the cramped right-side weakness\ncolor_bar = ColorBar(\n    color_mapper=color_mapper,\n    title=\"Log Density\",\n    title_text_font_size=\"30pt\",\n    title_text_color=INK,\n    title_standoff=26,\n    label_standoff=20,\n    width=70,\n    location=(0, 0),\n    major_label_text_font_size=\"26pt\",\n    major_label_text_color=INK_SOFT,\n    padding=50,\n    background_fill_color=PAGE_BG,\n    bar_line_color=INK_SOFT,\n)\np.add_layout(color_bar, \"right\")\n\n# Style — sizes per bokeh.md for 3200×1800 canvas\np.title.text_font_size = \"50pt\"\np.title.text_font_style = \"normal\"\np.title.text_color = INK\np.xaxis.axis_label_text_font_size = \"42pt\"\np.yaxis.axis_label_text_font_size = \"42pt\"\np.xaxis.major_label_text_font_size = \"34pt\"\np.yaxis.major_label_text_font_size = \"34pt\"\np.xaxis.axis_label_text_color = INK\np.yaxis.axis_label_text_color = INK\np.xaxis.major_label_text_color = INK_SOFT\np.yaxis.major_label_text_color = INK_SOFT\np.xaxis.axis_line_color = INK_SOFT\np.yaxis.axis_line_color = INK_SOFT\np.xaxis.major_tick_line_color = INK_SOFT\np.yaxis.major_tick_line_color = INK_SOFT\np.xaxis.minor_tick_line_color = None\np.yaxis.minor_tick_line_color = None\np.xaxis.formatter = NumeralTickFormatter(format=\"0.0\")\np.yaxis.formatter = NumeralTickFormatter(format=\"0.0\")\n\n# Remove grid lines for clean heatmap aesthetic\np.xgrid.grid_line_alpha = 0\np.ygrid.grid_line_alpha = 0\n\n# Theme-adaptive chrome\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = INK_SOFT\n\n# Save interactive HTML\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot with headless Chrome — Selenium 4 + CDP viewport override for exact dimensions\nW, H = 3200, 1800\nopts = Options()\nfor arg in (\n    \"--headless=new\",\n    \"--no-sandbox\",\n    \"--disable-dev-shm-usage\",\n    \"--disable-gpu\",\n    \"--hide-scrollbars\",\n    \"--force-device-scale-factor=1\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\n# CDP override sets the viewport (content area) to exactly W×H, bypassing\n# the ~143 px browser-chrome height that --window-size includes.\ndriver.execute_cdp_cmd(\n    \"Emulation.setDeviceMetricsOverride\", {\"width\": W, \"height\": H, \"deviceScaleFactor\": 1, \"mobile\": False}\n)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ntime.sleep(3)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}