1use crate::core::{BoundingBox, DrawCall, Material, PipelineType, RenderData, Vertex};
6use glam::{Vec3, Vec4};
7
8#[derive(Debug, Clone)]
10pub struct BarChart {
11 pub labels: Vec<String>,
13 pub values: Vec<f64>,
14
15 pub color: Vec4,
17 pub bar_width: f32,
18 pub outline_color: Option<Vec4>,
19 pub outline_width: f32,
20
21 pub label: Option<String>,
23 pub visible: bool,
24
25 vertices: Option<Vec<Vertex>>,
27 indices: Option<Vec<u32>>,
28 bounds: Option<BoundingBox>,
29 dirty: bool,
30}
31
32impl BarChart {
33 pub fn new(labels: Vec<String>, values: Vec<f64>) -> Result<Self, String> {
35 if labels.len() != values.len() {
36 return Err(format!(
37 "Data length mismatch: {} labels, {} values",
38 labels.len(),
39 values.len()
40 ));
41 }
42
43 if labels.is_empty() {
44 return Err("Cannot create bar chart with empty data".to_string());
45 }
46
47 Ok(Self {
48 labels,
49 values,
50 color: Vec4::new(0.0, 0.5, 1.0, 1.0), bar_width: 0.8, outline_color: None,
53 outline_width: 1.0,
54 label: None,
55 visible: true,
56 vertices: None,
57 indices: None,
58 bounds: None,
59 dirty: true,
60 })
61 }
62
63 pub fn with_style(mut self, color: Vec4, bar_width: f32) -> Self {
65 self.color = color;
66 self.bar_width = bar_width.clamp(0.1, 1.0);
67 self.dirty = true;
68 self
69 }
70
71 pub fn with_outline(mut self, outline_color: Vec4, outline_width: f32) -> Self {
73 self.outline_color = Some(outline_color);
74 self.outline_width = outline_width.max(0.1);
75 self.dirty = true;
76 self
77 }
78
79 pub fn with_label<S: Into<String>>(mut self, label: S) -> Self {
81 self.label = Some(label.into());
82 self
83 }
84
85 pub fn update_data(&mut self, labels: Vec<String>, values: Vec<f64>) -> Result<(), String> {
87 if labels.len() != values.len() {
88 return Err(format!(
89 "Data length mismatch: {} labels, {} values",
90 labels.len(),
91 values.len()
92 ));
93 }
94
95 if labels.is_empty() {
96 return Err("Cannot update with empty data".to_string());
97 }
98
99 self.labels = labels;
100 self.values = values;
101 self.dirty = true;
102 Ok(())
103 }
104
105 pub fn set_color(&mut self, color: Vec4) {
107 self.color = color;
108 self.dirty = true;
109 }
110
111 pub fn set_bar_width(&mut self, width: f32) {
113 self.bar_width = width.clamp(0.1, 1.0);
114 self.dirty = true;
115 }
116
117 pub fn set_visible(&mut self, visible: bool) {
119 self.visible = visible;
120 }
121
122 pub fn len(&self) -> usize {
124 self.labels.len()
125 }
126
127 pub fn is_empty(&self) -> bool {
129 self.labels.is_empty()
130 }
131
132 pub fn generate_vertices(&mut self) -> (&Vec<Vertex>, &Vec<u32>) {
134 if self.dirty || self.vertices.is_none() {
135 let (vertices, indices) = self.create_bar_geometry();
136 self.vertices = Some(vertices);
137 self.indices = Some(indices);
138 self.dirty = false;
139 }
140 (
141 self.vertices.as_ref().unwrap(),
142 self.indices.as_ref().unwrap(),
143 )
144 }
145
146 fn create_bar_geometry(&self) -> (Vec<Vertex>, Vec<u32>) {
148 let mut vertices = Vec::new();
149 let mut indices = Vec::new();
150
151 let _bar_spacing = 1.0; let half_width = self.bar_width * 0.5;
153
154 for (i, &value) in self.values.iter().enumerate() {
155 let x_center = i as f32; let left = x_center - half_width;
157 let right = x_center + half_width;
158 let bottom = 0.0; let top = value as f32;
160
161 let vertex_offset = vertices.len() as u32;
163
164 vertices.push(Vertex::new(Vec3::new(left, bottom, 0.0), self.color));
166
167 vertices.push(Vertex::new(Vec3::new(right, bottom, 0.0), self.color));
169
170 vertices.push(Vertex::new(Vec3::new(right, top, 0.0), self.color));
172
173 vertices.push(Vertex::new(Vec3::new(left, top, 0.0), self.color));
175
176 indices.push(vertex_offset);
179 indices.push(vertex_offset + 1);
180 indices.push(vertex_offset + 2);
181
182 indices.push(vertex_offset);
184 indices.push(vertex_offset + 2);
185 indices.push(vertex_offset + 3);
186 }
187
188 (vertices, indices)
189 }
190
191 pub fn bounds(&mut self) -> BoundingBox {
193 if self.dirty || self.bounds.is_none() {
194 let num_bars = self.values.len();
195 if num_bars == 0 {
196 self.bounds = Some(BoundingBox::default());
197 return self.bounds.unwrap();
198 }
199
200 let min_x = -self.bar_width * 0.5;
201 let max_x = (num_bars - 1) as f32 + self.bar_width * 0.5;
202
203 let min_y = self.values.iter().fold(0.0f64, |acc, &val| acc.min(val)) as f32;
204 let max_y = self.values.iter().fold(0.0f64, |acc, &val| acc.max(val)) as f32;
205
206 self.bounds = Some(BoundingBox::new(
207 Vec3::new(min_x, min_y, 0.0),
208 Vec3::new(max_x, max_y, 0.0),
209 ));
210 }
211 self.bounds.unwrap()
212 }
213
214 pub fn render_data(&mut self) -> RenderData {
216 let (vertices, indices) = self.generate_vertices();
217 let vertices = vertices.clone();
218 let indices = indices.clone();
219
220 let material = Material {
221 albedo: self.color,
222 ..Default::default()
223 };
224
225 let draw_call = DrawCall {
226 vertex_offset: 0,
227 vertex_count: vertices.len(),
228 index_offset: Some(0),
229 index_count: Some(indices.len()),
230 instance_count: 1,
231 };
232
233 RenderData {
234 pipeline_type: PipelineType::Triangles,
235 vertices,
236 indices: Some(indices),
237 material,
238 draw_calls: vec![draw_call],
239 }
240 }
241
242 pub fn statistics(&self) -> BarChartStatistics {
244 let value_range = if self.values.is_empty() {
245 (0.0, 0.0)
246 } else {
247 let min_val = self.values.iter().fold(f64::INFINITY, |a, &b| a.min(b));
248 let max_val = self.values.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
249 (min_val, max_val)
250 };
251
252 BarChartStatistics {
253 bar_count: self.values.len(),
254 value_range,
255 memory_usage: self.estimated_memory_usage(),
256 }
257 }
258
259 pub fn estimated_memory_usage(&self) -> usize {
261 let labels_size: usize = self.labels.iter().map(|s| s.len()).sum();
262 let values_size = self.values.len() * std::mem::size_of::<f64>();
263 let vertices_size = self
264 .vertices
265 .as_ref()
266 .map_or(0, |v| v.len() * std::mem::size_of::<Vertex>());
267 let indices_size = self
268 .indices
269 .as_ref()
270 .map_or(0, |i| i.len() * std::mem::size_of::<u32>());
271
272 labels_size + values_size + vertices_size + indices_size
273 }
274}
275
276#[derive(Debug, Clone)]
278pub struct BarChartStatistics {
279 pub bar_count: usize,
280 pub value_range: (f64, f64),
281 pub memory_usage: usize,
282}
283
284pub mod matlab_compat {
286 use super::*;
287
288 pub fn bar(values: Vec<f64>) -> Result<BarChart, String> {
290 let labels: Vec<String> = (1..=values.len()).map(|i| i.to_string()).collect();
291 BarChart::new(labels, values)
292 }
293
294 pub fn bar_with_labels(labels: Vec<String>, values: Vec<f64>) -> Result<BarChart, String> {
296 BarChart::new(labels, values)
297 }
298
299 pub fn bar_with_color(values: Vec<f64>, color: &str) -> Result<BarChart, String> {
301 let color_vec = parse_matlab_color(color)?;
302 let labels: Vec<String> = (1..=values.len()).map(|i| i.to_string()).collect();
303 Ok(BarChart::new(labels, values)?.with_style(color_vec, 0.8))
304 }
305
306 fn parse_matlab_color(color: &str) -> Result<Vec4, String> {
308 match color {
309 "r" | "red" => Ok(Vec4::new(1.0, 0.0, 0.0, 1.0)),
310 "g" | "green" => Ok(Vec4::new(0.0, 1.0, 0.0, 1.0)),
311 "b" | "blue" => Ok(Vec4::new(0.0, 0.0, 1.0, 1.0)),
312 "c" | "cyan" => Ok(Vec4::new(0.0, 1.0, 1.0, 1.0)),
313 "m" | "magenta" => Ok(Vec4::new(1.0, 0.0, 1.0, 1.0)),
314 "y" | "yellow" => Ok(Vec4::new(1.0, 1.0, 0.0, 1.0)),
315 "k" | "black" => Ok(Vec4::new(0.0, 0.0, 0.0, 1.0)),
316 "w" | "white" => Ok(Vec4::new(1.0, 1.0, 1.0, 1.0)),
317 _ => Err(format!("Unknown color: {color}")),
318 }
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use super::*;
325
326 #[test]
327 fn test_bar_chart_creation() {
328 let labels = vec!["A".to_string(), "B".to_string(), "C".to_string()];
329 let values = vec![10.0, 25.0, 15.0];
330
331 let chart = BarChart::new(labels.clone(), values.clone()).unwrap();
332
333 assert_eq!(chart.labels, labels);
334 assert_eq!(chart.values, values);
335 assert_eq!(chart.len(), 3);
336 assert!(!chart.is_empty());
337 assert!(chart.visible);
338 }
339
340 #[test]
341 fn test_bar_chart_data_validation() {
342 let labels = vec!["A".to_string(), "B".to_string()];
344 let values = vec![10.0, 25.0, 15.0];
345 assert!(BarChart::new(labels, values).is_err());
346
347 let empty_labels: Vec<String> = vec![];
349 let empty_values: Vec<f64> = vec![];
350 assert!(BarChart::new(empty_labels, empty_values).is_err());
351 }
352
353 #[test]
354 fn test_bar_chart_styling() {
355 let labels = vec!["X".to_string(), "Y".to_string()];
356 let values = vec![5.0, 10.0];
357 let color = Vec4::new(1.0, 0.0, 0.0, 1.0);
358
359 let chart = BarChart::new(labels, values)
360 .unwrap()
361 .with_style(color, 0.6)
362 .with_outline(Vec4::new(0.0, 0.0, 0.0, 1.0), 2.0)
363 .with_label("Test Chart");
364
365 assert_eq!(chart.color, color);
366 assert_eq!(chart.bar_width, 0.6);
367 assert_eq!(chart.outline_color, Some(Vec4::new(0.0, 0.0, 0.0, 1.0)));
368 assert_eq!(chart.outline_width, 2.0);
369 assert_eq!(chart.label, Some("Test Chart".to_string()));
370 }
371
372 #[test]
373 fn test_bar_chart_bounds() {
374 let labels = vec!["A".to_string(), "B".to_string(), "C".to_string()];
375 let values = vec![5.0, -2.0, 8.0];
376
377 let mut chart = BarChart::new(labels, values).unwrap();
378 let bounds = chart.bounds();
379
380 assert!(bounds.min.x < 0.0);
382 assert!(bounds.max.x > 2.0);
383
384 assert_eq!(bounds.min.y, -2.0);
386 assert_eq!(bounds.max.y, 8.0);
387 }
388
389 #[test]
390 fn test_bar_chart_vertex_generation() {
391 let labels = vec!["A".to_string(), "B".to_string()];
392 let values = vec![3.0, 5.0];
393
394 let mut chart = BarChart::new(labels, values).unwrap();
395 let (vertices, indices) = chart.generate_vertices();
396
397 assert_eq!(vertices.len(), 8);
399
400 assert_eq!(indices.len(), 12);
402
403 assert_eq!(vertices[0].position[1], 0.0); assert_eq!(vertices[2].position[1], 3.0); }
407
408 #[test]
409 fn test_bar_chart_render_data() {
410 let labels = vec!["Test".to_string()];
411 let values = vec![10.0];
412
413 let mut chart = BarChart::new(labels, values).unwrap();
414 let render_data = chart.render_data();
415
416 assert_eq!(render_data.pipeline_type, PipelineType::Triangles);
417 assert_eq!(render_data.vertices.len(), 4); assert!(render_data.indices.is_some());
419 assert_eq!(render_data.indices.as_ref().unwrap().len(), 6); }
421
422 #[test]
423 fn test_bar_chart_statistics() {
424 let labels = vec!["A".to_string(), "B".to_string(), "C".to_string()];
425 let values = vec![1.0, 5.0, 3.0];
426
427 let chart = BarChart::new(labels, values).unwrap();
428 let stats = chart.statistics();
429
430 assert_eq!(stats.bar_count, 3);
431 assert_eq!(stats.value_range, (1.0, 5.0));
432 assert!(stats.memory_usage > 0);
433 }
434
435 #[test]
436 fn test_matlab_compat_bar() {
437 use super::matlab_compat::*;
438
439 let values = vec![1.0, 3.0, 2.0];
440
441 let chart1 = bar(values.clone()).unwrap();
442 assert_eq!(chart1.len(), 3);
443 assert_eq!(chart1.labels, vec!["1", "2", "3"]);
444
445 let labels = vec!["X".to_string(), "Y".to_string(), "Z".to_string()];
446 let chart2 = bar_with_labels(labels.clone(), values.clone()).unwrap();
447 assert_eq!(chart2.labels, labels);
448
449 let chart3 = bar_with_color(values, "g").unwrap();
450 assert_eq!(chart3.color, Vec4::new(0.0, 1.0, 0.0, 1.0));
451 }
452}