plotpress Logo

Getting started

  • Installation
  • Usage
  • Performance

User guide

  • Plotting methods
  • Configuring axes
  • Figures and layout
  • Styling and colors
  • Saving and showing
  • Viewing figures
  • Interactive figures
  • Coordinate and pixel accuracy
  • How it works

Examples

  • Example gallery
  • Pairwise data
  • Statistical distributions
  • Gridded data
  • Multi-axes layout and annotation
  • Animation
  • Axes & figure manipulation
  • Secondary and inset axes
  • Building a figure across processes
  • Custom interactive JS
  • Seaborn-style distribution plots
  • Limitations
  • Figure layout
  • Grouping axes
  • Large-scale figures

Live plotting

  • Live streaming
  • Acquisition patterns
  • Lab instrument examples

Real applications

  • Real applications
    • Earth, ocean and atmosphere
    • Astronomy and space science
    • Medical imaging and clinical data
    • Biology, genomics and epidemiology
    • Chemistry and spectroscopy
    • Materials, surfaces and microscopy
    • Spectroscopy and wavefunctions
    • Readout and device maps
    • Coherence and noise
    • Gate calibration
    • Benchmarking and multi-qubit
    • Semiconductors and electronic test
    • Fluids, heat transfer and mechanical engineering
    • Acoustics, vibration and sonar
    • Energy and power systems
    • Transport, robotics and navigation
    • Manufacturing, reliability and quality
    • Computing, networks and machine learning
    • Finance, economics and risk
      • Earth, ocean and atmosphere
      • Astronomy and space science
      • Medical imaging and clinical data
      • Biology, genomics and epidemiology
      • Chemistry and spectroscopy
      • Materials, surfaces and microscopy
      • Spectroscopy and wavefunctions
      • Readout and device maps
      • Coherence and noise
      • Gate calibration
      • Benchmarking and multi-qubit
      • Semiconductors and electronic test
      • Fluids, heat transfer and mechanical engineering
      • Acoustics, vibration and sonar
      • Energy and power systems
      • Transport, robotics and navigation
      • Manufacturing, reliability and quality
      • Computing, networks and machine learning
      • Finance, economics and risk

Reference

  • API reference

Limitations

  • Limitations
plotpress
  • Real applications
  • Semiconductors and electronic test
  • Serial-link eye diagram
  • Edit on GitHub

Note

Go to the end to download the full example code.

Serial-link eye diagram

Twelve thousand unit intervals of a 10 Gb/s serial link, all folded onto the same two-bit window. The eye diagram is a persistence display: it is not a plot of a signal but a plot of every signal transition superimposed, and what matters is the size of the hole in the middle, because that hole is the margin the receiver has to sample in.

Overlaying twelve thousand traces as lines would be both slow and wrong – the result saturates to a solid block long before the statistics have converged, and the density information, which is the entire content, is thrown away. Binning the samples into a 2-D histogram keeps it: bright regions are where the waveform spends most of its time, and the faint haze at the crossings is the jitter distribution.

The counts span several decades between the rails and the rare worst-case excursions, so the histogram is coloured through a LogNorm. On a linear ramp the two rails saturate and everything else – transitions, jitter tails, the occasional runt bit that actually causes errors – disappears into the background.

The measured eye opening is marked as a box: the horizontal extent is the timing margin and the vertical extent the voltage margin, which together are what a compliance mask is checked against.

plot 06 eye diagram

Live figure — pick a tool, then zoom, pan, point-pick or annotate. Nothing is active until a tool is selected.

View this figure’s Vega export ↗ — the raw JSON spec, rendered live by a real Vega engine.

View this figure’s Vega-Lite export ↗ — the raw JSON spec(s), rendered live by a real Vega-Lite engine.

import numpy as np
import polars as pl
import plotpress

rng = np.random.default_rng(1010)

UI = 100.0                                         # unit interval (ps) at 10 Gb/s
SPS = 40                                           # samples per unit interval
N_BITS = 12000
SWING = 0.40                                       # V, differential
RJ = 0.030                                         # random jitter, UI rms

bits = rng.integers(0, 2, N_BITS).astype(float) * 2.0 - 1.0

# One impulse per bit, then the channel's pulse response. The response is
# wider than a unit interval, so each bit smears into its neighbours -- that
# inter-symbol interference is what closes the eye horizontally.
impulses = np.zeros(N_BITS * SPS)
impulses[::SPS] = bits
span = np.arange(-2 * SPS, 2 * SPS + 1) / SPS      # in UI
beta = 0.6
with np.errstate(divide="ignore", invalid="ignore"):
    pulse = np.sinc(span) * np.cos(np.pi * beta * span) / (1.0 - (2 * beta * span) ** 2)
pulse = np.nan_to_num(pulse, nan=np.pi / 4.0)
wave = np.convolve(impulses, pulse, mode="same") * SWING
wave += rng.normal(0.0, 0.008, wave.size)          # receiver noise

# Fold every bit onto the same two-UI window.
centres = np.arange(3, N_BITS - 3) * SPS
offsets = np.arange(-SPS, SPS)
v_all = wave[centres[:, None] + offsets[None, :]]

# Random jitter is a *time* shift, so it displaces the whole trace along x.
# Applying it to the sample values instead would leave every trace sampled at
# the same handful of phases -- and a histogram binned finer than that spacing
# then shows vertical stripes of empty bins rather than a continuous eye.
phase = offsets / SPS * UI
t_all = phase[None, :] + rng.normal(0.0, RJ * UI, (centres.size, 1))

# One row per folded sample -- the shape a scope's own eye-diagram acquisition
# is in, before it is binned into the persistence histogram.
samples = pl.DataFrame({"t": t_all.ravel(), "v": v_all.ravel()})
t_all = samples["t"].to_numpy()
v_all = samples["v"].to_numpy()

fig, ax = plotpress.subplots(figsize=(8.6, 5.6))
counts, im = ax.hist2d(t_all, v_all, bins=(360, 260), cmap="inferno",
                       norm=plotpress.LogNorm(vmin=1.0))
bar = fig.colorbar(im, ax=ax)
bar.set_title("samples\nper bin")

# Eye opening at the sampling instant: the gap between the 1% worst high sample
# and the 1% worst low one, over the window where that gap stays open.
HALF_WINDOW = 22.0                                 # ps either side of centre
mid = np.abs(t_all) < HALF_WINDOW
upper = np.percentile(v_all[mid & (v_all > 0)], 1.0)
lower = np.percentile(v_all[mid & (v_all < 0)], 99.0)
ax.plot([-HALF_WINDOW, HALF_WINDOW, HALF_WINDOW, -HALF_WINDOW, -HALF_WINDOW],
        [lower, lower, upper, upper, lower],
        color="#00ff88", linewidth=1.8, linestyle="--")
ax.text(0.0, 0.0,
        f"{2 * HALF_WINDOW / UI * 100:.0f}% UI x {1e3 * (upper - lower):.0f} mV",
        ha="center", color="#0a7d3f", fontsize=10)

ax.set_xlim(-UI, UI)
ax.set_xlabel("time within the unit interval (ps)")
ax.set_ylabel("differential voltage (V)")
ax.set_title(f"10 Gb/s eye: {N_BITS} overlaid bits, binned and log-scaled")
fig.tight_layout()

Total running time of the script: (0 minutes 0.325 seconds)

Download Jupyter notebook: plot_06_eye_diagram.ipynb

Download Python source code: plot_06_eye_diagram.py

Download zipped: plot_06_eye_diagram.zip

Gallery generated by Sphinx-Gallery

Previous Next

© Copyright 2026, plotpress contributors.

Built with Sphinx using a theme provided by Read the Docs.