use crate::core::buffer::Buffer;
use crate::core::color::Color;
use crate::core::rect::Rect;
use crate::widgets::Widget;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum AxisPosition {
Left,
Right,
Bottom,
Top,
}
#[derive(Debug, Clone)]
pub struct Axis {
pub title: String,
pub labels: Vec<String>,
pub position: AxisPosition,
}
impl Axis {
pub fn new(position: AxisPosition) -> Self {
Self {
title: String::new(),
labels: Vec::new(),
position,
}
}
pub fn with_title(mut self, title: &str) -> Self {
self.title = title.to_string();
self
}
pub fn with_labels(mut self, labels: Vec<String>) -> Self {
self.labels = labels;
self
}
}
#[derive(Debug, Clone)]
pub struct Dataset {
pub name: String,
pub color: Color,
pub data: Vec<(f64, f64)>,
pub marker: char,
}
impl Dataset {
pub fn new(name: &str, data: Vec<(f64, f64)>) -> Self {
Self {
name: name.to_string(),
color: Color::rgb(88, 166, 255),
data,
marker: '●',
}
}
pub fn with_color(mut self, color: Color) -> Self {
self.color = color;
self
}
}
#[derive(Debug, Clone)]
pub struct Chart {
pub datasets: Vec<Dataset>,
pub x_axis: Axis,
pub y_axis: Axis,
pub labels: Vec<String>,
pub graph_area: Option<Rect>,
}
impl Chart {
pub fn new(datasets: Vec<Dataset>) -> Self {
Self {
datasets,
x_axis: Axis::new(AxisPosition::Bottom),
y_axis: Axis::new(AxisPosition::Left),
labels: Vec::new(),
graph_area: None,
}
}
pub fn with_x_axis(mut self, axis: Axis) -> Self {
self.x_axis = axis;
self
}
pub fn with_y_axis(mut self, axis: Axis) -> Self {
self.y_axis = axis;
self
}
fn render_axes(&self, buffer: &mut Buffer, area: Rect) {
let axis_color = Color::rgb(48, 54, 61);
match self.y_axis.position {
AxisPosition::Left => {
for y in (area.y + 1) as usize..(area.bottom() - 1) as usize {
buffer.set(
area.x as usize,
y,
crate::core::buffer::Cell {
ch: '│',
fg: axis_color,
bg: None,
bold: false,
italic: false,
underlined: false,
},
);
}
}
AxisPosition::Right => {
for y in (area.y + 1) as usize..(area.bottom() - 1) as usize {
buffer.set(
(area.right() - 1) as usize,
y,
crate::core::buffer::Cell {
ch: '│',
fg: axis_color,
bg: None,
bold: false,
italic: false,
underlined: false,
},
);
}
}
_ => {}
}
match self.x_axis.position {
AxisPosition::Bottom => {
for x in (area.x + 1) as usize..(area.right() - 1) as usize {
buffer.set(
x,
(area.bottom() - 1) as usize,
crate::core::buffer::Cell {
ch: '─',
fg: axis_color,
bg: None,
bold: false,
italic: false,
underlined: false,
},
);
}
}
AxisPosition::Top => {
for x in (area.x + 1) as usize..(area.right() - 1) as usize {
buffer.set(
x,
area.y as usize,
crate::core::buffer::Cell {
ch: '─',
fg: axis_color,
bg: None,
bold: false,
italic: false,
underlined: false,
},
);
}
}
_ => {}
}
buffer.set(
area.x as usize,
(area.bottom() - 1) as usize,
crate::core::buffer::Cell {
ch: '└',
fg: axis_color,
bg: None,
bold: false,
italic: false,
underlined: false,
},
);
}
fn render_points(&self, buffer: &mut Buffer, area: Rect) {
if self.datasets.is_empty() || area.width < 4 || area.height < 4 {
return;
}
let inner = Rect::new(area.x + 1, area.y + 1, area.width - 2, area.height - 2);
let all_x: Vec<f64> = self
.datasets
.iter()
.flat_map(|d| d.data.iter().map(|(x, _)| *x))
.collect();
let all_y: Vec<f64> = self
.datasets
.iter()
.flat_map(|d| d.data.iter().map(|(_, y)| *y))
.collect();
let x_min = all_x.iter().copied().fold(f64::INFINITY, f64::min);
let x_max = all_x.iter().copied().fold(f64::NEG_INFINITY, f64::max);
let y_min = all_y.iter().copied().fold(f64::INFINITY, f64::min);
let y_max = all_y.iter().copied().fold(f64::NEG_INFINITY, f64::max);
let x_range = (x_max - x_min).max(1.0);
let y_range = (y_max - y_min).max(1.0);
for dataset in &self.datasets {
for &(x, y) in &dataset.data {
let nx = ((x - x_min) / x_range * (inner.width as f64 - 1.0)) as u16;
let ny = ((1.0 - (y - y_min) / y_range) * (inner.height as f64 - 1.0)) as u16;
let px = (inner.x + nx) as usize;
let py = (inner.y + ny) as usize;
if px < inner.right() as usize && py < inner.bottom() as usize {
buffer.set(
px,
py,
crate::core::buffer::Cell {
ch: dataset.marker,
fg: dataset.color,
bg: None,
bold: true,
italic: false,
underlined: false,
},
);
}
}
}
}
}
impl Widget for Chart {
fn render(&self, buffer: &mut Buffer, area: Rect) {
self.render_axes(buffer, area);
self.render_points(buffer, area);
}
}