{"spec_id":"eye-diagram-basic","library":"altair","language":"python","code":"\"\"\" anyplot.ai\neye-diagram-basic: Signal Integrity Eye Diagram\nLibrary: altair 6.2.1 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-06-18\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent this file from shadowing the installed altair package on sys.path\n_here = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p) != _here]\ndel _here\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\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\"\nANNOTATION_COLOR = \"#AE3030\"  # Imprint matte-red for measurement indicators\n\n# Data\nnp.random.seed(42)\n\nn_traces = 300\nsamples_per_ui = 200\namplitude = 1.0\nnoise_sigma = 0.05 * amplitude\njitter_sigma = 0.03\n\nn_bits = n_traces + 4\nbits = np.random.randint(0, 2, n_bits)\n\nsamples_per_bit = samples_per_ui\ntotal_signal_len = n_bits * samples_per_bit\nt_full = np.arange(total_signal_len) / samples_per_bit\n\nsignal_full = np.zeros(total_signal_len)\nfor i in range(total_signal_len):\n    bit_idx = int(t_full[i])\n    if bit_idx >= n_bits:\n        bit_idx = n_bits - 1\n    frac = t_full[i] - bit_idx\n\n    current_level = bits[bit_idx] * amplitude\n    prev_level = bits[bit_idx - 1] * amplitude if bit_idx > 0 else bits[0] * amplitude\n\n    transition_width = 0.12\n    blend = 1.0 / (1.0 + np.exp(-14 * (frac - transition_width) / transition_width))\n    signal_full[i] = prev_level + (current_level - prev_level) * blend\n\nsignal_full += np.random.normal(0, noise_sigma, total_signal_len)\n\nall_time = []\nall_voltage = []\nwindow_samples = 2 * samples_per_ui\n\nfor trace in range(n_traces):\n    start_bit = trace + 1\n    start_sample = start_bit * samples_per_bit\n    end_sample = start_sample + window_samples\n\n    if end_sample > total_signal_len:\n        break\n\n    jitter_offset = np.random.normal(0, jitter_sigma)\n    trace_time = np.linspace(0, 2, window_samples) + jitter_offset\n    trace_voltage = signal_full[start_sample:end_sample]\n\n    all_time.extend(trace_time.tolist())\n    all_voltage.extend(trace_voltage.tolist())\n\n# Pre-bin into 2D histogram for density heatmap\ntime_bins = 350\nvoltage_bins = 225\ntime_edges = np.linspace(-0.05, 2.05, time_bins + 1)\nvoltage_edges = np.linspace(-0.2, 1.2, voltage_bins + 1)\n\nhist, _, _ = np.histogram2d(all_time, all_voltage, bins=[time_edges, voltage_edges])\n\n# Use exact bin edges (x/x2/y/y2) for seamless tiling — no pixel arithmetic needed\nrows = []\nfor i in range(time_bins):\n    for j in range(voltage_bins):\n        if hist[i, j] > 0:\n            rows.append(\n                {\n                    \"t_left\": round(float(time_edges[i]), 5),\n                    \"t_right\": round(float(time_edges[i + 1]), 5),\n                    \"v_bottom\": round(float(voltage_edges[j]), 5),\n                    \"v_top\": round(float(voltage_edges[j + 1]), 5),\n                    \"density\": float(hist[i, j]),\n                }\n            )\n\ndf = pd.DataFrame(rows)\ndf[\"log_density\"] = np.log1p(df[\"density\"])\n\n# Eye measurements for annotations\nall_time_arr = np.array(all_time)\nall_voltage_arr = np.array(all_voltage)\nmid_time = 1.0\neye_center_v = amplitude / 2\n\nmid_mask = (all_time_arr > 0.9) & (all_time_arr < 1.1)\nmid_voltages = all_voltage_arr[mid_mask]\nhigh_voltages = mid_voltages[mid_voltages > eye_center_v]\nlow_voltages = mid_voltages[mid_voltages <= eye_center_v]\neye_top = float(np.percentile(high_voltages, 5)) if len(high_voltages) > 0 else 0.9\neye_bottom = float(np.percentile(low_voltages, 95)) if len(low_voltages) > 0 else 0.1\neye_height_val = eye_top - eye_bottom\n\nmid_v_mask = (all_voltage_arr > 0.4) & (all_voltage_arr < 0.6)\ntransition_times = all_time_arr[mid_v_mask]\nleft_transitions = transition_times[transition_times < 1.0]\nright_transitions = transition_times[transition_times >= 1.0]\neye_left = float(np.percentile(left_transitions, 95)) if len(left_transitions) > 0 else 0.3\neye_right = float(np.percentile(right_transitions, 5)) if len(right_transitions) > 0 else 1.7\neye_width_val = eye_right - eye_left\n\n# Title scaling: 67-char baseline at 16px default for altair\ntitle_str = \"eye-diagram-basic · python · altair · anyplot.ai\"\ntitle_fontsize = max(11, round(16 * 67 / max(len(title_str), 67)))\n\n# Heatmap using exact bin edges (x2/y2) — eliminates tiling gaps\nheatmap = (\n    alt.Chart(df)\n    .mark_rect()\n    .encode(\n        x=alt.X(\"t_left:Q\", title=\"Time (UI)\", scale=alt.Scale(domain=[0, 2])),\n        x2=alt.X2(\"t_right:Q\"),\n        y=alt.Y(\"v_bottom:Q\", title=\"Voltage (V)\", scale=alt.Scale(domain=[-0.15, 1.15])),\n        y2=alt.Y2(\"v_top:Q\"),\n        color=alt.Color(\n            \"log_density:Q\",\n            scale=alt.Scale(range=[\"#009E73\", \"#4467A3\"]),  # Imprint sequential\n            legend=alt.Legend(title=\"Log Density\", gradientLength=200, orient=\"right\"),\n        ),\n        tooltip=[\n            alt.Tooltip(\"t_left:Q\", title=\"Time (UI)\", format=\".3f\"),\n            alt.Tooltip(\"v_bottom:Q\", title=\"Voltage (V)\", format=\".3f\"),\n            alt.Tooltip(\"density:Q\", title=\"Trace Count\", format=\".0f\"),\n        ],\n    )\n)\n\n# Eye height annotation (vertical dashed line)\nheight_rule = (\n    alt.Chart(pd.DataFrame([{\"x\": mid_time + 0.05, \"y\": eye_bottom, \"y2\": eye_top}]))\n    .mark_rule(color=ANNOTATION_COLOR, strokeWidth=2.5, strokeDash=[8, 4])\n    .encode(x=\"x:Q\", y=\"y:Q\", y2=\"y2:Q\")\n)\n\n# Eye width annotation (horizontal dashed line)\nwidth_rule = (\n    alt.Chart(pd.DataFrame([{\"x\": eye_left, \"x2\": eye_right, \"y\": eye_center_v}]))\n    .mark_rule(color=ANNOTATION_COLOR, strokeWidth=2.5, strokeDash=[8, 4])\n    .encode(x=\"x:Q\", x2=\"x2:Q\", y=\"y:Q\")\n)\n\n# Measurement labels\nann_labels = pd.DataFrame(\n    [\n        {\"x\": mid_time + 0.22, \"y\": eye_center_v + 0.19, \"text\": f\"Eye Height: {eye_height_val:.3f} V\"},\n        {\"x\": (eye_left + eye_right) / 2, \"y\": eye_center_v - 0.16, \"text\": f\"Eye Width: {eye_width_val:.2f} UI\"},\n    ]\n)\nlabels = (\n    alt.Chart(ann_labels)\n    .mark_text(color=ANNOTATION_COLOR, fontSize=12, fontWeight=\"bold\", align=\"center\", baseline=\"middle\")\n    .encode(x=\"x:Q\", y=\"y:Q\", text=\"text:N\")\n)\n\n# Diamond markers at eye opening corners\neye_markers = pd.DataFrame(\n    [\n        {\"x\": mid_time, \"y\": eye_top},\n        {\"x\": mid_time, \"y\": eye_bottom},\n        {\"x\": eye_left, \"y\": eye_center_v},\n        {\"x\": eye_right, \"y\": eye_center_v},\n    ]\n)\nmarkers = (\n    alt.Chart(eye_markers)\n    .mark_point(shape=\"diamond\", color=ANNOTATION_COLOR, size=120, filled=True, opacity=0.9)\n    .encode(x=\"x:Q\", y=\"y:Q\")\n)\n\n# Compose layers and configure\nchart = (\n    (heatmap + height_rule + width_rule + labels + markers)\n    .properties(\n        width=620,\n        height=320,\n        background=PAGE_BG,\n        title=alt.Title(\n            title_str,\n            fontSize=title_fontsize,\n            fontWeight=500,\n            color=INK,\n            subtitle=\"NRZ signal — 300 traces · 5% noise · 3% jitter\",\n            subtitleFontSize=13,\n            subtitleColor=INK_SOFT,\n        ),\n    )\n    .configure_view(fill=PAGE_BG, stroke=INK_SOFT)\n    .configure_axis(\n        labelFontSize=10,\n        titleFontSize=12,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        tickColor=INK_SOFT,\n        domainColor=INK_SOFT,\n        grid=False,\n    )\n    .configure_legend(\n        fillColor=ELEVATED_BG,\n        strokeColor=INK_SOFT,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        labelFontSize=12,\n        titleFontSize=12,\n    )\n    .configure_title(color=INK)\n)\n\n# Save PNG\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\n\n# Pad to exact 3200×1800\nTW, TH = 3200, 1800\n_img = Image.open(f\"plot-{THEME}.png\").convert(\"RGB\")\n_w, _h = _img.size\nif _w > TW or _h > TH:\n    raise SystemExit(\n        f\"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. \"\n        f\"Shrink chart .properties(width=, height=) values and re-render.\"\n    )\nif _w < TW or _h < TH:\n    _canvas = Image.new(\"RGB\", (TW, TH), PAGE_BG)\n    _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2))\n    _canvas.save(f\"plot-{THEME}.png\")\n\n# Save HTML\nchart.save(f\"plot-{THEME}.html\")\n"}