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