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_runtimePlotState.

Array form vs lambda form

  • Array (plot_lines / plot_histogram) — pass a values : 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) — pass values_count and a getter : 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.