Compare commits
4 Commits
rect-offse
...
kd/histogr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8be13ca74e | ||
|
|
b06557bbaf | ||
|
|
3b002fdcab | ||
|
|
0207160784 |
@@ -1,20 +1,20 @@
|
||||
# Examples
|
||||
|
||||
This folder contains unreleased code. View the [examples for the latest release
|
||||
(0.25.0)](https://github.com/ratatui-org/ratatui/tree/v0.25.0/examples) instead.
|
||||
This folder might use unreleased code. View the examples for the latest release instead.
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> There are backwards incompatible changes in these examples, as they are designed to compile
|
||||
> There may be backwards incompatible changes in these examples, as they are designed to compile
|
||||
> against the `main` branch.
|
||||
>
|
||||
> There are a few workaround for this problem:
|
||||
>
|
||||
> - View the examples as they were when the latest version was release by selecting the tag that
|
||||
> matches that version. E.g. <https://github.com/ratatui-org/ratatui/tree/v0.25.0/examples>. There
|
||||
> is a combo box at the top of this page which allows you to select any previous tagged version.
|
||||
> - To view the code locally, checkout the tag using `git switch --detach v0.25.0`.
|
||||
> - Use the latest [alpha version of Ratatui]. These are released weekly on Saturdays.
|
||||
> matches that version. E.g. <https://github.com/ratatui-org/ratatui/tree/v0.26.1/examples>.
|
||||
> - If you're viewing this file on GitHub, there is a combo box at the top of this page which
|
||||
> allows you to select any previous tagged version.
|
||||
> - To view the code locally, checkout the tag. E.g. `git switch --detach v0.26.1`.
|
||||
> - Use the latest [alpha version of Ratatui] in your app. These are released weekly on Saturdays.
|
||||
> - Compile your code against the main branch either locally by adding e.g. `path = "../ratatui"` to
|
||||
> the dependency, or remotely by adding `git = "https://github.com/ratatui-org/ratatui"`
|
||||
>
|
||||
|
||||
108
examples/histogram.rs
Normal file
108
examples/histogram.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
// use crossterm::{
|
||||
// event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
// execute,
|
||||
// terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
// };
|
||||
// use ratatui::{
|
||||
// backend::{Backend, CrosstermBackend},
|
||||
// layout::{Constraint, Direction, Layout},
|
||||
// style::{Color, Style},
|
||||
// widgets::{Block, Borders, Histogram},
|
||||
// Frame, Terminal,
|
||||
// };
|
||||
use std::{
|
||||
error::Error,
|
||||
io,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
//
|
||||
// use rand::{rngs::ThreadRng, thread_rng, Rng};
|
||||
//
|
||||
// struct App {
|
||||
// data: Vec<u64>,
|
||||
// size: usize,
|
||||
// rng: ThreadRng,
|
||||
// }
|
||||
//
|
||||
// impl App {
|
||||
// fn new(size: usize) -> App {
|
||||
// let data = vec![0; size];
|
||||
// App {
|
||||
// data,
|
||||
// rng: thread_rng(),
|
||||
// size,
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// fn on_tick(&mut self) {
|
||||
// for i in 0..self.size {
|
||||
// self.data[i] = self.rng.gen_range(0..100);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
// // setup terminal
|
||||
// enable_raw_mode()?;
|
||||
// let mut stdout = io::stdout();
|
||||
// execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
// let backend = CrosstermBackend::new(stdout);
|
||||
// let mut terminal = Terminal::new(backend)?;
|
||||
//
|
||||
// // create app and run it
|
||||
// let tick_rate = Duration::from_millis(250);
|
||||
// let app = App::new(100);
|
||||
// let res = run_app(&mut terminal, app, tick_rate);
|
||||
//
|
||||
// // restore terminal
|
||||
// disable_raw_mode()?;
|
||||
// execute!(
|
||||
// terminal.backend_mut(),
|
||||
// LeaveAlternateScreen,
|
||||
// DisableMouseCapture
|
||||
// )?;
|
||||
// terminal.show_cursor()?;
|
||||
//
|
||||
// if let Err(err) = res {
|
||||
// println!("{:?}", err)
|
||||
// }
|
||||
//
|
||||
Ok(())
|
||||
}
|
||||
//
|
||||
// fn run_app<B: Backend>(
|
||||
// terminal: &mut Terminal<B>,
|
||||
// mut app: App,
|
||||
// tick_rate: Duration,
|
||||
// ) -> io::Result<()> { let mut last_tick = Instant::now(); loop { terminal.draw(|f| ui(f, &app))?;
|
||||
//
|
||||
// let timeout = tick_rate
|
||||
// .checked_sub(last_tick.elapsed())
|
||||
// .unwrap_or_else(|| Duration::from_secs(0));
|
||||
// if crossterm::event::poll(timeout)? {
|
||||
// if let Event::Key(key) = event::read()? {
|
||||
// if let KeyCode::Char('q') = key.code {
|
||||
// return Ok(());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if last_tick.elapsed() >= tick_rate {
|
||||
// app.on_tick();
|
||||
// last_tick = Instant::now();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
|
||||
// let chunks = Layout::default()
|
||||
// .direction(Direction::Vertical)
|
||||
// .margin(2)
|
||||
// .constraints([Constraint::Percentage(100)].as_ref())
|
||||
// .split(f.size());
|
||||
// let histogram = Histogram::default()
|
||||
// .block(Block::default().title("Data1").borders(Borders::ALL))
|
||||
// .data(&app.data, 10)
|
||||
// .bar_style(Style::default().fg(Color::Yellow))
|
||||
// .value_style(Style::default().fg(Color::Black).bg(Color::Yellow));
|
||||
// f.render_widget(histogram, chunks[0]);
|
||||
// }
|
||||
101
src/text/line.rs
101
src/text/line.rs
@@ -453,6 +453,39 @@ impl<'a> Line<'a> {
|
||||
self.spans.iter_mut()
|
||||
}
|
||||
|
||||
/// Returns a line that's truncated corresponding to it's alignment and result width
|
||||
#[must_use = "method returns the modified value"]
|
||||
fn truncated(&'a self, result_width: u16) -> Self {
|
||||
let mut truncated_line = Line::default();
|
||||
let width = self.width() as u16;
|
||||
let mut offset = match self.alignment {
|
||||
Some(Alignment::Center) => (width.saturating_sub(result_width)) / 2,
|
||||
Some(Alignment::Right) => width.saturating_sub(result_width),
|
||||
_ => 0,
|
||||
};
|
||||
let mut x = 0;
|
||||
for span in &self.spans {
|
||||
let span_width = span.width() as u16;
|
||||
if offset >= span_width {
|
||||
offset -= span_width;
|
||||
continue;
|
||||
}
|
||||
let mut new_span = span.clone();
|
||||
let new_span_width = span_width - offset;
|
||||
if x + new_span_width > result_width {
|
||||
let span_end = (result_width - x + offset) as usize;
|
||||
new_span.content = Cow::from(&span.content[offset as usize..span_end]);
|
||||
truncated_line.spans.push(new_span);
|
||||
break;
|
||||
}
|
||||
|
||||
new_span.content = Cow::from(&span.content[offset as usize..]);
|
||||
truncated_line.spans.push(new_span);
|
||||
x += new_span_width;
|
||||
offset = 0;
|
||||
}
|
||||
truncated_line
|
||||
}
|
||||
/// Adds a span to the line.
|
||||
///
|
||||
/// `span` can be any type that is convertible into a `Span`. For example, you can pass a
|
||||
@@ -554,17 +587,23 @@ impl WidgetRef for Line<'_> {
|
||||
let area = area.intersection(buf.area);
|
||||
buf.set_style(area, self.style);
|
||||
let width = self.width() as u16;
|
||||
let offset = match self.alignment {
|
||||
Some(Alignment::Center) => (area.width.saturating_sub(width)) / 2,
|
||||
Some(Alignment::Right) => area.width.saturating_sub(width),
|
||||
Some(Alignment::Left) | None => 0,
|
||||
let mut x = area.left();
|
||||
let line = if width > area.width {
|
||||
self.truncated(area.width)
|
||||
} else {
|
||||
let offset = match self.alignment {
|
||||
Some(Alignment::Center) => (area.width.saturating_sub(width)) / 2,
|
||||
Some(Alignment::Right) => area.width.saturating_sub(width),
|
||||
Some(Alignment::Left) | None => 0,
|
||||
};
|
||||
x = x.saturating_add(offset);
|
||||
self.to_owned()
|
||||
};
|
||||
let mut x = area.left().saturating_add(offset);
|
||||
for span in &self.spans {
|
||||
for span in &line.spans {
|
||||
let span_width = span.width() as u16;
|
||||
let span_area = Rect {
|
||||
x,
|
||||
width: span_width.min(area.right() - x),
|
||||
width: span_width.min(area.right().saturating_sub(x)),
|
||||
..area
|
||||
};
|
||||
span.render(span_area, buf);
|
||||
@@ -604,6 +643,7 @@ mod tests {
|
||||
use rstest::{fixture, rstest};
|
||||
|
||||
use super::*;
|
||||
use crate::assert_buffer_eq;
|
||||
|
||||
#[fixture]
|
||||
fn small_buf() -> Buffer {
|
||||
@@ -847,6 +887,53 @@ mod tests {
|
||||
|
||||
assert_eq!(format!("{line_from_styled_span}"), "Hello, world!");
|
||||
}
|
||||
#[test]
|
||||
fn render_truncates_left() {
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
|
||||
Line::from("Hello world")
|
||||
.left_aligned()
|
||||
.render(buf.area, &mut buf);
|
||||
let expected = Buffer::with_lines(vec!["Hello"]);
|
||||
assert_buffer_eq!(buf, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_truncates_right() {
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
|
||||
Line::from("Hello world")
|
||||
.right_aligned()
|
||||
.render(buf.area, &mut buf);
|
||||
let expected = Buffer::with_lines(vec!["world"]);
|
||||
assert_buffer_eq!(buf, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_truncates_center() {
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
|
||||
Line::from("Hello world")
|
||||
.centered()
|
||||
.render(buf.area, &mut buf);
|
||||
let expected = Buffer::with_lines(vec!["lo wo"]);
|
||||
assert_buffer_eq!(buf, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truncate_line_with_multiple_spans() {
|
||||
let line = Line::default().spans(vec!["foo", "bar"]);
|
||||
assert_eq!(
|
||||
line.right_aligned().truncated(4).to_string(),
|
||||
String::from("obar")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truncation_ignores_useless_spans() {
|
||||
let line = Line::default().spans(vec!["foo", "bar"]);
|
||||
assert_eq!(
|
||||
line.right_aligned().truncated(3),
|
||||
Line::default().spans(vec!["bar"])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn left_aligned() {
|
||||
|
||||
@@ -30,6 +30,7 @@ pub mod canvas;
|
||||
mod chart;
|
||||
mod clear;
|
||||
mod gauge;
|
||||
mod histogram;
|
||||
mod list;
|
||||
mod paragraph;
|
||||
mod reflow;
|
||||
@@ -45,6 +46,7 @@ pub use self::{
|
||||
chart::{Axis, Chart, Dataset, GraphType, LegendPosition},
|
||||
clear::Clear,
|
||||
gauge::{Gauge, LineGauge},
|
||||
histogram::Histogram,
|
||||
list::{List, ListDirection, ListItem, ListState},
|
||||
paragraph::{Paragraph, Wrap},
|
||||
scrollbar::{ScrollDirection, Scrollbar, ScrollbarOrientation, ScrollbarState},
|
||||
|
||||
187
src/widgets/histogram.rs
Normal file
187
src/widgets/histogram.rs
Normal file
@@ -0,0 +1,187 @@
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
style::Style,
|
||||
symbols,
|
||||
widgets::{Block, Widget},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
struct Bucket {
|
||||
low: f64,
|
||||
high: f64,
|
||||
count: usize,
|
||||
}
|
||||
|
||||
/// A bar chart specialized for showing histograms
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tui::widgets::{Block, Borders, Histogram};
|
||||
/// # use tui::style::{Style, Color, Modifier};
|
||||
/// Histogram::default()
|
||||
/// .block(Block::default().title("Histogram").borders(Borders::ALL))
|
||||
/// .bar_width(3)
|
||||
/// .bar_gap(1)
|
||||
/// .bar_style(Style::default().fg(Color::Yellow).bg(Color::Red))
|
||||
/// .value_style(Style::default().fg(Color::Red).add_modifier(Modifier::BOLD))
|
||||
/// .label_style(Style::default().fg(Color::White))
|
||||
/// .data(&[("B0", 0), ("B1", 2), ("B2", 4), ("B3", 3)])
|
||||
/// .max(4);
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Histogram<'a> {
|
||||
/// Block to wrap the widget in
|
||||
block: Option<Block<'a>>,
|
||||
/// The gap between each bar
|
||||
bar_gap: u16,
|
||||
/// Set of symbols used to display the data
|
||||
bar_set: symbols::bar::Set,
|
||||
/// Style of the bars
|
||||
bar_style: Style,
|
||||
/// Style of the values printed at the bottom of each bar
|
||||
value_style: Style,
|
||||
/// Style of the labels printed under each bar
|
||||
label_style: Style,
|
||||
/// Style for the widget
|
||||
style: Style,
|
||||
/// Slice of values to plot on the chart
|
||||
data: &'a [f64],
|
||||
/// each bucket keeps a count of the data points that fall into it
|
||||
/// buckets[0].count counts items where buckets[0].low <= x < buckets[0].high
|
||||
/// buckets[1].count counts items where buckets[1].low <= x < buckets[1].high
|
||||
/// etc.
|
||||
buckets: Vec<Bucket>,
|
||||
/// Value necessary for a bar to reach the maximum height (if no value is specified,
|
||||
/// the maximum value in the data is taken as reference)
|
||||
max: Option<u64>,
|
||||
/// Values to display on the bar (computed when the data is passed to the widget)
|
||||
values: Vec<String>,
|
||||
}
|
||||
|
||||
impl<'a> Default for Histogram<'a> {
|
||||
fn default() -> Histogram<'a> {
|
||||
Histogram {
|
||||
block: None,
|
||||
max: None,
|
||||
data: &[],
|
||||
values: Vec::new(),
|
||||
bar_style: Style::default(),
|
||||
bar_gap: 1,
|
||||
bar_set: symbols::bar::NINE_LEVELS,
|
||||
buckets: Vec::new(),
|
||||
value_style: Default::default(),
|
||||
label_style: Default::default(),
|
||||
style: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Histogram<'a> {
|
||||
pub fn data(mut self, data: &'a [f64], n_buckets: u64) -> Histogram<'a> {
|
||||
self.data = data;
|
||||
|
||||
let min = data.iter().cloned().fold(f64::NAN, f64::min);
|
||||
let max = data.iter().cloned().fold(f64::NAN, f64::max);
|
||||
|
||||
let bucket_size = (max - min) / n_buckets as f64;
|
||||
self.buckets = Vec::with_capacity(n_buckets as usize);
|
||||
|
||||
// initialize buckets
|
||||
self.values = Vec::with_capacity(n_buckets as usize);
|
||||
for i in 0..n_buckets {
|
||||
let start = min + bucket_size * i as f64;
|
||||
let bucket = Bucket {
|
||||
low: start,
|
||||
high: start + bucket_size,
|
||||
count: 0,
|
||||
};
|
||||
self.buckets.push(bucket);
|
||||
self.values
|
||||
.push(format!("[{:.1}, {:.1})", bucket.low, bucket.high));
|
||||
}
|
||||
|
||||
// bucketize data
|
||||
for &x in self.data.iter() {
|
||||
let idx: usize = ((x - min) / bucket_size).floor() as usize;
|
||||
if idx < self.buckets.len() {
|
||||
self.buckets[idx].count += 1;
|
||||
} else {
|
||||
// TODO: decide what to do with excess
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn block(mut self, block: Block<'a>) -> Histogram<'a> {
|
||||
self.block = Some(block);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn max(mut self, max: u64) -> Histogram<'a> {
|
||||
self.max = Some(max);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bar_style(mut self, style: Style) -> Histogram<'a> {
|
||||
self.bar_style = style;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bar_gap(mut self, gap: u16) -> Histogram<'a> {
|
||||
self.bar_gap = gap;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bar_set(mut self, bar_set: symbols::bar::Set) -> Histogram<'a> {
|
||||
self.bar_set = bar_set;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn value_style(mut self, style: Style) -> Histogram<'a> {
|
||||
self.value_style = style;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn label_style(mut self, style: Style) -> Histogram<'a> {
|
||||
self.label_style = style;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn style(mut self, style: Style) -> Histogram<'a> {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for Histogram<'a> {
|
||||
fn render(mut self, area: Rect, buf: &mut Buffer) {
|
||||
buf.set_style(area, self.style);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::{assert_eq, assert_ne};
|
||||
|
||||
use super::*;
|
||||
use crate::assert_buffer_eq;
|
||||
|
||||
#[test]
|
||||
fn test_compute_bins() {
|
||||
let data = [0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0];
|
||||
let hist = Histogram::default().data(&data, 3);
|
||||
|
||||
assert_eq!(hist.buckets.len(), 3);
|
||||
assert_eq!(hist.buckets[0].count, 2); // 0.0, 0.5
|
||||
assert_eq!(hist.buckets[1].count, 2); // 1.0, 1.5
|
||||
assert_eq!(hist.buckets[2].count, 2); // 2.0, 2.5
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_render_histogram() {}
|
||||
}
|
||||
230
src/widgets/overlappingbarchart.rs
Normal file
230
src/widgets/overlappingbarchart.rs
Normal file
@@ -0,0 +1,230 @@
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
style::Style,
|
||||
symbols,
|
||||
widgets::{Block, Widget},
|
||||
};
|
||||
use std::cmp::min;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
/// A series for a stacked bar chart
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BarSeries<'a> {
|
||||
/// Name of the series
|
||||
name: Cow<'a, str>,
|
||||
/// The color to display for this series
|
||||
bar_style: Style,
|
||||
/// A reference to the data for this series
|
||||
data: &'a [u64]
|
||||
}
|
||||
|
||||
/// Display multiple bars in a single widgets
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tui::widgets::{Block, Borders, BarChart};
|
||||
/// # use tui::style::{Style, Color, Modifier};
|
||||
/// BarChart::default()
|
||||
/// .block(Block::default().title("BarChart").borders(Borders::ALL))
|
||||
/// .bar_width(3)
|
||||
/// .bar_gap(1)
|
||||
/// .bar_style(Style::default().fg(Color::Yellow).bg(Color::Red))
|
||||
/// .value_style(Style::default().fg(Color::Red).add_modifier(Modifier::BOLD))
|
||||
/// .label_style(Style::default().fg(Color::White))
|
||||
/// .data(&[("B0", 0), ("B1", 2), ("B2", 4), ("B3", 3)])
|
||||
/// .max(4);
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OverlappingBarChart<'a> {
|
||||
/// Block to wrap the widget in
|
||||
block: Option<Block<'a>>,
|
||||
/// The width of each bar
|
||||
bar_width: u16,
|
||||
/// The gap between each bar
|
||||
bar_gap: u16,
|
||||
/// Set of symbols used to display the data
|
||||
bar_set: symbols::bar::Set,
|
||||
/// Style of the bars
|
||||
bar_style: Style,
|
||||
/// Style of the values printed at the bottom of each bar
|
||||
value_style: Style,
|
||||
/// Style of the labels printed under each bar
|
||||
label_style: Style,
|
||||
/// Style for the widget
|
||||
style: Style,
|
||||
/// Vec of slices of (label, value) pair to plot on the chart
|
||||
data: Vec<&'a [(&'a str, u64)]>,
|
||||
/// Value necessary for a bar to reach the maximum height (if no value is specified,
|
||||
/// the maximum value in the data is taken as reference)
|
||||
max: Option<u64>,
|
||||
/// Values to display on the bar (computed when the data is passed to the widget)
|
||||
values: Vec<String>,
|
||||
}
|
||||
|
||||
impl<'a> Default for BarChart<'a> {
|
||||
fn default() -> BarChart<'a> {
|
||||
BarChart {
|
||||
block: None,
|
||||
max: None,
|
||||
data: Vec::new(),
|
||||
values: Vec::new(),
|
||||
bar_style: Style::default(),
|
||||
bar_width: 1,
|
||||
bar_gap: 1,
|
||||
bar_set: symbols::bar::NINE_LEVELS,
|
||||
value_style: Default::default(),
|
||||
label_style: Default::default(),
|
||||
style: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> BarChart<'a> {
|
||||
pub fn data(mut self, data: &'a [(&'a str, u64)]) -> BarChart<'a> {
|
||||
self.data = data;
|
||||
self.values = Vec::with_capacity(self.data.len());
|
||||
for &(_, v) in self.data {
|
||||
self.values.push(format!("{}", v));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn block(mut self, block: Block<'a>) -> BarChart<'a> {
|
||||
self.block = Some(block);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn max(mut self, max: u64) -> BarChart<'a> {
|
||||
self.max = Some(max);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bar_style(mut self, style: Style) -> BarChart<'a> {
|
||||
self.bar_style = style;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bar_width(mut self, width: u16) -> BarChart<'a> {
|
||||
self.bar_width = width;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bar_gap(mut self, gap: u16) -> BarChart<'a> {
|
||||
self.bar_gap = gap;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bar_set(mut self, bar_set: symbols::bar::Set) -> BarChart<'a> {
|
||||
self.bar_set = bar_set;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn value_style(mut self, style: Style) -> BarChart<'a> {
|
||||
self.value_style = style;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn label_style(mut self, style: Style) -> BarChart<'a> {
|
||||
self.label_style = style;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn style(mut self, style: Style) -> BarChart<'a> {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for BarChart<'a> {
|
||||
fn render(mut self, area: Rect, buf: &mut Buffer) {
|
||||
buf.set_style(area, self.style);
|
||||
|
||||
let chart_area = match self.block.take() {
|
||||
Some(b) => {
|
||||
let inner_area = b.inner(area);
|
||||
b.render(area, buf);
|
||||
inner_area
|
||||
}
|
||||
None => area,
|
||||
};
|
||||
|
||||
if chart_area.height < 2 {
|
||||
return;
|
||||
}
|
||||
|
||||
let max = self
|
||||
.max
|
||||
.unwrap_or_else(|| self.data.iter().map(|t| t.1).max().unwrap_or_default());
|
||||
let max_index = min(
|
||||
(chart_area.width / (self.bar_width + self.bar_gap)) as usize,
|
||||
self.data.len(),
|
||||
);
|
||||
let mut data = self
|
||||
.data
|
||||
.iter()
|
||||
.take(max_index)
|
||||
.map(|&(l, v)| {
|
||||
(
|
||||
l,
|
||||
v * u64::from(chart_area.height - 1) * 8 / std::cmp::max(max, 1),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<(&str, u64)>>();
|
||||
for j in (0..chart_area.height - 1).rev() {
|
||||
for (i, d) in data.iter_mut().enumerate() {
|
||||
let symbol = match d.1 {
|
||||
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,
|
||||
};
|
||||
|
||||
for x in 0..self.bar_width {
|
||||
buf.get_mut(
|
||||
chart_area.left() + i as u16 * (self.bar_width + self.bar_gap) + x,
|
||||
chart_area.top() + j,
|
||||
)
|
||||
.set_symbol(symbol)
|
||||
.set_style(self.bar_style);
|
||||
}
|
||||
|
||||
if d.1 > 8 {
|
||||
d.1 -= 8;
|
||||
} else {
|
||||
d.1 = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, &(label, value)) in self.data.iter().take(max_index).enumerate() {
|
||||
if value != 0 {
|
||||
let value_label = &self.values[i];
|
||||
let width = value_label.width() as u16;
|
||||
if width < self.bar_width {
|
||||
buf.set_string(
|
||||
chart_area.left()
|
||||
+ i as u16 * (self.bar_width + self.bar_gap)
|
||||
+ (self.bar_width - width) / 2,
|
||||
chart_area.bottom() - 2,
|
||||
value_label,
|
||||
self.value_style,
|
||||
);
|
||||
}
|
||||
}
|
||||
buf.set_stringn(
|
||||
chart_area.left() + i as u16 * (self.bar_width + self.bar_gap),
|
||||
chart_area.bottom() - 1,
|
||||
label,
|
||||
self.bar_width as usize,
|
||||
self.label_style,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user