orbital_charts/engine/
hit_test.rs1use crate::PlacedScatterPoint;
4
5#[derive(Clone, Debug, PartialEq)]
7pub struct HitTarget {
8 pub series_id: String,
10 pub data_index: usize,
12 pub distance: f64,
14}
15
16pub fn find_nearest_point(
18 px: f64,
19 py: f64,
20 points: &[PlacedScatterPoint],
21 max_radius: Option<f64>,
22) -> Option<HitTarget> {
23 let mut best: Option<HitTarget> = None;
24
25 for point in points {
26 let dx = px - point.px;
27 let dy = py - point.py;
28 let distance = (dx * dx + dy * dy).sqrt();
29
30 if let Some(max) = max_radius {
31 if distance > max {
32 continue;
33 }
34 }
35
36 if best.as_ref().is_none_or(|b| distance < b.distance) {
37 best = Some(HitTarget {
38 series_id: point.series_id.clone(),
39 data_index: point.data_index,
40 distance,
41 });
42 }
43 }
44
45 best
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51
52 fn point(px: f64, py: f64, series_id: &str, data_index: usize) -> PlacedScatterPoint {
53 PlacedScatterPoint {
54 px,
55 py,
56 series_id: series_id.into(),
57 data_index,
58 }
59 }
60
61 #[test]
62 fn find_nearest_point_picks_closest() {
63 let points = vec![point(10.0, 10.0, "a", 0), point(50.0, 50.0, "a", 1)];
64 let hit = find_nearest_point(12.0, 11.0, &points, None).unwrap();
65 assert_eq!(hit.data_index, 0);
66 }
67
68 #[test]
69 fn find_nearest_point_respects_max_radius() {
70 let points = vec![point(100.0, 100.0, "a", 0)];
71 assert!(find_nearest_point(0.0, 0.0, &points, Some(10.0)).is_none());
72 assert!(find_nearest_point(95.0, 100.0, &points, Some(10.0)).is_some());
73 }
74}