use crate::core::buffer::Buffer;
use crate::core::color::Color;
use crate::core::rect::Rect;
use crate::widgets::Widget;
#[derive(Debug, Clone)]
pub struct Bar {
pub label: String,
pub value: u64,
pub color: Color,
}
impl Bar {
pub fn new(label: &str, value: u64) -> Self {
Self {
label: label.to_string(),
value,
color: Color::rgb(88, 166, 255),
}
}
pub fn with_color(mut self, color: Color) -> Self {
self.color = color;
self
}
}
#[derive(Debug, Clone)]
pub struct BarChart {
pub bars: Vec<Bar>,
pub max: Option<u64>,
pub bar_width: u16,
pub gap: u16,
pub show_labels: bool,
pub direction: BarDirection,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BarDirection {
Vertical,
Horizontal,
}
impl BarChart {
pub fn new() -> Self {
Self {
bars: Vec::new(),
max: None,
bar_width: 1,
gap: 1,
show_labels: true,
direction: BarDirection::Vertical,
}
}
pub fn with_bars(mut self, bars: Vec<Bar>) -> Self {
self.bars = bars;
self
}
pub fn with_direction(mut self, dir: BarDirection) -> Self {
self.direction = dir;
self
}
}
impl Widget for BarChart {
fn render(&self, buffer: &mut Buffer, area: Rect) {
if self.bars.is_empty() {
return;
}
let max = self
.max
.unwrap_or_else(|| self.bars.iter().map(|b| b.value).max().unwrap_or(1));
match self.direction {
BarDirection::Vertical => self.render_vertical(buffer, area, max),
BarDirection::Horizontal => self.render_horizontal(buffer, area, max),
}
}
}
impl BarChart {
fn render_vertical(&self, buffer: &mut Buffer, area: Rect, max: u64) {
let bar_area_height = if self.show_labels {
area.height.saturating_sub(2) as usize
} else {
area.height as usize
};
for (i, bar) in self.bars.iter().enumerate() {
let x = (area.x as usize) + i * (self.bar_width as usize + self.gap as usize);
if x >= area.right() as usize {
break;
}
let fill = if max == 0 {
0
} else {
((bar.value as f64 / max as f64) * bar_area_height as f64) as usize
};
for dy in 0..bar_area_height {
let y = (area.y as usize) + bar_area_height - 1 - dy;
if y < area.bottom() as usize {
let ch = if dy < fill { '█' } else { '░' };
let fg = if dy < fill {
bar.color
} else {
bar.color.dim(0.3)
};
buffer.set(
x,
y,
crate::core::buffer::Cell {
ch,
fg,
bg: None,
bold: false,
italic: false,
underlined: false,
},
);
}
}
if self.show_labels {
let label_y = area.bottom() as usize - 1;
let label_display: String =
bar.label.chars().take(self.bar_width as usize).collect();
buffer.set_str(x, label_y, &label_display, bar.color.dim(0.5), None);
}
}
}
fn render_horizontal(&self, buffer: &mut Buffer, area: Rect, max: u64) {
let bar_area_width = if self.show_labels {
area.width.saturating_sub(12) as usize
} else {
area.width as usize
};
for (i, bar) in self.bars.iter().enumerate() {
let y = (area.y as usize) + i;
if y >= area.bottom() as usize {
break;
}
if self.show_labels {
let label_display: String = bar.label.chars().take(10).collect();
buffer.set_str(area.x as usize, y, &label_display, bar.color.dim(0.5), None);
}
let fill = if max == 0 {
0
} else {
((bar.value as f64 / max as f64) * bar_area_width as f64) as usize
};
let x_offset = if self.show_labels {
area.x + 11
} else {
area.x
};
for dx in 0..bar_area_width {
let x = (x_offset as usize) + dx;
if x >= area.right() as usize {
break;
}
let ch = if dx < fill { '█' } else { '░' };
let fg = if dx < fill {
bar.color
} else {
bar.color.dim(0.3)
};
buffer.set(
x,
y,
crate::core::buffer::Cell {
ch,
fg,
bg: None,
bold: false,
italic: false,
underlined: false,
},
);
}
}
}
}
impl Default for BarChart {
fn default() -> Self {
Self::new()
}
}