Plot
Four read-only sample plots. Two array-form widgets take a
per-frame array<float> and copy it synchronously; two
lambda-form widgets call back once per sample, skipping the
backing array entirely.
plot_lines(IDENT, "title", values, scale_min, scale_max, size)
plot_histogram(IDENT, "title", values, scale_min, scale_max, size)
plot_lines_getter(IDENT, "title", count,
@(idx : int) : float => ...,
overlay, scale_min, scale_max, size)
plot_histogram_getter(IDENT, "title", count,
@(idx : int) : float => ...,
overlay, scale_min, scale_max, size)
All four share PlotState — title + samples round-trip into the
snapshot so visual regression tests can diff the data.
Source: examples/tutorial/plot.das.
Walkthrough
1options gen2
2
3require math
4require imgui
5require imgui_app
6require opengl/opengl_boost
7require live/glfw_live
8require live/live_api
9require live/live_commands
10require live/live_vars
11require live_host
12require imgui/imgui_live
13require imgui/imgui_boost_runtime
14require imgui/imgui_boost_v2
15require imgui/imgui_widgets_builtin
16require imgui/imgui_containers_builtin
17require imgui/imgui_visual_aids
18
19// =============================================================================
20// TUTORIAL: plot — four read-only sample-plot widgets.
21//
22// plot_lines(IDENT, "title", values, scale_min, scale_max, size)
23// plot_histogram(IDENT, "title", values, scale_min, scale_max, size)
24// Array form — pass a per-frame array<float>. ImGui copies it
25// synchronously. Pass scale_min = scale_max = FLT_MAX to auto-fit
26// bounds to the data; explicit bounds avoid the per-frame jitter.
27//
28// plot_lines_getter(IDENT, "title", count, getter, overlay,
29// scale_min, scale_max, size)
30// plot_histogram_getter(IDENT, "title", count, getter, overlay,
31// scale_min, scale_max, size)
32// Lambda form — callback (idx : int) : float fires once per sample
33// synchronously inside the C call. Use for synthesized signals or
34// virtualized buffers (avoids building a backing array).
35//
36// All four share PlotState — title + sample telemetry round-trips into
37// the snapshot payload for visual regression testing.
38//
39// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/plot.das
40// LIVE: daslang-live modules/dasImgui/examples/tutorial/plot.das
41// =============================================================================
42
43let SAMPLES = 64
44let FLT_MAX = 3.40282347e38f
45
46var private g_phase : float = 0.0f
47
48def fill_sine(var arr : array<float>; phase : float; n : int) {
49 arr |> resize(n)
50 for (i in range(n)) {
51 arr[i] = sin(float(i) * 0.2f + phase)
52 }
53}
54
55def fill_bars(var arr : array<float>) {
56 arr |> resize(8)
57 for (i in range(8)) {
58 arr[i] = float(i) * 0.15f + 0.1f
59 }
60}
61
62[export]
63def init() {
64 live_create_window("dasImgui plot tutorial", 800, 720)
65 live_imgui_init(live_window)
66 let io & = unsafe(GetIO())
67 GetStyle().FontScaleMain = 1.4
68}
69
70[export]
71def update() {
72 if (!live_begin_frame()) return
73 begin_frame()
74
75 ImGui_ImplGlfw_NewFrame()
76 apply_synth_io_override()
77 NewFrame()
78
79 g_phase += 0.05f
80 var sine_data : array<float>
81 fill_sine(sine_data, g_phase, SAMPLES)
82 var bar_data : array<float>
83 fill_bars(bar_data)
84
85 SetNextWindowPos(ImVec2(20.0f, 20.0f), ImGuiCond.Always)
86 SetNextWindowSize(ImVec2(760.0f, 680.0f), ImGuiCond.Always)
87 window(PL_WIN, (text = "plot tutorial", closable = false,
88 flags = ImGuiWindowFlags.None)) {
89
90 text("Read-only sample plots - PlotState records title + samples each frame.")
91 text(PL_HINT, (text = "Array form (lines / histogram) vs lambda form (lines_getter / histogram_getter)."))
92 separator()
93
94 // ---- Stage 1: plot_lines (array) — sine wave with explicit bounds ----
95 plot_lines(PL_LINES, "Sine wave", sine_data, -1.2f, 1.2f, float2(0.0f, 80.0f))
96 spacing()
97
98 // ---- Stage 2: plot_histogram (array) — fixed bars ----
99 plot_histogram(PL_HIST, "Histogram", bar_data, 0.0f, 1.5f, float2(0.0f, 80.0f))
100 spacing()
101
102 // ---- Stage 3: plot_lines_getter (lambda) — no backing array ----
103 plot_lines_getter(PL_LINES_G, "sin(i*0.1)", SAMPLES,
104 @(idx : int) : float => sin(float(idx) * 0.1f + g_phase),
105 "", -1.2f, 1.2f, float2(0.0f, 80.0f))
106 spacing()
107
108 // ---- Stage 4: plot_histogram_getter (lambda) ----
109 plot_histogram_getter(PL_HIST_G, "abs(sin(i*0.1))", SAMPLES,
110 @(idx : int) : float => abs(sin(float(idx) * 0.1f + g_phase)),
111 "", 0.0f, 1.2f, float2(0.0f, 80.0f))
112 }
113
114 end_of_frame()
115 Render()
116 var w, h : int
117 live_get_framebuffer_size(w, h)
118 glViewport(0, 0, w, h)
119 glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
120 glClear(GL_COLOR_BUFFER_BIT)
121 live_imgui_render()
122
123 live_end_frame()
124}
125
126[export]
127def shutdown() {
128 live_imgui_shutdown()
129 live_destroy_window()
130}
131
132[export]
133def main() {
134 init()
135 while (!exit_requested()) {
136 update()
137 }
138 shutdown()
139}
Requires
Already in the baseline boost layer:
imgui/imgui_widgets_builtin— both array and lambda rails.imgui/imgui_boost_runtime—PlotState.
Array form vs lambda form
Array (
plot_lines/plot_histogram) — pass avalues : array<float>. ImGui copies element-by-element inside the C call. Use when you already have a backing buffer (telemetry ring, captured samples, table column).Lambda (
*_getter) — passvalues_countand agetter : lambda<(idx : int) : float>. The callback fires once per sample, synchronously inside the C call. Use for synthesized signals (sine, noise, generated frequency response) or virtualized buffers where building an array would waste work.
Both forms are O(n) per frame — the choice is about whether you want to allocate the array or generate on the fly.
Auto-fit vs explicit bounds
scale_min and scale_max default to FLT_MAX (3.4e38), which
ImGui treats as “auto-fit to the data this frame.” That’s tempting,
but the bounds change every frame — the plot stretches/compresses
as the data range shifts, which reads as jitter:
// Auto-fit — bounds drift per frame:
plot_lines(LATENCY, "ms", samples) // FLT_MAX defaults
// Explicit bounds — calm plot, comparable across frames:
plot_lines(LATENCY, "ms", samples,
0.0f, 200.0f, float2(0.0f, 80.0f)) // 0..200 ms y-axis
Use auto-fit only when you don’t know the data range and don’t care about per-frame stability. For anything you’ll watch over time, explicit bounds.
Size
The last arg is the plot bbox in pixels — float2(width, height).
width = 0.0f lets ImGui auto-size to the available content width;
height = 0.0f falls back to a small default. Pass 80.0f or
120.0f for the height to keep two plots visible side-by-side.
The overlay arg
Lambda-form widgets take an extra overlay_text : string arg before
scale_min. Pass "" for no overlay, or a status string that
prints centered on top of the plot:
plot_lines_getter(FRAMETIME, "ms/frame", 60,
@(idx : int) : float => sample_at(idx),
"avg = {avg_ms:.2f}",
0.0f, 33.0f, float2(0.0f, 80.0f))
Useful for the “rolling average” or “max” annotation under the line.
Driving from outside
PlotState exposes title + samples in the snapshot payload —
no imgui_force_set channel (the samples are caller-pushed). Snapshot
probes are the right tool:
curl -X POST -d '{"name":"imgui_snapshot"}' localhost:9090/command \
| jq '.globals."PL_WIN/PL_LINES".payload'
The samples array gives visual-regression tests something to diff without screenshotting.
See also
Full source: examples/tutorial/plot.das
Features-side demos: examples/features/plot_widgets.das
(array form), examples/features/plot_getter.das (lambda form).
Boost macros — the macro layer.