1use std::fmt;
2
3#[derive(Debug, Clone, Copy)]
4pub enum ChartType {
5 Bar,
6 Line,
7}
8
9pub struct Chart {
10 data: Vec<(String, f64)>,
11 chart_type: ChartType,
12 height: usize,
13 title: Option<String>,
14}
15
16impl Chart {
17 pub fn new(chart_type: ChartType) -> Self {
18 Self {
19 data: Vec::new(),
20 chart_type,
21 height: 10,
22 title: None,
23 }
24 }
25
26 pub fn add_data_point(&mut self, label: impl Into<String>, value: f64) {
27 self.data.push((label.into(), value));
28 }
29
30 pub fn with_height(mut self, height: usize) -> Self {
31 self.height = height;
32 self
33 }
34
35 pub fn with_title(mut self, title: impl Into<String>) -> Self {
36 self.title = Some(title.into());
37 self
38 }
39
40 fn generate_bar_chart(&self) -> Vec<String> {
41 let max_value = self.data.iter().map(|(_, v)| *v).fold(0.0, f64::max);
42 let mut output = Vec::new();
43
44 for (label, value) in &self.data {
46 let bar_width = ((value / max_value) * 40.0) as usize;
47 let bar = "█".repeat(bar_width);
48 output.push(format!("{:<6} │ {:<40} {:.1}", label, bar, value));
49 }
50
51 output
52 }
53
54 fn generate_line_chart(&self) -> Vec<String> {
55 if self.data.is_empty() {
56 return Vec::new();
57 }
58
59 let max_value = self.data.iter().map(|(_, v)| *v).fold(0.0, f64::max);
60 let min_value = self.data.iter().map(|(_, v)| *v).fold(f64::MAX, f64::min);
61 let col_width = 12; let width = self.data.len() * col_width;
63 let mut chart = vec![vec![' '; width]; self.height];
64
65 let y_range = max_value - min_value;
67 let y_values = [
68 max_value, max_value - (y_range / 2.0), min_value, ];
72
73 for i in 0..self.data.len() - 1 {
75 let (x1, y1) = (
76 i * col_width,
77 self.height - 1 - ((self.data[i].1 - min_value) / (max_value - min_value) * (self.height - 1) as f64) as usize
78 );
79 let (x2, y2) = (
80 (i + 1) * col_width,
81 self.height - 1 - ((self.data[i + 1].1 - min_value) / (max_value - min_value) * (self.height - 1) as f64) as usize
82 );
83
84 chart[y1][x1] = '●';
86 if i == self.data.len() - 2 {
87 chart[y2][x2] = '●';
88 }
89
90 let dx = x2 - x1;
92 let dy = y2 as i32 - y1 as i32;
93 let steps = dx.max(dy.abs() as usize);
94
95 for step in 1..steps {
96 let x = x1 + step;
97 let y = y1 as i32 + (dy * step as i32 / steps as i32);
98 if y >= 0 && y < self.height as i32 {
99 let char = if dy == 0 {
101 '─'
102 } else if dy < 0 {
103 '/'
104 } else {
105 '\\'
106 };
107 chart[y as usize][x] = char;
108 }
109 }
110 }
111
112 let mut output = Vec::new();
114 for (i, row) in chart.iter().enumerate() {
115 let y_value = if i == 0 {
116 format!("{:>8.1} ", y_values[0])
117 } else if i == self.height / 2 {
118 format!("{:>8.1} ", y_values[1])
119 } else if i == self.height - 1 {
120 format!("{:>8.1} ", y_values[2])
121 } else {
122 format!("{:<10}", "")
123 };
124 output.push(format!("{:<10}│ {}", y_value, row.iter().collect::<String>()));
125 }
126
127 output.push(format!("{:<10}└{}", "", "─".repeat(width)));
129
130 let mut labels = String::from(" ");
132 for (i, (label, _)) in self.data.iter().enumerate() {
133 labels.push_str(&format!("{:<11}", label));
134 if i < self.data.len() - 1 {
135 labels.push(' ');
136 }
137 }
138 output.push(labels);
139
140 output
141 }
142}
143
144impl fmt::Display for Chart {
145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146 if let Some(title) = &self.title {
148 writeln!(f, "{}", title)?;
149 writeln!(f, "{}", "=".repeat(title.len()))?;
150 writeln!(f)?; }
152
153 let chart_data = match self.chart_type {
154 ChartType::Bar => self.generate_bar_chart(),
155 ChartType::Line => self.generate_line_chart(),
156 };
157
158 for line in chart_data {
159 writeln!(f, "{}", line)?;
160 }
161 Ok(())
162 }
163}