Oscilloscope rolling trace

A scope shows a fixed-width time window, not the whole run: as new samples arrive the trace shifts left and the oldest samples fall off the left edge entirely, rather than the x axis growing to hold everything ever captured (contrast the acquisition-pattern gallery’s growing-x-axis example). A collections.deque(maxlen=...) is the natural buffer for this – append the newest samples, let the oldest ones drop on their own.

The code below is exactly what you’d write against the real plotpress.qt.LiveArtist: a callback that receives whatever new samples came in since the last tick and pushes them to the plot, fed by a loop simulating an instrument. Only read_from_scope() is meant to be replaced, with your own driver call.

plot 01 oscilloscope rolling trace
from collections import deque

import numpy as np
import plotpress


WINDOW = 200   # samples visible at once

fig, ax = plotpress.subplots(figsize=(7, 4))
scope = LiveArtist(ax, color="#39d353", linewidth=1.2)
t_buf, v_buf = deque(maxlen=WINDOW), deque(maxlen=WINDOW)


def on_new_samples(ts, vs):
    """Called once per acquisition tick with whatever new samples came in
    since the last one -- how most scope drivers actually hand off data (a
    buffer read), not literally one sample at a time.
    """
    t_buf.extend(ts)
    v_buf.extend(vs)
    scope.update(np.array(t_buf), np.array(v_buf))
    ax.set_facecolor("#0b1a0b")
    ax.set_ylim(-1.5, 1.5)
    ax.set_xlabel("time (s)"); ax.set_ylabel("voltage (V)")
    ax.set_title("60 Hz line pickup + 900 Hz ringing")
    fig.tight_layout()


# ---------------------------------------------------------------------------
# Data acquisition -- replace this with your own instrument driver. Every-
# thing above only needs (ts, vs) handed to on_new_samples() as they arrive.
# ---------------------------------------------------------------------------
rng = np.random.default_rng(1)
DT = 0.0005              # s between samples -- 2 kS/s
N_TICKS = 40
SAMPLES_PER_TICK = 12


def read_from_scope(t0):
    """Stand-in for a real scope's buffer read: SAMPLES_PER_TICK new
    (t, v) samples starting at t0.
    """
    ts = t0 + DT * np.arange(SAMPLES_PER_TICK)
    vs = (1.0 * np.sin(2 * np.pi * 60.0 * ts)
          + 0.15 * np.sin(2 * np.pi * 900.0 * ts)
          + 0.05 * rng.standard_normal(SAMPLES_PER_TICK))
    return ts, vs


t = 0.0
for _ in range(N_TICKS):
    ts, vs = read_from_scope(t)
    on_new_samples(ts, vs)
    t = ts[-1] + DT

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

Gallery generated by Sphinx-Gallery