{"spec_id":"eye-diagram-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\neye-diagram-basic: Signal Integrity Eye Diagram\nLibrary: plotnine 0.15.7 | Python 3.13.13\nQuality: 87/100 | Updated: 2026-06-18\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    after_stat,\n    annotate,\n    element_line,\n    element_rect,\n    element_text,\n    geom_bin2d,\n    ggplot,\n    labs,\n    scale_fill_gradient,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n    theme_void,\n)\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\"\n\n# Imprint palette — imprint_seq endpoints for density heatmap\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\nTRACE_LOW = IMPRINT_PALETTE[0]  # brand green — low trace density\nTRACE_HIGH = IMPRINT_PALETTE[2]  # blue — high trace density (imprint_seq)\nANN_HEIGHT = IMPRINT_PALETTE[1]  # lavender — eye height annotation\nANN_WIDTH = IMPRINT_PALETTE[3]  # ochre — eye width annotation\n\n# Data\nnp.random.seed(42)\nn_traces = 400\nsamples_per_ui = 150\nn_bits = 12\nui_window = 2\nnoise_sigma = 0.05\njitter_sigma = 0.03\n\nt_per_bit = np.linspace(0, 1, samples_per_ui, endpoint=False)\nt_full = np.concatenate([t_per_bit + i for i in range(n_bits)])\n\nall_time = []\nall_voltage = []\n\nfor _ in range(n_traces):\n    bits = np.random.randint(0, 2, n_bits)\n    signal = np.repeat(bits.astype(float), samples_per_ui)\n\n    # Smooth transitions with moving-average kernel\n    kernel_len = samples_per_ui // 4\n    kernel = np.ones(kernel_len) / kernel_len\n    signal = np.convolve(signal, kernel, mode=\"same\")\n\n    signal += np.random.normal(0, noise_sigma, len(signal))\n\n    # Per-bit jitter + sub-sample dither to prevent bin aliasing\n    jittered_t = t_full.copy()\n    for b in range(n_bits):\n        start = b * samples_per_ui\n        end = (b + 1) * samples_per_ui\n        jittered_t[start:end] += np.random.normal(0, jitter_sigma)\n    jittered_t += np.random.uniform(-0.5 / samples_per_ui, 0.5 / samples_per_ui, len(jittered_t))\n\n    # Overlay 2-UI windows\n    for b in range(n_bits - ui_window):\n        start = b * samples_per_ui\n        end = (b + ui_window) * samples_per_ui\n        seg_t = jittered_t[start:end] - jittered_t[start]\n        seg_v = signal[start:end]\n        all_time.extend(seg_t)\n        all_voltage.extend(seg_v)\n\ndf = pd.DataFrame({\"time\": np.array(all_time), \"voltage\": np.array(all_voltage)})\n\n# Eye height: sample vertical opening at t=0.5 UI\neye_center_t = 0.5\ncenter_mask = (df[\"time\"] > eye_center_t - 0.05) & (df[\"time\"] < eye_center_t + 0.05)\ncenter_voltages = df.loc[center_mask, \"voltage\"]\nupper_rail = center_voltages[center_voltages > 0.5]\nlower_rail = center_voltages[center_voltages <= 0.5]\neye_top = upper_rail.quantile(0.10)\neye_bot = lower_rail.quantile(0.90)\neye_height_val = eye_top - eye_bot\n\n# Eye width: find sparse transition zone via mid-voltage histogram\nmid_mask = (df[\"voltage\"] > 0.3) & (df[\"voltage\"] < 0.7)\nmid_times = df.loc[mid_mask, \"time\"]\ncounts, bin_edges = np.histogram(mid_times[(mid_times > 0) & (mid_times < 1)], bins=100)\nbin_centers = (bin_edges[:-1] + bin_edges[1:]) / 2\nthreshold = counts.max() * 0.1\nsparse_bins = bin_centers[counts < threshold]\nleft_edge = sparse_bins[sparse_bins < 0.5].min() if (sparse_bins < 0.5).any() else 0.15\nright_edge = sparse_bins[sparse_bins > 0.5].max() if (sparse_bins > 0.5).any() else 0.85\neye_width_val = right_edge - left_edge\n\n# Title — count chars and scale font size if longer than 67-char baseline\ntitle = \"eye-diagram-basic · python · plotnine · anyplot.ai\"\nn = len(title)\ntitle_fs = max(8, round(12 * (67 / n if n > 67 else 1.0)))\n\n# Plot\nplot = (\n    ggplot(df, aes(x=\"time\", y=\"voltage\"))\n    + geom_bin2d(aes(fill=after_stat(\"count\")), bins=(160, 100), alpha=0.9)\n    + scale_fill_gradient(low=TRACE_LOW, high=TRACE_HIGH, name=\"Trace\\nDensity\")\n    + scale_x_continuous(expand=(0, 0), breaks=[0, 0.5, 1.0, 1.5, 2.0], name=\"Time (UI)\")\n    + scale_y_continuous(expand=(0, 0.02), breaks=[0, 0.5, 1.0], name=\"Voltage (V)\")\n    + annotate(\n        \"segment\",\n        x=eye_center_t,\n        xend=eye_center_t,\n        y=eye_bot,\n        yend=eye_top,\n        color=ANN_HEIGHT,\n        size=0.7,\n        linetype=\"dashed\",\n    )\n    + annotate(\n        \"text\",\n        x=eye_center_t + 0.04,\n        y=0.65,\n        label=f\"Eye Height\\n{eye_height_val:.2f} V\",\n        color=ANN_HEIGHT,\n        size=5,\n        ha=\"left\",\n        va=\"center\",\n    )\n    + annotate(\"segment\", x=left_edge, xend=right_edge, y=0.5, yend=0.5, color=ANN_WIDTH, size=0.7, linetype=\"dashed\")\n    + annotate(\n        \"text\",\n        x=(left_edge + right_edge) / 2,\n        y=0.35,\n        label=f\"Eye Width\\n{eye_width_val:.2f} UI\",\n        color=ANN_WIDTH,\n        size=5,\n        ha=\"center\",\n        va=\"center\",\n    )\n    + labs(title=title)\n    + theme_void()\n    + theme(\n        figure_size=(8, 4.5),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        text=element_text(size=7, color=INK_SOFT),\n        axis_title=element_text(size=10, color=INK, margin={\"r\": 12, \"t\": 12}),\n        axis_text=element_text(size=8, color=INK_SOFT),\n        axis_text_x=element_text(margin={\"t\": 6}),\n        axis_text_y=element_text(margin={\"r\": 6}),\n        axis_line=element_line(color=INK_SOFT, size=0.3),\n        axis_ticks=element_line(color=INK_SOFT, size=0.2),\n        plot_title=element_text(size=title_fs, color=INK, weight=\"bold\", margin={\"b\": 12}),\n        legend_text=element_text(size=8, color=INK_SOFT),\n        legend_title=element_text(size=8, color=INK),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        plot_margin=0.02,\n    )\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=8, height=4.5, units=\"in\", verbose=False)\n"}