#[derive(Debug, Clone, Copy, PartialEq)]
pub struct HoverState {
pub index: usize,
pub client_x: f64,
pub client_y: f64,
}
#[must_use]
pub fn pixel_to_index(client_x: f64, plot_left: f64, plot_width: f64, n_points: usize) -> usize {
if n_points <= 1 || plot_width <= 0.0 {
return 0;
}
let last = n_points - 1;
let fraction = ((client_x - plot_left) / plot_width).clamp(0.0, 1.0);
let raw = fraction * last as f64;
let snapped = raw.round() as usize;
snapped.min(last)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pixel_to_index_snaps_to_nearest() {
assert_eq!(pixel_to_index(0.0, 0.0, 1000.0, 10), 0);
assert_eq!(pixel_to_index(1000.0, 0.0, 1000.0, 10), 9);
assert_eq!(pixel_to_index(500.0, 0.0, 1000.0, 10), 5);
assert_eq!(pixel_to_index(56.0, 0.0, 1000.0, 10), 1);
assert_eq!(pixel_to_index(54.0, 0.0, 1000.0, 10), 0);
}
#[test]
fn pixel_to_index_clamps_at_bounds() {
assert_eq!(pixel_to_index(-100.0, 0.0, 1000.0, 5), 0);
assert_eq!(pixel_to_index(2000.0, 0.0, 1000.0, 5), 4);
}
#[test]
fn pixel_to_index_handles_single_point() {
assert_eq!(pixel_to_index(500.0, 0.0, 1000.0, 1), 0);
}
#[test]
fn pixel_to_index_respects_plot_left_offset() {
assert_eq!(pixel_to_index(600.0, 200.0, 800.0, 11), 5);
}
#[test]
fn pixel_to_index_handles_zero_width() {
assert_eq!(pixel_to_index(500.0, 0.0, 0.0, 5), 0);
}
}