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.