Realtime scroll

A streaming chart: each frame appends a sample to a capped buffer and re-pins the x axis to a window that ends at “now”, so the plot scrolls as data arrives. The load-bearing detail is the condition on the axis limits — ImPlotCond.Always re-applies the range every frame, where the default ImPlotCond.Once would set it on the first frame and then let the user pan.

g_t += DT                                // advance the clock
g_xs |> push(g_t); g_fast |> push(...)   // append one sample
if (length(g_xs) > MAX_POINTS) {         // drop the oldest -> fixed-width window
    g_xs |> erase(0); g_fast |> erase(0)
}
plot(SCROLL, (title = "scrolling signals", size = float2(-1.0f, 600.0f), flags = ImPlotFlags.None)) {
    setup_axes("time (s)", "value")
    setup_axes_limits(g_t - HISTORY, g_t, -1.2lf, 1.2lf, ImPlotCond.Always)
    plot_line("fast", g_xs, g_fast)
}

Source: examples/tutorial/realtime_scroll.das.

 1options gen2
 2options _comment_hygiene = true
 3
 4require imgui/imgui_harness
 5require imgui/imgui_containers_builtin
 6require imgui/imgui_widgets_builtin
 7require imgui/imgui_implot_boost_v2
 8require implot
 9require math
10
11// =============================================================================
12// TUTORIAL: realtime_scroll — a rolling buffer plotted against a moving x window.
13//
14//   Each frame appends a sample to a capped buffer (oldest dropped past MAX_POINTS)
15//   and re-pins the x axis to [t - HISTORY, t] with ImPlotCond.Always, so the plot
16//   scrolls as data streams in. The y axis stays locked.
17//
18//   setup_axes_limits(..., ImPlotCond.Always)  — re-apply the range EVERY frame
19//                                                 (vs Once, which lets the user pan).
20//
21// STANDALONE: daslang.exe modules/dasImguiImplot/examples/tutorial/realtime_scroll.das
22// LIVE:       daslang-live modules/dasImguiImplot/examples/tutorial/realtime_scroll.das
23// =============================================================================
24
25let HISTORY = 5.0lf     // seconds of data kept on screen
26let DT = 0.1lf          // sim seconds advanced per frame
27let MAX_POINTS = 70     // buffer cap (~ HISTORY / DT + margin)
28
29var g_ctx : ImPlotContext?
30var g_t : double = 0.0lf
31var g_xs : array<double>
32var g_fast : array<double>
33var g_slow : array<double>
34
35[export]
36def init() {
37    harness_init("dasImguiImplot — realtime_scroll", 1100, 720)
38    g_ctx = implot::CreateContext()
39}
40
41[export]
42def update() {
43    if (!harness_begin_frame()) return
44    harness_new_frame()
45
46    // Advance the clock and append one sample of each signal.
47    g_t += DT
48    g_xs |> push(g_t)
49    g_fast |> push(double(sin(float(g_t) * 2.0f)))
50    g_slow |> push(double(sin(float(g_t) * 2.0f + 1.5f)) * 0.6lf)
51    // Drop the oldest sample once the buffer is full — a fixed-width rolling window.
52    if (length(g_xs) > MAX_POINTS) {
53        g_xs |> erase(0)
54        g_fast |> erase(0)
55        g_slow |> erase(0)
56    }
57
58    SetNextWindowPos(float2(20.0, 20.0), ImGuiCond.Always)
59    SetNextWindowSize(float2(1060.0, 680.0), ImGuiCond.Always)
60    window(PLOT_WIN, (text = "realtime", closable = false,
61                      flags = ImGuiWindowFlags.None)) {
62        text("A rolling buffer: each frame appends a sample and the x axis is pinned to [t-HISTORY, t].")
63        plot(SCROLL, (title = "scrolling signals", size = float2(-1.0f, 600.0f),
64                      flags = ImPlotFlags.None)) {
65            setup_axes("time (s)", "value")
66            // Re-pin the x window every frame so it scrolls; lock y.
67            setup_axes_limits(g_t - HISTORY, g_t, -1.2lf, 1.2lf, ImPlotCond.Always)
68            plot_line("fast", g_xs, g_fast)
69            plot_line("slow", g_xs, g_slow)
70        }
71    }
72
73    harness_end_frame()
74}
75
76[export]
77def shutdown() {
78    if (g_ctx != null) {
79        DestroyContext(g_ctx)
80    }
81    harness_shutdown()
82}
83
84[export]
85def main() {
86    init()
87    while (!exit_requested()) {
88        update()
89    }
90    shutdown()
91}

Walkthrough

There is nothing to drive here — the chart streams on its own. The recording narrates over the live animation and asserts the window genuinely advances (x_min climbs over the narration), so a frozen plot would fail it.

The rolling buffer

The buffer is three parallel array<double> (one x, two y) trimmed together: push appends the new sample, and once the length exceeds MAX_POINTS an erase(0) drops the oldest. MAX_POINTS is sized at roughly HISTORY / DT so the buffer holds exactly the visible window.

Pinning the x window

setup_axes_limits(g_t - HISTORY, g_t, …, ImPlotCond.Always) keeps the x range a fixed HISTORY-wide window whose right edge is the current time g_t. Because g_t advances every frame, the left edge climbs — which is what the test_realtime_scroll regression checks: it waits for x_min to pass 0.5, proving the window is genuinely scrolling rather than static.