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