liftof_tui/
widgets.rs

1use std::collections::VecDeque;
2
3use ratatui::symbols;
4use ratatui::symbols::line::*;
5use ratatui::text::Span;
6use ratatui::style::{
7    Modifier,
8    Color,
9    Style,
10};
11use ratatui::widgets::{
12    Axis,
13    Block,
14    BorderType,
15    BarChart,
16    GraphType,
17    Dataset,
18    Chart,
19    LineGauge,
20    Borders,
21};
22
23use ndhistogram::{
24    //ndhistogram,
25    Histogram,
26    Hist1D,
27};
28use ndhistogram::axis::{
29    Uniform,
30};
31
32pub const LG_LINE_HORIZONTAL : &str = "░";
33pub const LG_LINE: Set = Set {
34        vertical         : THICK_VERTICAL,
35        //horizontal       : THICK_HORIZONTAL,
36        horizontal       : LG_LINE_HORIZONTAL,
37        top_right        : THICK_TOP_RIGHT,
38        top_left         : THICK_TOP_LEFT,
39        bottom_right     : THICK_BOTTOM_RIGHT,
40        bottom_left      : THICK_BOTTOM_LEFT,
41        vertical_left    : THICK_VERTICAL_LEFT,
42        vertical_right   : THICK_VERTICAL_RIGHT,
43        horizontal_down  : THICK_HORIZONTAL_DOWN,
44        horizontal_up    : THICK_HORIZONTAL_UP,
45        cross            : THICK_CROSS,
46};
47
48//extern crate num_traits;
49//use num_traits::Num;
50
51use crate::colors::{
52  ColorTheme,
53};
54
55
56//#[derive(Debug, Clone)]
57//struct HistoDisplay {
58//  pub nbin     : usize,
59//  pub bin_low  : f32,
60//  pub bin_high : f32,
61//  pub histo    : Hist1D<Uniform<f32>>,
62//}
63//
64//impl HistoDisplay {
65//  pub fn new(nbin : usize, bin_low : f32, bin_high : f32) -> Self {
66//    let bins = Uniform::new(nbin, bin_low, bin_high);  
67//    Self {
68//      nbin     : nbin,
69//      bin_low  : bin_low,
70//      bin_high : bin_high,
71//      histo    : ndhistogram!(bins), 
72//    }
73//  }
74//}
75
76
77/// Adapt the bins of the histogram for the 
78/// bar chart which will get rendered.
79/// Always show a minimum number of bins, 
80/// but if the max y-bin is "too far to the left"
81/// then shorten the range for a better visualization
82///
83/// # Arguments
84///
85/// * labels       : bin labels for rendering
86/// * clean_from   : leave bins below this 
87///                  untouched
88pub fn clean_data<'a>(histo      : &'a Hist1D<Uniform<f32>>, 
89                      labels     : &'a Vec<String>, 
90                      clean_from : usize) -> Vec<(&'a str,u64)> {
91  let mut max_pop_bin = 0;
92  let mut vec_index   = 0;
93  let mut bins = Vec::<(u64, u64)>::new();
94  for bin in histo.iter() {
95    let bin_value = *bin.value as u64;
96    bins.push((bin.index as u64, bin_value));
97    // always show the first x bins, but if 
98    // the bins with index > clean_from are not 
99    // populated, discard them
100    if bin_value > 0 && bin.index > clean_from {
101      max_pop_bin = vec_index;
102    }
103    vec_index += 1;
104  }
105  bins.retain(|&(x,_)| x <= max_pop_bin);
106  let mut clean_data = Vec::<(&str, u64)>::new();
107  for n in bins.iter() {
108    clean_data.push((&labels[n.0 as usize], n.1));
109  }
110  clean_data
111}
112
113/// Create the labels for a certain histogram
114/// for rendering
115pub fn create_labels(histo : &Hist1D<Uniform<f32>>) -> Vec<String> {
116  let mut labels = Vec::<String>::new();
117  for bin in histo.iter() {
118    match bin.bin.start() {
119      None => {
120        labels.push(String::from("x"));
121      },
122      Some(value) => {
123        labels.push(format!("{}", value as u64));
124      }
125    }
126  }
127  labels
128}
129
130// FIXME - merge this with clean data
131/// Prepare data for histogram widget
132///
133/// # Arguments:
134///
135/// * remove_uf   : Remove underflow bin
136pub fn prep_data<'a>(histo      : &'a Hist1D<Uniform<f32>>, 
137                     labels     : &'a Vec<String>,
138                     spacing    : usize,
139                     remove_uf  : bool) -> Vec<(&'a str,u64)> {
140  let mut data = Vec::<(&str, u64)>::new();
141  for (k,bin) in histo.iter().enumerate() {
142    if k == 0 && remove_uf {
143      continue;
144    }
145    if k == 1 && remove_uf {
146      data.push((&labels[k], *bin.value as u64));
147      continue;
148    }
149    // k+1 to account for underflow bin
150    if k % spacing != 0 {
151      data.push(("-", *bin.value as u64));
152    } else {
153      data.push((&labels[k], *bin.value as u64));
154    }
155  }
156  data
157}
158
159pub fn histogram<'a>(hist_data : Vec<(&'a str, u64)>,
160                     title     : String,
161                     bar_width : u16,
162                     bar_gap   : u16,
163                     theme     : &ColorTheme) -> BarChart<'a> {
164  //let bins = Uniform::new(nbin, bin_low, bin_high);
165  //let mut histo = ndhistogram!(bins);
166  //for k in data {
167  //  histo.fill(&k);
168  //}
169  let chart  = BarChart::default()
170    .block(Block::default().title(title).borders(Borders::ALL))
171    .data(hist_data.as_slice())
172    .bar_width(bar_width)
173    .bar_gap(bar_gap)
174    //.bar_style(Style::default().fg(Color::Blue))
175    .bar_style(theme.style())
176    .value_style(
177      theme.style()
178      //Style::default()
179      //.bg(Color::Blue)
180      .add_modifier(Modifier::BOLD),
181    )
182    .style(theme.background());
183  chart
184}
185
186pub fn timeseries<'a>(data        : &'a mut VecDeque<(f64,f64)>,
187                      ds_name     : String,
188                      xlabel      : String,
189                      theme       : &'a ColorTheme) -> Chart<'a> {
190  let x_only : Vec::<f64> = data.iter().map(|z| z.0).collect();
191  // get timing axis
192  let t_min : u64;
193  let mut t_max : u64;
194  if x_only.len() == 0 {
195    t_min = 0;
196    t_max = 0;
197  } else {   
198    t_min = x_only[0] as u64;
199    t_max = x_only[x_only.len() -1] as u64;
200  }
201  t_max += (0.05*t_max as f64).round() as u64;
202  let t_spacing = (t_max - t_min)/10;
203  let mut t_labels = Vec::<Span>::new();
204  for k in 0..10 {
205    let _label = format!("{}", (t_min + t_spacing * k as u64));
206    t_labels.push(Span::from(_label));
207  }
208
209  let y_only : Vec::<f64> = data.iter().map(|z| z.1).collect();
210  let mut y_min = f64::MAX;
211  let mut y_max = f64::MIN;
212  if y_only.len() == 0 {
213    y_max = 0.0;
214    y_min = 0.0;
215  }
216  for y in y_only {
217    if y < y_min {
218      y_min = y;
219    }
220    if y > y_max {
221      y_max = y;
222    }
223  }
224  y_max += f64::abs(y_max)*0.05;
225  y_min -= f64::abs(y_min)*0.05;
226  let y_spacing = f64::abs(y_max - y_min)/5.0;
227  let mut y_labels = Vec::<Span>::new() ;
228  let mut precision = 0u8;
229  if f64::abs(y_max - y_min) <= 10.0 {
230    precision = 1;
231  }
232  if f64::abs(y_max - y_min) <= 1.0 {
233    precision = 2;
234  }
235  for k in 0..5 {
236    match precision {
237      0 => {
238        let _label = format!("{}", (y_min + y_spacing * k as f64).round() as i64);
239        y_labels.push(Span::from(_label));
240      },
241      1 => {
242        let _label = format!("{:.1}", (y_min + y_spacing * k as f64));
243        y_labels.push(Span::from(_label));
244      },
245      2 => {
246        let _label = format!("{:.2}", (y_min + y_spacing * k as f64));
247        y_labels.push(Span::from(_label));
248      },
249      _ => (),
250    }
251  }
252
253  let dataset = vec![Dataset::default()
254      .name(ds_name)
255      .marker(symbols::Marker::Braille)
256      .graph_type(GraphType::Line)
257      .style(theme.style())
258      .data(data.make_contiguous())];
259  let chart = Chart::new(dataset)
260    .block(
261      Block::default()
262        .borders(Borders::ALL)
263        .style(theme.style())
264        .title(xlabel )
265        .border_type(BorderType::Rounded),
266    )
267    .x_axis(Axis::default()
268      .title(Span::styled("MET [s]", Style::default().fg(Color::White)))
269      .style(theme.style())
270      .bounds([t_min as f64, t_max as f64])
271      //.labels(t_labels.clone().iter().cloned().map(Span::from).collect()))
272      .labels(t_labels.clone())
273    )
274    .y_axis(Axis::default()
275      //.title(Span::styled("T [\u{00B0}C]", Style::default().fg(Color::White)))
276      .style(theme.style())
277      .bounds([y_min as f64, y_max as f64])
278      //.labels(y_labels.clone().iter().cloned().map(Span::from).collect()))
279      .labels(y_labels.clone())
280    )
281    .style(theme.style());
282    chart
283}
284
285
286/// A simple line gauge, that is bacically a progress bar
287pub fn gauge(title : String,
288             label : String,
289             ratio : f64,
290             theme : &ColorTheme) -> LineGauge<'_> {
291    let gauge = LineGauge::default()
292      .block(
293        Block::default()
294        .borders(Borders::ALL)
295        .style(theme.style())
296        .title(title)
297        .border_type(BorderType::Rounded)
298      )
299      .filled_style(
300        Style::default()
301          .fg(theme.hc)
302          .bg(theme.bg1)
303          .add_modifier(Modifier::BOLD)
304      )
305      //.use_unicode(true)
306      .label(label)
307      //.line_set(symbols::line::THICK)  // THICK
308      .line_set(LG_LINE)
309      //.percent(self.disk_usage as u16);
310      .ratio(ratio);
311    gauge
312}
313