1use std::collections::HashMap;
4
5pub mod conversion {
7 use super::*;
8
9 pub fn js_data_to_dataframe(
11 js_data: &serde_json::Value,
12 ) -> Result<crate::DataFrame, crate::data_processing::DataError> {
13 use polars::prelude::*;
14
15 if let Some(array) = js_data.as_array() {
16 if array.is_empty() {
17 return Ok(DataFrame::empty());
18 }
19
20 let mut columns = HashMap::new();
21
22 if let Some(first_obj) = array[0].as_object() {
24 for key in first_obj.keys() {
25 columns.insert(key.clone(), Vec::new());
26 }
27 }
28
29 for item in array {
31 if let Some(obj) = item.as_object() {
32 for (key, value) in obj {
33 if let Some(column_vec) = columns.get_mut(key) {
34 column_vec.push(value.clone());
35 }
36 }
37 }
38 }
39
40 let series: Vec<Series> = columns
42 .into_iter()
43 .map(|(name, values)| {
44 let polars_values: Vec<_> = values
46 .iter()
47 .map(|v| match v {
48 serde_json::Value::Number(n) => {
49 if n.is_f64() {
50 polars::prelude::AnyValue::Float64(n.as_f64().unwrap())
51 } else {
52 polars::prelude::AnyValue::Int64(n.as_i64().unwrap())
53 }
54 }
55 serde_json::Value::String(s) => polars::prelude::AnyValue::String(s),
56 serde_json::Value::Bool(b) => polars::prelude::AnyValue::Boolean(*b),
57 serde_json::Value::Null => polars::prelude::AnyValue::Null,
58 _ => polars::prelude::AnyValue::String(Box::leak(
59 v.to_string().into_boxed_str(),
60 )),
61 })
62 .collect();
63
64 Series::new((&name).into(), polars_values)
65 })
66 .collect();
67
68 Ok(DataFrame::new(
69 series.into_iter().map(|s| s.into()).collect(),
70 )?)
71 } else {
72 Err(crate::data_processing::DataError::Format(
73 "Expected JSON array".to_string(),
74 ))
75 }
76 }
77
78 pub fn dataframe_to_js(
80 df: &crate::DataFrame,
81 ) -> Result<serde_json::Value, crate::data_processing::DataError> {
82 let mut result = Vec::new();
83
84 for row in 0..df.height() {
85 let mut obj = serde_json::Map::new();
86
87 for col_name in df.get_column_names() {
88 let series = df.column(col_name)?;
89 let value = series
90 .get(row)
91 .map_err(|e| crate::data_processing::DataError::Processing(e.to_string()))?;
92
93 let json_value = match value {
94 polars::prelude::AnyValue::Int32(i) => serde_json::Value::Number(i.into()),
95 polars::prelude::AnyValue::Int64(i) => serde_json::Value::Number(i.into()),
96 polars::prelude::AnyValue::Float32(f) => {
97 serde_json::Value::Number(serde_json::Number::from_f64(f as f64).unwrap())
98 }
99 polars::prelude::AnyValue::Float64(f) => {
100 serde_json::Value::Number(serde_json::Number::from_f64(f).unwrap())
101 }
102 polars::prelude::AnyValue::String(s) => {
103 serde_json::Value::String(s.to_string())
104 }
105 polars::prelude::AnyValue::Boolean(b) => serde_json::Value::Bool(b),
106 polars::prelude::AnyValue::Null => serde_json::Value::Null,
107 _ => serde_json::Value::String(value.to_string()),
108 };
109
110 obj.insert(col_name.to_string(), json_value);
111 }
112
113 result.push(serde_json::Value::Object(obj));
114 }
115
116 Ok(serde_json::Value::Array(result))
117 }
118}
119
120pub mod performance {
122 use std::time::{Duration, Instant};
123
124 pub fn measure_time<F, R>(f: F) -> (R, Duration)
126 where
127 F: FnOnce() -> R,
128 {
129 let start = Instant::now();
130 let result = f();
131 let duration = start.elapsed();
132 (result, duration)
133 }
134
135 pub fn benchmark<F, R>(f: F, iterations: usize) -> BenchmarkResult
137 where
138 F: Fn() -> R,
139 {
140 let mut times = Vec::with_capacity(iterations);
141
142 for _ in 0..iterations {
143 let (_, duration) = measure_time(&f);
144 times.push(duration);
145 }
146
147 times.sort();
148
149 BenchmarkResult {
150 min: times[0],
151 max: times[iterations - 1],
152 mean: times.iter().sum::<Duration>() / iterations as u32,
153 median: times[iterations / 2],
154 p95: times[(iterations as f64 * 0.95) as usize],
155 p99: times[(iterations as f64 * 0.99) as usize],
156 }
157 }
158
159 #[derive(Debug, Clone)]
161 pub struct BenchmarkResult {
162 pub min: Duration,
164 pub max: Duration,
166 pub mean: Duration,
168 pub median: Duration,
170 pub p95: Duration,
172 pub p99: Duration,
174 }
175}
176
177pub mod memory {
179 use std::sync::atomic::{AtomicUsize, Ordering};
180
181 pub struct MemoryTracker {
183 allocated: AtomicUsize,
184 peak: AtomicUsize,
185 }
186
187 impl Default for MemoryTracker {
188 fn default() -> Self {
189 Self::new()
190 }
191 }
192
193 impl MemoryTracker {
194 pub const fn new() -> Self {
200 Self {
201 allocated: AtomicUsize::new(0),
202 peak: AtomicUsize::new(0),
203 }
204 }
205
206 pub fn allocated(&self) -> usize {
212 self.allocated.load(Ordering::Relaxed)
213 }
214
215 pub fn peak(&self) -> usize {
221 self.peak.load(Ordering::Relaxed)
222 }
223
224 pub fn record_allocation(&self, size: usize) {
230 let current = self.allocated.fetch_add(size, Ordering::Relaxed);
231 let new_total = current + size;
232
233 let mut peak = self.peak.load(Ordering::Relaxed);
234 while peak < new_total {
235 match self.peak.compare_exchange_weak(
236 peak,
237 new_total,
238 Ordering::Relaxed,
239 Ordering::Relaxed,
240 ) {
241 Ok(_) => break,
242 Err(p) => peak = p,
243 }
244 }
245 }
246
247 pub fn record_deallocation(&self, size: usize) {
253 self.allocated.fetch_sub(size, Ordering::Relaxed);
254 }
255 }
256
257 pub static MEMORY_TRACKER: MemoryTracker = MemoryTracker::new();
259}
260
261pub mod validation {
263 use crate::chart::{ChartSpec, ValidationError};
264
265 pub fn validate_chart_spec(spec: &ChartSpec) -> Result<(), ValidationError> {
267 spec.validate()
268 }
269
270 pub fn validate_data_types(spec: &ChartSpec) -> Result<(), ValidationError> {
272 if false {
276 for (_field, _encoding) in spec.encoding.get_field_encodings() {
277 }
280 }
281
282 Ok(())
283 }
284
285 fn types_compatible(
286 actual: &polars::prelude::DataType,
287 expected: &crate::chart::DataType,
288 ) -> bool {
289 matches!(
290 (actual, expected),
291 (
292 polars::prelude::DataType::Int32 | polars::prelude::DataType::Int64,
293 crate::chart::DataType::Quantitative,
294 ) | (
295 polars::prelude::DataType::Float32 | polars::prelude::DataType::Float64,
296 crate::chart::DataType::Quantitative,
297 ) | (
298 polars::prelude::DataType::String,
299 crate::chart::DataType::Nominal | crate::chart::DataType::Ordinal,
300 ) | (
301 polars::prelude::DataType::Boolean,
302 crate::chart::DataType::Nominal
303 ) | (
304 polars::prelude::DataType::Date | polars::prelude::DataType::Datetime(_, _),
305 crate::chart::DataType::Temporal,
306 )
307 )
308 }
309}
310
311pub mod error {
313 use crate::HeliosError;
314
315 pub fn user_friendly_error(error: &HeliosError) -> String {
317 error.user_message()
318 }
319
320 pub fn suggested_actions(error: &HeliosError) -> Vec<String> {
322 error.suggested_actions()
323 }
324
325 pub fn is_recoverable(error: &HeliosError) -> bool {
327 error.is_recoverable()
328 }
329}
330
331pub mod test_utils {
333 use polars::prelude::*;
334
335 pub fn create_test_dataframe() -> DataFrame {
337 df! {
338 "x" => [1, 2, 3, 4, 5],
339 "y" => [2, 4, 1, 5, 3],
340 "category" => ["A", "B", "A", "C", "B"],
341 }
342 .unwrap()
343 }
344
345 pub fn create_large_test_dataframe(size: usize) -> DataFrame {
347 let x: Vec<i32> = (0..size as i32).collect();
348 let y: Vec<f64> = (0..size).map(|i| (i as f64).sin()).collect();
349 let category: Vec<String> = (0..size).map(|i| format!("Category_{}", i % 10)).collect();
350
351 df! {
352 "x" => x,
353 "y" => y,
354 "category" => category,
355 }
356 .unwrap()
357 }
358
359 pub fn create_test_chart_spec() -> crate::chart::ChartSpec {
361 crate::chart::ChartSpec {
362 data: crate::chart::DataReference {
363 source: "test".to_string(),
364 format: crate::chart::DataFormat::Inline,
365 schema: None,
366 },
367 mark: crate::chart::MarkType::Point {
368 size: Some(5.0),
369 shape: None,
370 opacity: None,
371 },
372 encoding: crate::chart::Encoding {
373 x: Some(crate::chart::PositionEncoding {
374 field: "x".to_string(),
375 data_type: crate::chart::DataType::Quantitative,
376 scale: None,
377 axis: None,
378 legend: None,
379 bin: None,
380 aggregate: None,
381 sort: None,
382 }),
383 y: Some(crate::chart::PositionEncoding {
384 field: "y".to_string(),
385 data_type: crate::chart::DataType::Quantitative,
386 scale: None,
387 axis: None,
388 legend: None,
389 bin: None,
390 aggregate: None,
391 sort: None,
392 }),
393 color: Some(crate::chart::ColorEncoding {
394 field: "category".to_string(),
395 data_type: crate::chart::DataType::Nominal,
396 scale: None,
397 axis: None,
398 legend: None,
399 bin: None,
400 aggregate: None,
401 sort: None,
402 }),
403 ..Default::default()
404 },
405 transform: Vec::new(),
406 selection: Vec::new(),
407 intelligence: None,
408 config: crate::chart::ChartConfig::default(),
409 }
410 }
411
412 pub fn assert_dataframes_approx_equal(df1: &DataFrame, df2: &DataFrame, tolerance: f64) {
414 assert_eq!(df1.height(), df2.height(), "DataFrame heights differ");
415 assert_eq!(df1.width(), df2.width(), "DataFrame widths differ");
416
417 for col_name in df1.get_column_names() {
418 let series1 = df1.column(col_name).unwrap();
419 let series2 = df2.column(col_name).unwrap();
420
421 assert_eq!(
422 series1.dtype(),
423 series2.dtype(),
424 "Column '{}' types differ",
425 col_name
426 );
427
428 for i in 0..series1.len() {
429 let val1 = series1.get(i).unwrap();
430 let val2 = series2.get(i).unwrap();
431
432 match (val1.clone(), val2.clone()) {
433 (AnyValue::Float64(f1), AnyValue::Float64(f2)) => {
434 assert!(
435 (f1 - f2).abs() < tolerance,
436 "Column '{}' row {}: {} != {} (tolerance: {})",
437 col_name,
438 i,
439 f1,
440 f2,
441 tolerance
442 );
443 }
444 (AnyValue::Float32(f1), AnyValue::Float32(f2)) => {
445 assert!(
446 (f1 - f2).abs() < tolerance as f32,
447 "Column '{}' row {}: {} != {} (tolerance: {})",
448 col_name,
449 i,
450 f1,
451 f2,
452 tolerance
453 );
454 }
455 _ => assert_eq!(val1, val2, "Column '{}' row {} values differ", col_name, i),
456 }
457 }
458 }
459 }
460}
461
462impl crate::chart::Encoding {
464 pub fn get_field_encodings(&self) -> Vec<(String, &crate::chart::PositionEncoding)> {
470 let mut encodings = Vec::new();
471
472 if let Some(ref x) = self.x {
473 encodings.push((x.field.clone(), x));
474 }
475 if let Some(ref y) = self.y {
476 encodings.push((y.field.clone(), y));
477 }
478
479 encodings
480 }
481}
482
483impl crate::chart::PositionEncoding {
485 pub fn data_type(&self) -> &crate::chart::DataType {
491 &self.data_type
492 }
493}