use std::time::Duration;
use tui::{
layout::Rect,
style::Style,
symbols,
widgets::{Block, Widget},
};
pub(crate) struct MiniHistogram<'a> {
block: Option<Block<'a>>,
style: Style,
data: &'a [u64],
metadata: HistogramMetadata,
max: Option<u64>,
bar_set: symbols::bar::Set,
duration_precision: usize,
}
#[derive(Debug, Default)]
pub(crate) struct HistogramMetadata {
pub(crate) max_value: u64,
pub(crate) min_value: u64,
pub(crate) max_bucket: u64,
pub(crate) min_bucket: u64,
pub(crate) high_outliers: u64,
pub(crate) highest_outlier: Option<Duration>,
}
impl<'a> Default for MiniHistogram<'a> {
fn default() -> Self {
MiniHistogram {
block: None,
style: Default::default(),
data: &[],
metadata: Default::default(),
max: None,
bar_set: symbols::bar::NINE_LEVELS,
duration_precision: 4,
}
}
}
impl<'a> Widget for MiniHistogram<'a> {
fn render(mut self, area: tui::layout::Rect, buf: &mut tui::buffer::Buffer) {
let inner_area = match self.block.take() {
Some(b) => {
let inner_area = b.inner(area);
b.render(area, buf);
inner_area
}
None => area,
};
if inner_area.height < 1 {
return;
}
let max_qty_label = self.metadata.max_bucket.to_string();
let min_qty_label = self.metadata.min_bucket.to_string();
let max_record_label = format!(
"{:.prec$?}",
Duration::from_nanos(self.metadata.max_value),
prec = self.duration_precision,
);
let min_record_label = format!(
"{:.prec$?}",
Duration::from_nanos(self.metadata.min_value),
prec = self.duration_precision,
);
let y_axis_label_width = max_qty_label.len() as u16;
self.render_legend(
inner_area,
buf,
max_record_label,
min_record_label,
max_qty_label,
min_qty_label,
);
let legend_height = if self.metadata.high_outliers > 0 {
2
} else {
1
};
let bars_area = Rect {
x: inner_area.x + y_axis_label_width,
y: inner_area.y,
width: inner_area.width - y_axis_label_width,
height: inner_area.height - legend_height,
};
self.render_bars(bars_area, buf);
}
}
impl<'a> MiniHistogram<'a> {
fn render_legend(
&mut self,
area: tui::layout::Rect,
buf: &mut tui::buffer::Buffer,
max_record_label: String,
min_record_label: String,
max_qty_label: String,
min_qty_label: String,
) {
let labels_pos = if self.metadata.high_outliers > 0 {
let outliers = format!(
"{} outliers (highest: {:?})",
self.metadata.high_outliers,
self.metadata
.highest_outlier
.expect("if there are outliers, the highest should be set")
);
buf.set_string(
area.right() - outliers.len() as u16,
area.bottom() - 1,
&outliers,
Style::default(),
);
2
} else {
1
};
buf.set_string(area.left(), area.top(), &max_qty_label, Style::default());
let zero_label = format!("{:>width$}", &min_qty_label, width = max_qty_label.len());
buf.set_string(
area.left(),
area.bottom() - labels_pos,
&zero_label,
Style::default(),
);
buf.set_string(
area.left() + max_qty_label.len() as u16,
area.bottom() - labels_pos,
&min_record_label,
Style::default(),
);
buf.set_string(
area.right() - max_record_label.len() as u16,
area.bottom() - labels_pos,
&max_record_label,
Style::default(),
);
}
fn render_bars(&mut self, area: tui::layout::Rect, buf: &mut tui::buffer::Buffer) {
let max = match self.max {
Some(v) => v,
None => *self.data.iter().max().unwrap_or(&1u64),
};
let max_index = std::cmp::min(area.width as usize, self.data.len());
let mut data = self
.data
.iter()
.take(max_index)
.map(|e| {
if max != 0 {
let r = e * u64::from(area.height) * 8 / max;
if *e > 0 && r == 0 {
1
} else {
r
}
} else {
0
}
})
.collect::<Vec<u64>>();
for j in (0..area.height).rev() {
for (i, d) in data.iter_mut().enumerate() {
let symbol = match *d {
0 => self.bar_set.empty,
1 => self.bar_set.one_eighth,
2 => self.bar_set.one_quarter,
3 => self.bar_set.three_eighths,
4 => self.bar_set.half,
5 => self.bar_set.five_eighths,
6 => self.bar_set.three_quarters,
7 => self.bar_set.seven_eighths,
_ => self.bar_set.full,
};
buf.get_mut(area.left() + i as u16, area.top() + j)
.set_symbol(symbol)
.set_style(self.style);
if *d > 8 {
*d -= 8;
} else {
*d = 0;
}
}
}
}
pub fn duration_precision(mut self, precision: usize) -> MiniHistogram<'a> {
self.duration_precision = precision;
self
}
#[allow(dead_code)]
pub fn block(mut self, block: Block<'a>) -> MiniHistogram<'a> {
self.block = Some(block);
self
}
#[allow(dead_code)]
pub fn style(mut self, style: Style) -> MiniHistogram<'a> {
self.style = style;
self
}
#[allow(dead_code)]
pub fn data(mut self, data: &'a [u64]) -> MiniHistogram<'a> {
self.data = data;
self
}
#[allow(dead_code)]
pub fn metadata(mut self, metadata: HistogramMetadata) -> MiniHistogram<'a> {
self.metadata = metadata;
self
}
#[allow(dead_code)]
pub fn max(mut self, max: u64) -> MiniHistogram<'a> {
self.max = Some(max);
self
}
#[allow(dead_code)]
pub fn bar_set(mut self, bar_set: symbols::bar::Set) -> MiniHistogram<'a> {
self.bar_set = bar_set;
self
}
}