1use super::*;
7
8pub const STATS: [&str; 4] = ["nnz", "sum", "mean", "sd"];
10
11pub struct StatData<'a> {
13 pub title: &'a str,
15 pub side: &'a str,
17 pub values: [&'a [f32]; 4],
19 pub marked: Option<&'a [bool]>,
21 pub cursor: Option<(usize, &'a str)>,
23}
24
25pub fn draw_stat(d: &StatData, k: usize, c: &mut Canvas, (x, y, w, h): (f64, f64, f64, f64)) {
27 let values = d.values[k];
28 let marked = d.marked.filter(|m| m.iter().any(|&b| b));
29 let mut sorted: Vec<f32> = values.iter().copied().filter(|v| v.is_finite()).collect();
30 sorted.sort_by(f32::total_cmp);
31 let median = sorted.get(sorted.len() / 2).copied().unwrap_or(0.0);
32 let mut subtitle = format!("median {}", short(median as f64));
33 if let Some(m) = marked {
34 subtitle.push_str(&format!(" · {} marked", m.iter().filter(|&&b| b).count()));
35 }
36 if let Some((i, name)) = d.cursor {
37 let rank = sorted.len() - sorted.partition_point(|&v| v <= values[i]) + 1;
38 subtitle.push_str(&format!(" · ▲ {name}, rank {rank} of {}", sorted.len()));
39 }
40 let side = d.side.trim_end_matches('s');
41 let mut layers = vec![(None, BAR)];
42 if let Some(m) = marked {
43 layers.push((Some(m), ACCENT));
44 }
45 Hist {
46 title: format!("{} per {side}", STATS[k]),
47 subtitle,
48 x_title: format!("{} per {side}", STATS[k]),
49 axis: Axis::fit(values, true),
50 values,
51 layers,
52 edge: None,
53 cursor: d.cursor.map(|(i, _)| values[i] as f64),
54 }
55 .draw(c, x, y, w, h);
56}
57
58pub fn draw_mean_sd(d: &StatData, c: &mut Canvas, (x, y, w, h): (f64, f64, f64, f64)) {
60 let (mean, sd) = (d.values[2], d.values[3]);
61 let marked = d.marked.filter(|m| m.iter().any(|&b| b));
62 let points: Vec<Point> = (0..mean.len())
63 .map(|i| {
64 let on = marked.is_some_and(|m| m[i]) || d.cursor.is_some_and(|(k, _)| k == i);
65 Point {
66 x: mean[i],
67 y: sd[i],
68 fill: if on { ACCENT } else { BAR },
69 ringed: false,
70 }
71 })
72 .collect();
73 let (xa, ya) = (Axis::fit(mean, true), Axis::fit(sd, true));
74 let side = d.side.trim_end_matches('s');
75 let mut key = vec![];
76 if marked.is_some() {
77 key.push((ACCENT, "marked".to_string()));
78 }
79 Scatter {
80 title: "Mean against sd".into(),
81 subtitle: format!("per {side} · dashed: Poisson, sd = √mean"),
82 x_title: "mean".into(),
83 y_title: "sd".into(),
84 x: xa,
85 y: ya,
86 points: &points,
87 x_edge: None,
88 y_edge: None,
89 key,
90 }
91 .draw(c, x, y, w, h);
92 let (px, py, pw, ph) = plot_area(x, y, w, h);
94 let line: Vec<(f64, f64)> = (0..=60)
95 .map(|k| xa.at(k as f64 / 60.0))
96 .filter(|&m| m.sqrt() >= ya.lo && m.sqrt() <= ya.hi)
97 .map(|m| (px + xa.frac(m) * pw, py + ph - ya.frac(m.sqrt()) * ph))
98 .collect();
99 if line.len() > 1 {
100 c.polyline(&line, MUTED, 0.9, true);
101 }
102}
103
104pub fn figure(d: &StatData) -> String {
106 let (cols, pw, ph) = (3usize, 320.0, 230.0);
107 let (left, top) = (16.0, 74.0);
108 let (w, h) = (left * 2.0 + cols as f64 * pw, top + 2.0 * ph + 12.0);
109 let mut c = Canvas::new(w, h);
110 let n = d.values[0].len();
111 c.bold(
112 left + 8.0,
113 28.0,
114 &format!("{} · {} statistics", d.title, d.side.trim_end_matches('s')),
115 15.0,
116 Anchor::Start,
117 INK,
118 );
119 c.text(
120 left + 8.0,
121 46.0,
122 &format!("{n} {}", d.side),
123 10.0,
124 Anchor::Start,
125 MUTED,
126 );
127 let mut boxes = (0..6).map(|k| {
128 (
129 left + (k % cols) as f64 * pw,
130 top + (k / cols) as f64 * ph,
131 pw - 12.0,
132 ph - 14.0,
133 )
134 });
135 for k in 0..4 {
136 draw_stat(d, k, &mut c, boxes.next().unwrap());
137 }
138 draw_mean_sd(d, &mut c, boxes.next().unwrap());
139 c.finish()
140}