Skip to content

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

rust
use navi_chart::{ChartView, ChartInteraction};

Usage

rust
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

MethodDescription
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:

js
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:

VariantCSS cursorWhen 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>:

rust
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

MethodDescription
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.
rust
// 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

MethodDescription
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

rust
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:

rust
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

rust
// 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

rust
cv.request_text_edit(annotation_id);

See Settings & Events for the full ChartEvent table.

Released under the MIT License.