graph_explorer_layout/
radial.rs1use crate::Position;
2
3pub struct RadialInput {
5 pub history_len: usize, pub candidate_count: usize,
7}
8
9pub struct RadialPositions {
10 pub focus: Position,
11 pub history: Vec<Position>, pub candidates: Vec<Position>,}
14
15pub struct RadialLayout {
16 pub hist_x: f32, pub hist_gap: f32, pub cand_radius: f32,pub cand_spread: f32,}
21
22impl Default for RadialLayout {
23 fn default() -> Self {
24 Self { hist_x: -3.0, hist_gap: 1.4, cand_radius: 3.0, cand_spread: 0.9 }
25 }
26}
27
28impl RadialLayout {
29 pub fn compute(&self, input: &RadialInput) -> RadialPositions {
30 let focus = [0.0, 0.0];
31
32 let n = input.history_len;
33 let history: Vec<Position> = (0..n)
34 .map(|i| {
35 let recency = (n - 1 - i) as f32;
37 [self.hist_x, recency * self.hist_gap]
38 })
39 .collect();
40
41 let m = input.candidate_count;
42 let candidates: Vec<Position> = (0..m)
43 .map(|j| {
44 let t = if m <= 1 { 0.5 } else { j as f32 / (m as f32 - 1.0) }; let angle = (t - 0.5) * std::f32::consts::PI * self.cand_spread; [self.cand_radius * angle.cos(), self.cand_radius * angle.sin()]
47 })
48 .collect();
49
50 RadialPositions { focus, history, candidates }
51 }
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 #[test]
59 fn focus_at_origin() {
60 let out = RadialLayout::default().compute(&RadialInput { history_len: 0, candidate_count: 0 });
61 assert_eq!(out.focus, [0.0, 0.0]);
62 assert!(out.history.is_empty());
63 assert!(out.candidates.is_empty());
64 }
65
66 #[test]
67 fn history_is_a_left_column_newest_nearest() {
68 let out = RadialLayout::default().compute(&RadialInput { history_len: 3, candidate_count: 0 });
69 assert_eq!(out.history.len(), 3);
70 assert!(out.history.iter().all(|p| p[0] < 0.0));
72 let oldest_y = out.history[0][1].abs();
74 let newest_y = out.history[2][1].abs();
75 assert!(newest_y < oldest_y, "newest should sit nearer the focus than oldest");
76 }
77
78 #[test]
79 fn candidates_fan_to_the_right() {
80 let out = RadialLayout::default().compute(&RadialInput { history_len: 0, candidate_count: 4 });
81 assert_eq!(out.candidates.len(), 4);
82 assert!(out.candidates.iter().all(|p| p[0] > 0.0), "candidates must be on the right");
83 }
84}