1use crate::core::{
6 vertex_utils, BoundingBox, DrawCall, Material, PipelineType, RenderData, Vertex,
7};
8use glam::{Vec3, Vec4};
9
10#[derive(Debug, Clone)]
12pub struct LinePlot {
13 pub x_data: Vec<f64>,
15 pub y_data: Vec<f64>,
16
17 pub color: Vec4,
19 pub line_width: f32,
20 pub line_style: LineStyle,
21
22 pub label: Option<String>,
24 pub visible: bool,
25
26 vertices: Option<Vec<Vertex>>,
28 bounds: Option<BoundingBox>,
29 dirty: bool,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum LineStyle {
35 Solid,
36 Dashed,
37 Dotted,
38 DashDot,
39}
40
41impl Default for LineStyle {
42 fn default() -> Self {
43 Self::Solid
44 }
45}
46
47impl LinePlot {
48 pub fn new(x_data: Vec<f64>, y_data: Vec<f64>) -> Result<Self, String> {
50 if x_data.len() != y_data.len() {
51 return Err(format!(
52 "Data length mismatch: x_data has {} points, y_data has {} points",
53 x_data.len(),
54 y_data.len()
55 ));
56 }
57
58 if x_data.is_empty() {
59 return Err("Cannot create line plot with empty data".to_string());
60 }
61
62 Ok(Self {
63 x_data,
64 y_data,
65 color: Vec4::new(0.0, 0.5, 1.0, 1.0), line_width: 1.0,
67 line_style: LineStyle::default(),
68 label: None,
69 visible: true,
70 vertices: None,
71 bounds: None,
72 dirty: true,
73 })
74 }
75
76 pub fn with_style(mut self, color: Vec4, line_width: f32, line_style: LineStyle) -> Self {
78 self.color = color;
79 self.line_width = line_width;
80 self.line_style = line_style;
81 self.dirty = true;
82 self
83 }
84
85 pub fn with_label<S: Into<String>>(mut self, label: S) -> Self {
87 self.label = Some(label.into());
88 self
89 }
90
91 pub fn update_data(&mut self, x_data: Vec<f64>, y_data: Vec<f64>) -> Result<(), String> {
93 if x_data.len() != y_data.len() {
94 return Err(format!(
95 "Data length mismatch: x_data has {} points, y_data has {} points",
96 x_data.len(),
97 y_data.len()
98 ));
99 }
100
101 if x_data.is_empty() {
102 return Err("Cannot update with empty data".to_string());
103 }
104
105 self.x_data = x_data;
106 self.y_data = y_data;
107 self.dirty = true;
108 Ok(())
109 }
110
111 pub fn set_color(&mut self, color: Vec4) {
113 self.color = color;
114 self.dirty = true;
115 }
116
117 pub fn set_line_width(&mut self, width: f32) {
119 self.line_width = width.max(0.1); self.dirty = true;
121 }
122
123 pub fn set_line_style(&mut self, style: LineStyle) {
125 self.line_style = style;
126 self.dirty = true;
127 }
128
129 pub fn set_visible(&mut self, visible: bool) {
131 self.visible = visible;
132 }
133
134 pub fn len(&self) -> usize {
136 self.x_data.len()
137 }
138
139 pub fn is_empty(&self) -> bool {
141 self.x_data.is_empty()
142 }
143
144 pub fn generate_vertices(&mut self) -> &Vec<Vertex> {
146 if self.dirty || self.vertices.is_none() {
147 self.vertices = Some(vertex_utils::create_line_plot(
148 &self.x_data,
149 &self.y_data,
150 self.color,
151 ));
152 self.dirty = false;
153 }
154 self.vertices.as_ref().unwrap()
155 }
156
157 pub fn bounds(&mut self) -> BoundingBox {
159 if self.dirty || self.bounds.is_none() {
160 let points: Vec<Vec3> = self
161 .x_data
162 .iter()
163 .zip(self.y_data.iter())
164 .map(|(&x, &y)| Vec3::new(x as f32, y as f32, 0.0))
165 .collect();
166 self.bounds = Some(BoundingBox::from_points(&points));
167 }
168 self.bounds.unwrap()
169 }
170
171 pub fn render_data(&mut self) -> RenderData {
173 let vertices = self.generate_vertices().clone();
174 let vertex_count = vertices.len();
175
176 let material = Material {
177 albedo: self.color,
178 ..Default::default()
179 };
180
181 let draw_call = DrawCall {
182 vertex_offset: 0,
183 vertex_count,
184 index_offset: None,
185 index_count: None,
186 instance_count: 1,
187 };
188
189 RenderData {
190 pipeline_type: PipelineType::Lines,
191 vertices,
192 indices: None,
193 material,
194 draw_calls: vec![draw_call],
195 }
196 }
197
198 pub fn statistics(&self) -> PlotStatistics {
200 let (min_x, max_x) = self
201 .x_data
202 .iter()
203 .fold((f64::INFINITY, f64::NEG_INFINITY), |(min, max), &x| {
204 (min.min(x), max.max(x))
205 });
206 let (min_y, max_y) = self
207 .y_data
208 .iter()
209 .fold((f64::INFINITY, f64::NEG_INFINITY), |(min, max), &y| {
210 (min.min(y), max.max(y))
211 });
212
213 PlotStatistics {
214 point_count: self.x_data.len(),
215 x_range: (min_x, max_x),
216 y_range: (min_y, max_y),
217 memory_usage: self.estimated_memory_usage(),
218 }
219 }
220
221 pub fn estimated_memory_usage(&self) -> usize {
223 std::mem::size_of::<f64>() * (self.x_data.len() + self.y_data.len())
224 + self
225 .vertices
226 .as_ref()
227 .map_or(0, |v| v.len() * std::mem::size_of::<Vertex>())
228 }
229}
230
231#[derive(Debug, Clone)]
233pub struct PlotStatistics {
234 pub point_count: usize,
235 pub x_range: (f64, f64),
236 pub y_range: (f64, f64),
237 pub memory_usage: usize,
238}
239
240pub mod matlab_compat {
242 use super::*;
243
244 pub fn plot(x: Vec<f64>, y: Vec<f64>) -> Result<LinePlot, String> {
246 LinePlot::new(x, y)
247 }
248
249 pub fn plot_with_color(x: Vec<f64>, y: Vec<f64>, color: &str) -> Result<LinePlot, String> {
251 let color_vec = parse_matlab_color(color)?;
252 Ok(LinePlot::new(x, y)?.with_style(color_vec, 1.0, LineStyle::Solid))
253 }
254
255 fn parse_matlab_color(color: &str) -> Result<Vec4, String> {
257 match color {
258 "r" | "red" => Ok(Vec4::new(1.0, 0.0, 0.0, 1.0)),
259 "g" | "green" => Ok(Vec4::new(0.0, 1.0, 0.0, 1.0)),
260 "b" | "blue" => Ok(Vec4::new(0.0, 0.0, 1.0, 1.0)),
261 "c" | "cyan" => Ok(Vec4::new(0.0, 1.0, 1.0, 1.0)),
262 "m" | "magenta" => Ok(Vec4::new(1.0, 0.0, 1.0, 1.0)),
263 "y" | "yellow" => Ok(Vec4::new(1.0, 1.0, 0.0, 1.0)),
264 "k" | "black" => Ok(Vec4::new(0.0, 0.0, 0.0, 1.0)),
265 "w" | "white" => Ok(Vec4::new(1.0, 1.0, 1.0, 1.0)),
266 _ => Err(format!("Unknown color: {color}")),
267 }
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn test_line_plot_creation() {
277 let x = vec![0.0, 1.0, 2.0, 3.0];
278 let y = vec![0.0, 1.0, 0.0, 1.0];
279
280 let plot = LinePlot::new(x.clone(), y.clone()).unwrap();
281
282 assert_eq!(plot.x_data, x);
283 assert_eq!(plot.y_data, y);
284 assert_eq!(plot.len(), 4);
285 assert!(!plot.is_empty());
286 assert!(plot.visible);
287 }
288
289 #[test]
290 fn test_line_plot_data_validation() {
291 let x = vec![0.0, 1.0, 2.0];
293 let y = vec![0.0, 1.0];
294 assert!(LinePlot::new(x, y).is_err());
295
296 let empty_x: Vec<f64> = vec![];
298 let empty_y: Vec<f64> = vec![];
299 assert!(LinePlot::new(empty_x, empty_y).is_err());
300 }
301
302 #[test]
303 fn test_line_plot_styling() {
304 let x = vec![0.0, 1.0, 2.0];
305 let y = vec![1.0, 2.0, 1.5];
306 let color = Vec4::new(1.0, 0.0, 0.0, 1.0);
307
308 let plot = LinePlot::new(x, y)
309 .unwrap()
310 .with_style(color, 2.0, LineStyle::Dashed)
311 .with_label("Test Line");
312
313 assert_eq!(plot.color, color);
314 assert_eq!(plot.line_width, 2.0);
315 assert_eq!(plot.line_style, LineStyle::Dashed);
316 assert_eq!(plot.label, Some("Test Line".to_string()));
317 }
318
319 #[test]
320 fn test_line_plot_data_update() {
321 let mut plot = LinePlot::new(vec![0.0, 1.0], vec![0.0, 1.0]).unwrap();
322
323 let new_x = vec![0.0, 0.5, 1.0, 1.5];
324 let new_y = vec![0.0, 0.25, 1.0, 2.25];
325
326 plot.update_data(new_x.clone(), new_y.clone()).unwrap();
327
328 assert_eq!(plot.x_data, new_x);
329 assert_eq!(plot.y_data, new_y);
330 assert_eq!(plot.len(), 4);
331 }
332
333 #[test]
334 fn test_line_plot_bounds() {
335 let x = vec![-1.0, 0.0, 1.0, 2.0];
336 let y = vec![-2.0, 0.0, 1.0, 3.0];
337
338 let mut plot = LinePlot::new(x, y).unwrap();
339 let bounds = plot.bounds();
340
341 assert_eq!(bounds.min.x, -1.0);
342 assert_eq!(bounds.max.x, 2.0);
343 assert_eq!(bounds.min.y, -2.0);
344 assert_eq!(bounds.max.y, 3.0);
345 }
346
347 #[test]
348 fn test_line_plot_vertex_generation() {
349 let x = vec![0.0, 1.0, 2.0];
350 let y = vec![0.0, 1.0, 0.0];
351
352 let mut plot = LinePlot::new(x, y).unwrap();
353 let vertices = plot.generate_vertices();
354
355 assert_eq!(vertices.len(), 4);
357
358 assert_eq!(vertices[0].position, [0.0, 0.0, 0.0]);
360 assert_eq!(vertices[1].position, [1.0, 1.0, 0.0]);
361 }
362
363 #[test]
364 fn test_line_plot_render_data() {
365 let x = vec![0.0, 1.0, 2.0];
366 let y = vec![1.0, 2.0, 1.0];
367
368 let mut plot = LinePlot::new(x, y).unwrap();
369 let render_data = plot.render_data();
370
371 assert_eq!(render_data.pipeline_type, PipelineType::Lines);
372 assert_eq!(render_data.vertices.len(), 4); assert!(render_data.indices.is_none());
374 assert_eq!(render_data.draw_calls.len(), 1);
375 }
376
377 #[test]
378 fn test_line_plot_statistics() {
379 let x = vec![0.0, 1.0, 2.0, 3.0];
380 let y = vec![-1.0, 0.0, 1.0, 2.0];
381
382 let plot = LinePlot::new(x, y).unwrap();
383 let stats = plot.statistics();
384
385 assert_eq!(stats.point_count, 4);
386 assert_eq!(stats.x_range, (0.0, 3.0));
387 assert_eq!(stats.y_range, (-1.0, 2.0));
388 assert!(stats.memory_usage > 0);
389 }
390
391 #[test]
392 fn test_matlab_compat_colors() {
393 use super::matlab_compat::*;
394
395 let x = vec![0.0, 1.0];
396 let y = vec![0.0, 1.0];
397
398 let red_plot = plot_with_color(x.clone(), y.clone(), "r").unwrap();
399 assert_eq!(red_plot.color, Vec4::new(1.0, 0.0, 0.0, 1.0));
400
401 let blue_plot = plot_with_color(x.clone(), y.clone(), "blue").unwrap();
402 assert_eq!(blue_plot.color, Vec4::new(0.0, 0.0, 1.0, 1.0));
403
404 assert!(plot_with_color(x, y, "invalid").is_err());
406 }
407}