adguardian/widgets/
chart.rs1
2use tui::{
3 style::{Color, Modifier, Style},
4 widgets::{Axis, Block, Borders, Dataset, Chart},
5 text::{Span},
6 symbols,
7};
8
9use crate::fetch::fetch_stats::{StatsResponse};
10
11
12pub fn make_history_chart(stats: &StatsResponse) -> Chart<'_> {
13 let datasets = make_history_datasets(stats);
15 let (x_bound, y_bound) = find_bounds(stats);
17 let x_labels = generate_x_labels(stats.dns_queries.len() as i32, 5);
19 let y_labels = generate_y_labels(y_bound as i32, 5);
20 let chart = Chart::new(datasets)
22 .block(
23 Block::default()
24 .title(Span::styled(
25 "History",
26 Style::default().add_modifier(Modifier::BOLD),
27 ))
28 .borders(Borders::ALL)
29 )
30 .x_axis(
31 Axis::default()
32 .title("Time (Days ago)")
33 .bounds([0.0, x_bound])
34 .labels(x_labels),
35 )
36 .y_axis(Axis::default().title("Query Count").labels(y_labels).bounds([0.0, y_bound]));
37
38 chart
39}
40
41fn make_history_datasets(stats: &StatsResponse) -> Vec<Dataset<'_>> {
43 let dns_queries_dataset = Dataset::default()
44 .name("DNS Queries")
45 .marker(symbols::Marker::Braille)
46 .style(Style::default().fg(Color::Green))
47 .data(&stats.dns_queries_chart);
48
49 let blocked_filtering_dataset = Dataset::default()
50 .name("Blocked Filtering")
51 .marker(symbols::Marker::Braille)
52 .style(Style::default().fg(Color::Red))
53 .data(&stats.blocked_filtering_chart);
54
55 let datasets = vec![dns_queries_dataset, blocked_filtering_dataset];
56
57 datasets
58}
59
60fn find_bounds(stats: &StatsResponse) -> (f64, f64) {
62 let mut max_length = 0;
63 let mut max_value = f64::MIN;
64
65 for dataset in &[&stats.dns_queries_chart, &stats.blocked_filtering_chart] {
66 let length = dataset.len();
67 if length > max_length {
68 max_length = length;
69 }
70
71 let max_in_dataset = dataset
72 .iter()
73 .map(|&(_, y)| y)
74 .fold(f64::MIN, f64::max);
75 if max_in_dataset > max_value {
76 max_value = max_in_dataset;
77 }
78 }
79 (max_length as f64, max_value)
80}
81
82fn generate_y_labels(max: i32, count: usize) -> Vec<Span<'static>> {
84 let step = max / (count - 1) as i32;
85
86 (0..count)
87 .map(|x| Span::raw(format!("{}", x * step as usize)))
88 .collect::<Vec<Span<'static>>>()
89}
90
91fn generate_x_labels(max_days: i32, num_labels: i32) -> Vec<Span<'static>> {
93 let step = max_days / (num_labels - 1);
94 (0..num_labels)
95 .map(|i| {
96 let day = (max_days - i * step).to_string();
97 if i == num_labels - 1 {
98 Span::styled("Today", Style::default().add_modifier(Modifier::BOLD))
99 } else {
100 Span::raw(day)
101 }
102 })
103 .collect()
104}
105
106fn convert_to_chart_data(data: Vec<f64>) -> Vec<(f64, f64)> {
108 data.iter().enumerate().map(|(i, &v)| (i as f64, v)).collect()
109}
110
111fn interpolate(input: &Vec<f64>, points_between: usize) -> Vec<f64> {
113 let mut output = Vec::new();
114
115 for window in input.windows(2) {
116 let start = window[0];
117 let end = window[1];
118 let step = (end - start) / (points_between as f64 + 1.0);
119
120 output.push(start);
121 for i in 1..=points_between {
122 output.push(start + step * i as f64);
123 }
124 }
125
126 output.push(*input.last().unwrap());
127 output
128}
129
130pub fn prepare_chart_data(stats: &mut StatsResponse) {
132 let dns_queries = stats.dns_queries.iter().map(|&v| v as f64).collect::<Vec<_>>();
133 let interpolated_dns_queries = interpolate(&dns_queries, 3);
134 stats.dns_queries_chart = convert_to_chart_data(interpolated_dns_queries);
135
136 let blocked_filtering: Vec<f64> = stats.blocked_filtering.iter()
137 .zip(&stats.replaced_safebrowsing)
138 .zip(&stats.replaced_parental)
139 .map(|((&b, &s), &p)| (b + s + p) as f64)
140 .collect();
141
142 let interpolated_blocked_filtering = interpolate(&blocked_filtering, 3);
143 let blocked_filtering_chart: Vec<(f64, f64)> = convert_to_chart_data(interpolated_blocked_filtering);
144
145 stats.blocked_filtering_chart = blocked_filtering_chart;
146}