1use crate::chart::types::*;
2use crate::data::data_view::DataView;
3use anyhow::{anyhow, Result};
4use chrono::{DateTime, Utc};
5
6pub struct ChartEngine {
7 data_view: DataView,
8}
9
10impl ChartEngine {
11 pub fn new(data_view: DataView) -> Self {
12 Self { data_view }
13 }
14
15 pub fn execute_chart_query(&mut self, config: &ChartConfig) -> Result<DataSeries> {
16 self.extract_chart_data(&self.data_view, config)
19 }
20
21 fn extract_chart_data(&self, data: &DataView, config: &ChartConfig) -> Result<DataSeries> {
22 let headers = data.column_names();
23
24 let x_col_idx = headers
26 .iter()
27 .position(|h| h == &config.x_axis)
28 .ok_or_else(|| anyhow!("X-axis column '{}' not found", config.x_axis))?;
29 let y_col_idx = headers
30 .iter()
31 .position(|h| h == &config.y_axis)
32 .ok_or_else(|| anyhow!("Y-axis column '{}' not found", config.y_axis))?;
33
34 let mut points = Vec::new();
35 let mut x_min = f64::MAX;
36 let mut x_max = f64::MIN;
37 let mut y_min = f64::MAX;
38 let mut y_max = f64::MIN;
39
40 for row_idx in 0..data.row_count() {
42 let x_value_str = data.get_cell_value(row_idx, x_col_idx);
43 let y_value_str = data.get_cell_value(row_idx, y_col_idx);
44
45 if let (Some(x_str), Some(y_str)) = (x_value_str, y_value_str) {
46 let (x_float, timestamp) = self.convert_str_to_float_with_time(&x_str)?;
47 let y_float = self.convert_str_to_float(&y_str)?;
48
49 if x_float.is_finite() && y_float.is_finite() {
51 points.push(DataPoint {
52 x: x_float,
53 y: y_float,
54 timestamp,
55 label: None,
56 });
57
58 x_min = x_min.min(x_float);
59 x_max = x_max.max(x_float);
60 y_min = y_min.min(y_float);
61 y_max = y_max.max(y_float);
62 }
63 }
64 }
65
66 if points.is_empty() {
67 return Err(anyhow!("No valid data points found"));
68 }
69
70 Ok(DataSeries {
71 name: format!("{} vs {}", config.y_axis, config.x_axis),
72 points,
73 x_range: (x_min, x_max),
74 y_range: (y_min, y_max),
75 })
76 }
77
78 fn convert_str_to_float_with_time(&self, s: &str) -> Result<(f64, Option<DateTime<Utc>>)> {
79 if let Ok(dt) = DateTime::parse_from_rfc3339(s) {
81 let utc_dt = dt.with_timezone(&Utc);
82 let timestamp_secs = utc_dt.timestamp() as f64;
84 Ok((timestamp_secs, Some(utc_dt)))
85 } else if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S") {
86 let utc_dt = dt.and_utc();
88 let timestamp_secs = utc_dt.timestamp() as f64;
89 Ok((timestamp_secs, Some(utc_dt)))
90 } else if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S.%f") {
91 let utc_dt = dt.and_utc();
93 let timestamp_secs = utc_dt.timestamp() as f64;
94 Ok((timestamp_secs, Some(utc_dt)))
95 } else if let Ok(f) = s.parse::<f64>() {
96 Ok((f, None))
97 } else {
98 Err(anyhow!("Cannot convert '{}' to numeric value", s))
99 }
100 }
101
102 fn convert_str_to_float(&self, s: &str) -> Result<f64> {
103 s.parse::<f64>()
104 .map_err(|_| anyhow!("Cannot convert '{}' to numeric value", s))
105 }
106}