forked from plotly/plotly.rs
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcallbacks.rs
More file actions
141 lines (129 loc) · 4.85 KB
/
callbacks.rs
File metadata and controls
141 lines (129 loc) · 4.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use web_sys::{js_sys::Function, HtmlElement};
/// Provides utilities for binding Plotly.js click events to Rust closures
/// via `wasm-bindgen`.
///
/// This module defines a `PlotlyDiv` foreign type for the Plotly `<div>`
/// element, a high-level `bind_click` function to wire up Rust callbacks, and
/// the `ClickPoint`/`ClickEvent` data structures to deserialize event payloads.
#[wasm_bindgen]
extern "C" {
/// A wrapper around the JavaScript `HTMLElement` representing a Plotly
/// `<div>`.
///
/// This type extends `web_sys::HtmlElement` and exposes Plotly’s
/// `.on(eventName, callback)` method for attaching event listeners.
#[wasm_bindgen(extends= HtmlElement, js_name=HTMLElement)]
type PlotlyDiv;
/// Attach a JavaScript event listener to this Plotly `<div>`.
///
/// # Parameters
/// - `event`: The Plotly event name (e.g., `"plotly_click"`).
/// - `cb`: A JS `Function` to invoke when the event fires.
///
/// # Panics
/// This method assumes the underlying element is indeed a Plotly div
/// and that the Plotly.js library has been loaded on the page.
#[wasm_bindgen(method,structural,js_name=on)]
fn on(this: &PlotlyDiv, event: &str, cb: &Function);
}
/// Bind a Rust callback to the Plotly `plotly_click` event on a given `<div>`.
///
/// # Type Parameters
/// - `F`: A `'static + FnMut(ClickEvent)` closure type to handle the click
/// data.
///
/// # Parameters
/// - `div_id`: The DOM `id` attribute of the Plotly `<div>`.
/// - `cb`: A mutable Rust closure that will be called with a `ClickEvent`.
///
/// # Details
/// 1. Looks up the element by `div_id`, converts it to `PlotlyDiv`.
/// 2. Wraps a `Closure<dyn FnMut(JsValue)>` that deserializes the JS event into
/// our `ClickEvent` type via `serde_wasm_bindgen`.
/// 3. Calls `plot_div.on("plotly_click", …)` to register the listener.
/// 4. Forgets the closure so it lives for the lifetime of the page.
///
/// # Example
/// ```ignore
/// bind_click("my-plot", |evt| {
/// web_sys::console::log_1(&format!("{:?}", evt).into());
/// });
/// ```
pub fn bind_click<F>(div_id: &str, mut cb: F)
where
F: 'static + FnMut(ClickEvent),
{
let closure = Closure::wrap(Box::new(move |event: JsValue| {
let event: ClickEvent =
serde_wasm_bindgen::from_value(event).expect("Could not serialize the event");
cb(event);
}) as Box<dyn FnMut(JsValue)>);
let plot_div: PlotlyDiv = get_div(div_id).expect("Could not get Div element by Id");
plot_div.on("plotly_click", closure.as_ref().unchecked_ref());
closure.forget();
}
fn get_div(tag: &str) -> Option<PlotlyDiv> {
web_sys::window()?
.document()?
.get_element_by_id(tag)?
.dyn_into()
.ok()
}
/// Represents a single point from a Plotly click event.
///
/// Fields mirror Plotly’s `event.points[i]` properties, all optional
/// where appropriate:
///
/// - `curve_number`: The zero-based index of the trace that was clicked.
/// - `point_numbers`: An optional list of indices if multiple points were
/// selected.
/// - `point_number`: The index of the specific point clicked (if singular).
/// - `x`, `y`, `z`: Optional numeric coordinates in data space.
/// - `lat`, `lon`: Optional geographic coordinates (for map plots).
///
/// # Serialization
/// Uses `serde` with `camelCase` field names to match Plotly’s JS API.
#[derive(Debug, Deserialize, Serialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct ClickPoint {
pub curve_number: usize,
pub point_numbers: Option<Vec<usize>>,
pub point_number: Option<usize>,
pub x: Option<f64>,
pub y: Option<f64>,
pub z: Option<f64>,
pub lat: Option<f64>,
pub lon: Option<f64>,
}
/// Provide a default single-point vector for `ClickEvent::points`.
///
/// Returns `vec![ClickPoint::default()]` so deserialization always yields
/// at least one element rather than an empty vector.
fn default_click_event() -> Vec<ClickPoint> {
vec![ClickPoint::default()]
}
/// The top-level payload for a Plotly click event.
///
/// - `points`: A `Vec<ClickPoint>` containing all clicked points. Defaults to
/// the result of `default_click_event` to ensure `points` is non-empty even
/// if Plotly sends no data.
///
/// # Serialization
/// Uses `serde` with `camelCase` names and a custom default so you can
/// call `event.points` without worrying about missing values.
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase", default)]
pub struct ClickEvent {
#[serde(default = "default_click_event")]
pub points: Vec<ClickPoint>,
}
/// A `Default` implementation yielding an empty `points` vector.
///
/// Useful when you need a zero-event placeholder (e.g., initial state).
impl Default for ClickEvent {
fn default() -> Self {
ClickEvent { points: vec![] }
}
}