Multi series

The same chart, now with the v2 DSL imgui_implot_boost_v2. The plot scope here is more than RAII: it is a snapshot-registered container. Each frame it serializes the plot’s geometry, axis limits, hovered flag, and mouse-plot position into the dasImgui snapshot rail, keyed by the window + plot identifiers (PLOT_WIN/WAVES) — which is exactly what the headless tests assert on. The item wrappers are OR-typed (float | double | int), so one call site handles any element type, and SetNext* styling colors the next item.

plot(WAVES, (title = "waves", size = float2(-1.0f, 480.0f), flags = ImPlotFlags.None)) {
    setup_axes("sample", "value")
    setup_axes_limits(0.0lf, 200.0lf, -1.5lf, 1.5lf)
    next_line_style(float4(0.30f, 0.70f, 1.00f, 1.00f), 2.0f)
    plot_line("sin", g_sin)
    plot_line("cos", g_cos)
    next_marker_style(ImPlotMarker.Circle, 4.0f)
    plot_scatter("samples", g_pts_x, g_pts_y)   // int / float / double all work
}

Source: examples/tutorial/multi_series.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: multi_series — the v2 `plot` snapshot scope + multi-type items.
12//
13//   plot(IDENT, (title, size, flags)) { ... }  — v2 RAII plot scope that ALSO
14//       serializes its per-frame state (geometry, axis limits, hovered,
15//       mouse-plot-pos) into the dasImgui snapshot rail, so a test/playwright
16//       layer can assert on it. EndPlot can't be skipped.
17//   setup_axes / setup_axes_limits             — label + fix the visible range.
18//   next_line_style / next_marker_style        — style the NEXT item.
19//   plot_line / plot_scatter / plot_bars       — OR-typed: float | double | int.
20//
21// vs v1's `with_plot`, the v2 `plot` is a snapshot-registered container — its
22// state shows up under the IDENT in `imgui_snapshot`. Built on the dasImgui
23// harness, so it runs standalone (windowed), headless (--headless), and live.
24//
25// STANDALONE: daslang.exe modules/dasImguiImplot/examples/tutorial/multi_series.das
26// LIVE:       daslang-live modules/dasImguiImplot/examples/tutorial/multi_series.das
27// =============================================================================
28
29var g_ctx : ImPlotContext?
30var g_sin : array<double>
31var g_cos : array<double>
32var g_pts_x : array<double>
33var g_pts_y : array<double>
34var g_bars : array<int>
35
36[export]
37def init() {
38    harness_init("dasImguiImplot — multi_series", 1280, 860)
39    g_ctx = implot::CreateContext()
40    g_sin <- [for (i in range(200)); double(sin(float(i) * 0.05f))]
41    g_cos <- [for (i in range(200)); double(cos(float(i) * 0.05f))]
42    g_pts_x <- [for (i in range(20)); double(i) * 10.0lf]
43    g_pts_y <- [for (i in range(20)); double(sin(float(i) * 0.5f)) * 0.8lf]
44    g_bars <- [for (i in range(10)); i % 5]
45}
46
47[export]
48def update() {
49    if (!harness_begin_frame()) return
50    harness_new_frame()
51
52    SetNextWindowPos(float2(20.0, 20.0), ImGuiCond.Always)
53    SetNextWindowSize(float2(1240.0, 820.0), ImGuiCond.Always)
54    window(PLOT_WIN, (text = "multi series", closable = false,
55                      flags = ImGuiWindowFlags.None)) {
56        text("v2 plot scope: snapshot-registered, multi-type items, per-item styling.")
57        plot(WAVES, (title = "waves", size = float2(-1.0f, 480.0f), flags = ImPlotFlags.None)) {
58            setup_axes("sample", "value")
59            setup_axes_limits(0.0lf, 200.0lf, -1.5lf, 1.5lf)
60            next_line_style(float4(0.30f, 0.70f, 1.00f, 1.00f), 2.0f)
61            plot_line("sin", g_sin)
62            plot_line("cos", g_cos)
63            next_marker_style(ImPlotMarker.Circle, 4.0f)
64            plot_scatter("samples", g_pts_x, g_pts_y)
65        }
66        plot(BARS, (title = "bars (int data)", size = float2(-1.0f, 240.0f),
67                    flags = ImPlotFlags.None)) {
68            setup_axes("bucket", "count")
69            plot_bars("counts", g_bars)
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

The plot snapshot scope

plot(IDENT, (title, size, flags)) { ... } is a [container] macro: the bare IDENT (WAVES) becomes the plot’s identifier in the snapshot tree, nested under its window (PLOT_WIN/WAVES). Geometry and the ImPlot queries (GetPlotLimits / IsPlotHovered / GetPlotMousePos) are captured between BeginPlot and EndPlot — the only window where they are valid — so a test reads them straight back out of the payload. This is the hook the whole testing layer is built on.

OR-typed items

plot_line / plot_scatter / plot_bars accept array<float> | array<double> | array<int> in one signature. The second plot in the example feeds plot_bars an array<int> directly — no conversion, no separate overload at the call site.

Per-item styling

next_line_style(col, weight) and next_marker_style(marker, size) set the appearance of the next item only (ImPlot’s SetNext* model). Call them immediately before the item you want to style.

Legend toggle

Every item with a label gets a legend entry, and clicking an entry hides or shows that series — ImPlot handles this for free; the data is still submitted each frame, it just isn’t drawn. The plot scope serializes the legend into the snapshot — one entry per series carrying its label, its shown flag, whether it is hovered, and its clickable rect — so a test or recording can target an entry by name and verify the toggle landed. The recording above clicks the cos entry to hide the curve and clicks it again to bring it back, asserting shown flips each time.

The playwright verb legend_toggle(session, "cos") drives this the way a person does: it moves the cursor onto the entry, waits until the entry actually reports hovered (wait_for_legend_hover), and only then clicks — so the synthetic press can’t land before the hover registers. wait_for_series_shown(session, "cos", false) is the gate that the click took effect.