Interaction
The ChartInteraction trait defines the pointer, keyboard, and IME event API shared by all ChartView implementations. Forward raw input events from your UI framework — the chart handles hit-testing, gesture recognition, crosshair updates, annotation dragging, pane-divider resizing, cursor feedback, and text editing internally.
Gesture detection is fully internalized. Single-click, double-click, and drag are all recognised inside the chart using timing and position tracking. You only forward the raw pointer events and the chart fires the appropriate ChartEvents automatically. Right-click context menus are handled by calling context_menu_items synchronously.
Import
use navi_chart::{ChartView, ChartInteraction};Usage
use navi_chart::{ChartInteraction, Modifiers, PointerKind, PointerButton};
fn wire_events<C>(cv: &C, event: MyUiEvent)
where
C: ChartInteraction,
{
match event {
MyUiEvent::Wheel(delta, x) => cv.on_wheel(delta, x),
MyUiEvent::Scroll(dx, dy) => cv.on_scroll(dx, dy),
MyUiEvent::Pinch(scale, cx, cy) => cv.on_pinch(scale, Point { x: cx, y: cy }),
MyUiEvent::PointerMove(id, x, y) => cv.on_pointer_move(id, Point { x, y }, Modifiers::empty()),
MyUiEvent::PointerDown(id, kind, x, y) => { cv.on_pointer_down(id, kind, PointerButton::Primary, Point { x, y }, Modifiers::empty()); }
MyUiEvent::PointerUp(id, x, y) => cv.on_pointer_up(id, Point { x, y }),
MyUiEvent::PointerCancel(id) => cv.on_pointer_cancel(id),
MyUiEvent::PointerEnter => cv.on_pointer_enter(),
MyUiEvent::PointerLeave => cv.on_pointer_leave(),
// Click, double-click are detected internally — no separate events needed.
_ => {}
}
// Update the CSS cursor after each event
let cursor = cv.cursor_at(last_x, last_y);
set_cursor(cursor);
}Pointer Methods
| Method | Description |
|---|---|
on_wheel(delta, anchor_x) | Accumulate a zoom impulse. delta is positive = zoom in, negative = zoom out. anchor_x is the pointer X position in logical pixels, used as the zoom anchor so the bar under the cursor stays fixed. Applied with smooth inertial deceleration via on_tick. |
on_scroll(dx, dy) | Pan immediately by a trackpad two-finger scroll delta, without Rust-side inertia — the OS supplies its own momentum. dx positive = advance toward newer bars. |
on_pinch(scale, center) | Zoom immediately by a multiplicative scale (>1 = zoom in) anchored at center. Driven by OS-recognized pinch gestures (trackpad or touch). |
on_pointer_down(id, kind, button, pos, modifiers) | Handle a pointer pressed-down. id distinguishes concurrent pointers (a second pointer begins a pinch). Returns true if the chart consumed the event (e.g. a divider/annotation grab) — suppress the host's default handling in that case. Click and double-click gestures are recognised internally from Down/Up — no separate calls are needed. Right-click context menus are handled separately via context_menu_items. |
on_pointer_move(id, pos, modifiers) | Handle a pointer moving (hover, drag, or pinch). Call on every move event. |
on_pointer_up(id, pos) | Handle a pointer released. Finalizes pane-divider drags and annotation drag-moves, seeds pan inertia, and fires any pending click or double-click gesture. |
on_pointer_cancel(id) | Handle a pointer interaction being cancelled (OS captured the gesture, touch interrupted). Never fires a click. |
on_pointer_enter() | Handle the pointer entering the canvas. The crosshair and hover state are restored naturally by the first on_pointer_move that follows. Call on every pointerenter. |
on_pointer_leave() | Handle the pointer leaving the canvas — hides the crosshair. An in-progress drag is unaffected (the platform retains pointer capture until release). Call on every pointerleave. |
context_menu_items(pos) | Return the list of ContextMenuItems for a right-click at pos. Call this synchronously when a right-click or contextmenu event fires and use the returned items to populate your native or custom context menu. This method may have side effects — it can push a SelectionChanged event if the click lands on a selectable element. |
cursor_at(pos) | Return the Cursor variant for the pointer position. Call on every pointer-move and map the variant to your platform's cursor API. |
hit_test(pos) | Return the HitTarget under pos. Does not change selection or push events. |
on_tick() | Advance pan and zoom inertia physics by one animation frame and re-render if any movement occurred. Call unconditionally on every requestAnimationFrame tick. |
Context Menu
When a right-click or contextmenu event fires, call context_menu_items(pos) synchronously to get the items for that position. The method may push a SelectionChanged event as a side effect if the click selects an element. Use the returned Vec<ContextMenuItem> to populate your native or custom context menu:
canvas.addEventListener('contextmenu', e => {
e.preventDefault(); // suppress the browser's default context menu
const items = cv.context_menu_items({ x: e.offsetX, y: e.offsetY });
if (items.length > 0) {
show_context_menu(e.clientX, e.clientY, items);
}
// Drain ChartEvents — context_menu_items may have pushed SelectionChanged
drain_events(cv);
});Cursor Enum
cursor_at returns a Cursor enum. Map its variants to CSS cursor values:
| Variant | CSS cursor | When used |
|---|---|---|
Default | "default" | Normal pointer mode |
Crosshair | "crosshair" | Over the chart plot area |
Pointer | "pointer" | Over a clickable element (button, label) |
Move | "move" | Over a selected annotation (drag to move) |
NSResize | "ns-resize" | Over a pane divider (vertical resize) |
EWResize | "ew-resize" | Over a horizontal resize handle |
NESWResize | "nesw-resize" | Over a corner resize handle |
NWSEResize | "nwse-resize" | Over a corner resize handle |
Text | "text" | Over an editable text annotation |
Grabbing | "grabbing" | While dragging |
ChartElement
selection() and hit_test() return Option<ChartElement>:
match cv.selection() {
Some(ChartElement::Bar) => {
// The candlestick series is selected
}
Some(ChartElement::Series { script_id, graph_id }) => {
// A series plot line or marker
}
Some(ChartElement::Annotation(id)) => {
// An annotation — use id to query its properties
}
None => {}
}Selection
| Method | Description |
|---|---|
selection() | Return the currently selected ChartElement, or None. Changes are also reported through SelectionChanged events. |
select(element) | Programmatically set the selected element. Pass None to clear the selection. Emits a SelectionChanged event and re-renders if the selection changed. |
// Select an annotation programmatically
cv.select(Some(ChartElement::Annotation(id)));
// Select the candlestick bar series
cv.select(Some(ChartElement::Bar));
// Clear the selection
cv.select(None);Keyboard & IME Methods
| Method | Description |
|---|---|
captures_keyboard() | Return true when a text annotation is being edited — forward all keyboard and IME events to the chart, and give focus to the host's IME input bridge. |
key_down(key, modifiers) | Forward a key-down event to the active tool. Returns true when a text-editing session starts or ends as a result — re-check captures_keyboard() and update IME focus accordingly. |
text_input(text) | Forward plain-character or paste text to the active tool. |
composition_start() | Notify the active tool that an IME composition session started. |
composition_update(text, cursor) | Update the in-progress IME composition string. |
composition_end(text) | Finish an IME composition. Empty text means the user cancelled. |
focus_lost() | Notify the active tool that input focus was lost — the text-editing tool treats this as an implicit commit. |
request_text_edit(id) | Enter text-edit mode for annotation id. Returns true if the transition succeeded. |
text_editor_selection() | Return the selected text in the active editor session, or None. |
text_editor_cut() | Delete the selected text and return it, or None. |
caret_pixel() | Return the screen pixel of the text caret when an editor session is active, or None. |
poll_event() | Dequeue and return the next pending ChartEvent, or None if the queue is empty. |
Typical Event-Loop Wiring
use navi_chart::{ChartInteraction, ChartSettings, Modifiers, PointerKind, PointerButton};
fn handle_pointer_event<C>(cv: &C, event: PointerEvent)
where
C: ChartInteraction + ChartSettings,
{
let mods = Modifiers::from_bits_truncate(event.modifier_bits);
match event.kind {
PointerEventKind::Down => {
let consumed = cv.on_pointer_down(
event.id, event.pointer_kind, event.button, event.pos, mods,
);
if consumed {
event.prevent_default();
}
}
// Always forward pointer-move — the chart drives pan internally
// when a button is held, and updates the crosshair otherwise.
PointerEventKind::Move => cv.on_pointer_move(event.id, event.pos, mods),
PointerEventKind::Up => cv.on_pointer_up(event.id, event.pos),
PointerEventKind::Cancel => cv.on_pointer_cancel(event.id),
PointerEventKind::Enter => cv.on_pointer_enter(),
PointerEventKind::Leave => cv.on_pointer_leave(),
// No Click or DblClick events needed —
// the chart detects these internally from Down/Up.
// Right-click is handled separately via context_menu_items.
}
// Drain the event queue after each interaction call
while let Some(ev) = cv.poll_event() {
handle_chart_event(ev);
}
set_cursor(cv.cursor_at(event.pos));
}
// Start a persistent animation-frame loop once after the chart is created.
fn start_tick_loop(cv: Rc<impl ChartInteraction + 'static>) {
request_animation_frame(move || {
cv.on_tick();
start_tick_loop(cv.clone());
});
}Keyboard & IME Input
When captures_keyboard() returns true, a text annotation is being edited. Forward keyboard events from your UI to the chart:
use navi_chart::{ChartInteraction, Key, Modifiers};
fn handle_keyboard<C>(cv: &C, event: KeyEvent)
where
C: ChartInteraction,
{
if !cv.captures_keyboard() {
return;
}
match event {
KeyEvent::KeyDown(key, mods) => { cv.key_down(key, mods); }
KeyEvent::TextInput(text) => cv.text_input(&text),
KeyEvent::CompositionStart => cv.composition_start(),
KeyEvent::CompositionUpdate(text, cursor) => cv.composition_update(&text, cursor),
KeyEvent::CompositionEnd(text) => cv.composition_end(&text),
KeyEvent::FocusLost => cv.focus_lost(),
}
}Clipboard
// Copy — on platform copy event (Ctrl+C or equivalent)
if let Some(text) = cv.text_editor_selection() {
platform_clipboard_write(&text);
}
// Cut — on platform cut event (Ctrl+X or equivalent)
if let Some(text) = cv.text_editor_cut() {
platform_clipboard_write(&text);
}
// Paste — on platform paste event (Ctrl+V or equivalent)
if let Some(text) = platform_clipboard_read() {
cv.text_input(&text);
}Programmatic Text Edit
cv.request_text_edit(annotation_id);See Settings & Events for the full ChartEvent table.