1use crate::charts::NamedLine;
2use crate::money::none;
3use crate::render::{filled, Named, Piece};
4use patterns_macros::because;
5
6pub type Reading = (u64, Vec<f64>);
7
8pub fn readings_between(readings: Vec<Reading>, from: u64, to: u64) -> Vec<Reading> {
9 readings.into_iter().filter(|(day, _)| *day >= from && *day <= to).collect()
10}
11because!(readings_between, "the readings inside a day range, both ends included, the range a reader picked in the date filter");
12
13pub fn readings_paged(readings: Vec<Reading>, newest_first: bool, page: usize, per_page: usize) -> (Vec<Reading>, bool) {
14 let ordered: Vec<Reading> = match newest_first {
15 true => readings.into_iter().rev().collect(),
16 false => readings,
17 };
18 let start = page.saturating_mul(per_page).min(ordered.len());
19 let end = start.saturating_add(per_page).saturating_add(1).min(ordered.len());
20 let slice = &ordered[start..end];
21 let has_more = slice.len() > per_page;
22 (slice.iter().take(per_page).cloned().collect(), has_more)
23}
24because!(readings_paged, "one page of readings in the direction asked for, with one more read than shown so the page knows whether another follows");
25
26pub fn column_points(readings: &[Reading], column: usize) -> Vec<(u64, f64)> {
27 readings
28 .iter()
29 .map(|(day, values)| (*day, values.get(column).copied().unwrap_or(none())))
30 .collect()
31}
32because!(column_points, "one column of a table of readings as chart points, so a chart is drawn from the same numbers the table shows");
33
34pub fn readings_keyed<K: Clone>(readings: Vec<Reading>, keys: &[K]) -> Vec<(u64, Vec<(K, f64)>)> {
35 readings
36 .into_iter()
37 .map(|(day, values)| (day, keys.iter().cloned().zip(values).collect()))
38 .collect()
39}
40because!(readings_keyed, "readings with each value paired to its column key in order, the shape a table row is answered in, with a value past the columns dropped and a column past the values absent");
41
42pub trait Charted {
43 type Col: PartialEq + Copy;
44 type Ph: Named + 'static;
45 type Reach: PartialEq + Copy;
46 fn lines(&self) -> Vec<(&'static [Piece<Self::Ph>], Self::Col, Self::Reach)>;
47}
48because!(Charted, "what a chart definition tells the renderer: each line's name pieces, the column it draws and where it applies, so the renderer draws a spec's charts without naming them");
49
50pub fn chart_series_of<C: Charted>(charts: &[C], readings: &[Reading], columns: &[C::Col], allowed: &[C::Reach], first: &str, second: &str) -> Vec<Vec<NamedLine>> {
51 charts
52 .iter()
53 .map(|chart| {
54 chart
55 .lines()
56 .into_iter()
57 .filter(|(_, _, reach)| allowed.contains(reach))
58 .filter_map(|(pieces, column, _)| {
59 let at = columns.iter().position(|c| *c == column)?;
60 Some((filled(pieces, first, second, "", ""), column_points(readings, at)))
61 })
62 .collect()
63 })
64 .collect()
65}
66because!(chart_series_of, "every chart's lines drawn from the table's readings, each line named from its pieces and read from the column it names, with the lines that do not apply on this deployment left out");