Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added doc/source/_static/tutorials/query_and_hover.mp4
Binary file not shown.
17 changes: 15 additions & 2 deletions doc/source/tutorials/query_and_hover.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ annotation uses.
let mp = GetPlotMousePos(ImAxis.X1, ImAxis.Y1) // data coords
g_cursor_x[0] = mp.x
plot_inf_lines("cursor", g_cursor_x) // vertical crosshair
plot_text("({mp.x}, {mp.y})", mp.x, mp.y, float2(10.0f, 10.0f))
plot_text("({mp.x:.1f}, {mp.y:.2f})", mp.x, mp.y, float2(10.0f, 10.0f))
}
}

Expand All @@ -32,6 +32,17 @@ Source: ``examples/tutorial/query_and_hover.das``.
:language: das
:linenos:

Walkthrough
===========

.. video:: query_and_hover.mp4

The recording glides the cursor across the plot with real synthetic input: the
vertical crosshair and the ``(x, y)`` label track it every frame, and ImPlot's
corner mouse readout updates alongside. It self-verifies that ``hovered`` flips true
and ``GetPlotMousePos`` resolves into the band the cursor was aimed at — left, then
right — so a dead hover or a frozen readout fails at teardown.

Live query
==========

Expand All @@ -47,7 +58,9 @@ Annotating the cursor
here a one-element array reused each frame for the crosshair (kept as a global to
avoid a per-frame allocation). ``plot_text(text, x, y, pix_offset)`` anchors a label
at a data point, offset by screen pixels so it sits beside the cursor rather than
under it.
under it. The label uses ``fmt`` precision specifiers in the interpolation —
``{mp.x:.1f}`` / ``{mp.y:.2f}`` — so it reads ``(74.0, -0.19)`` instead of full
double precision.

Testing the hover
=================
Expand Down
3 changes: 2 additions & 1 deletion examples/tutorial/query_and_hover.das
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require imgui/imgui_widgets_builtin
require imgui/imgui_implot_boost_v2
require implot
require math
require strings

// =============================================================================
// TUTORIAL: query_and_hover — read the cursor's plot position live and annotate it.
Expand Down Expand Up @@ -58,7 +59,7 @@ def update() {
g_cursor_x[0] = mp.x
next_line_style(float4(1.00f, 1.00f, 1.00f, 0.50f), 1.0f)
plot_inf_lines("cursor", g_cursor_x)
plot_text("({mp.x}, {mp.y})", mp.x, mp.y, float2(10.0f, 10.0f))
plot_text("({mp.x:.1f}, {mp.y:.2f})", mp.x, mp.y, float2(10.0f, 10.0f))
}
}
}
Expand Down
84 changes: 84 additions & 0 deletions tests/integration/record_query_and_hover.das
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
options gen2
options indenting = 4
options no_unused_block_arguments = false
options no_unused_function_arguments = false

require imgui/imgui_implot_app public
require imgui/imgui_implot_playwright public
require implot
require daslib/json public
require daslib/json_boost public

//! Driver: record query_and_hover.apng - a plot that reads the cursor LIVE. While the
//! pointer is over the plot the example draws a vertical crosshair at the cursor x and a
//! (x, y) label in DATA coords (GetPlotMousePos). The interaction this tutorial teaches
//! is the HOVER: move the cursor and the readout follows, every frame. Driven with a REAL
//! synthetic cursor glide, voiced and self-verifying - each beat waits for `hovered` to
//! flip true, then asserts GetPlotMousePos resolved into the band the cursor was aimed at
//! (and that it moved between beats), so a dead hover or a frozen readout aborts at
//! teardown. The headless regression is test_query_and_hover.das.

let CHART = "PLOT_WIN/CHART"

// Hold a beat's caption for the remainder of its voice dwell, measuring real elapsed work
// (glide + verify) so the next say_begin can't fire early and overlap the voice.
def hold_remainder(dwell : uint; t0 : int64) {
let elapsed = uint(get_time_usec(t0) / 1000)
if (dwell > elapsed) {
sleep(dwell - elapsed)
}
}

[export]
def main {
with_implot_recording_app("examples/tutorial/query_and_hover.das", "query_and_hover.apng", 45) $(app) {
let s = implot_open(app, CHART)
var snap = wait_for_widget(app, CHART, 15.0f)
if (snap == null) {
panic("{CHART} never rendered - wrong app running?")
}
// Data area geometry (GetPlotPos / GetPlotSize) - maps screen fractions to data x.
let p = plot_payload(snap, s)
let px = p?["pos"]?["x"] ?? 0.0f
let py = p?["pos"]?["y"] ?? 0.0f
let pw = p?["plot_size"]?["x"] ?? 0.0f
let ph = p?["plot_size"]?["y"] ?? 0.0f

// ---- Beat 1: the live readout ----
move_to(app, (px + pw * 0.5f, py + ph * 0.5f), 700)
var hc = wait_for_hovered(s, true)
record_check(app, "the plot reports hovered while the cursor is over it", hc != null)
Comment on lines +48 to +50
say(app, "hover to read the plot", CHART,
[voice = "This plot reads the cursor live. While you hover over it, it draws a crosshair at the cursor and labels its position in data coordinates."])

// ---- Beat 2: the readout follows the cursor (glide to the left third) ----
let d_l = say_begin(app, "the readout follows the cursor", CHART,
[voice = "Move the cursor and the readout follows. Here, near the left, the label shows the x and y under the pointer, updated every frame."])
let t_l = ref_time_ticks()
move_to(app, (px + pw * 0.22f, py + ph * 0.42f), 900)
wait_for_mouse_idle(app)
var hl = wait_for_hovered(s, true)
let mx_l = mouse_x(hl, s)
record_check(app, "readout tracks the cursor near the left (x in [8,40], got {mx_l})",
mx_l >= 8.0lf && mx_l <= 40.0lf)
hold_remainder(d_l, t_l)

// ---- Beat 3: read any point (glide across to the right third) ----
let d_r = say_begin(app, "read any point", CHART,
[voice = "Glide across to the right and the crosshair and the label track right along with it. You can read off the value at any point on the plot."])
let t_r = ref_time_ticks()
move_to(app, (px + pw * 0.74f, py + ph * 0.58f), 1100)
wait_for_mouse_idle(app)
var hr = wait_for_hovered(s, true)
let mx_r = mouse_x(hr, s)
record_check(app, "readout tracks the cursor near the right (x in [60,92], got {mx_r})",
mx_r >= 60.0lf && mx_r <= 92.0lf)
record_check(app, "the readout moved right with the cursor (left {mx_l} < right {mx_r})",
mx_r > mx_l + 20.0lf)
hold_remainder(d_r, t_r)
}
}

def mouse_x(var snap : JsonValue?; s : PlotSession) : double {
return plot_mouse_pos(snap, s)._0
}
Loading