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.