1use std::cmp::PartialEq;
2use std::time::{Instant, SystemTime};
3use crate::common::{Padding, Rgb, Size};
4use crate::options::ChartData::Vector2D;
5use crate::render::Chart;
6use crate::ChartData::VectorWithRadius;
7use ndarray::{Array1, Array2};
8use ndarray_linalg::error::LinalgError;
9use ndarray_linalg::LeastSquaresSvd;
10use ndarray_linalg::{Lapack, Scalar};
11use serde::{Deserialize, Serialize};
12use serde::ser::SerializeSeq;
13use uuid::Uuid;
14
15#[derive(Serialize, Deserialize, Debug, Clone)]
16#[serde(rename_all = "camelCase")]
17pub struct ChartConfig<X,Y> {
18
19 data: ChartDataSection<X,Y>,
20
21 options: ChartOptions<X,Y>
22}
23
24impl<X, Y> ChartConfig<X, Y> where ChartConfig<X, Y>:Serialize {
25
26 pub fn new(options: ChartOptions<X,Y>) -> Self {
27 Self {
28 data: ChartDataSection::default(),
29 options
30 }
31 }
32
33 pub fn set_x_axis(mut self, conf: ScaleConfig<X>) -> Self {
34 let mut scales = self.options.scales;
35 if scales.is_none() {
36 scales = Some(ScalingConfig{
37 x: Some(conf),
38 y: None
39 });
40 }else {
41 scales.as_mut().unwrap().x = Some(conf);
42 }
43 self.options.scales = scales;
44 self
45 }
46
47 pub fn set_y_axis(mut self, conf: ScaleConfig<Y>) -> Self {
48 let mut scales = self.options.scales;
49 if scales.is_none() {
50 scales = Some(ScalingConfig{
51 x: None,
52 y: Some(conf)
53 });
54 }else {
55 scales.as_mut().unwrap().y = Some(conf);
56 }
57 self.options.scales = scales;
58 self
59 }
60
61
62 pub fn title_str(mut self, text: String) -> Self {
63 self.options.plugins.title = Some(Title{
64 display: true,
65 full_size: false,
66 text: vec![text],
67 padding: None,
68 position: None,
69 });
70 self
71 }
72
73
74 pub fn add_series<T: Into<ChartData<X,Y>>>(mut self, r#type: ChartType, title:String, data: T)->Self{
75 self.data.datasets.push(Dataset{
76 r#type,
77 label: title,
78 data: data.into(),
79 fill: None,
80 border_color: None,
81 background_color: None,
82 });
83 self
84 }
85
86
87 pub fn enable_legend(mut self) -> Self{
88 if self.options.plugins.legend.is_none() {
89 self.options.plugins.legend = Some(Legend {
90 display: true,
91 full_size: false,
92 position: None,
93 align: None
94 });
95 }else {
96 self.options.plugins.legend.as_mut().unwrap().display = true;
97 }
98 self
99 }
100
101 pub fn build(self, width: Size, height: Size) -> Chart<X,Y>{
102 Chart::new(Uuid::new_v4().to_string(), width, height, self)
103 }
104
105}
106
107
108
109impl<X> ChartConfig<X, X> where ChartConfig<X, X>:Serialize, X:Scalar + Lapack + Clone + Into<f64> {
110
111 pub fn add_linear_regression_series<T: Into<ChartData<X,X>>>(self, title: &str, data: T) -> Result<Self, LinalgError> {
112 let data:Vec<(X,X)> = data.into().into();
113 let n = data.len();
114 let config_with_scatter = self.add_series(ChartType::Scatter, title.to_string(), data.clone());
115
116 let mut x_matrix = Array2::<X>::zeros((n, 2));
118 let mut y_array = Array1::<X>::zeros(n);
119
120 for (i, (x, y)) in data.into_iter().enumerate() {
121 x_matrix[[i, 0]] = X::one(); x_matrix[[i, 1]] = x;
123 y_array[i] = y;
124 }
125
126 let beta = x_matrix.least_squares(&y_array)?.solution;
127
128 let y_pred = x_matrix.dot(&beta);
129 let r2 = Self::r_squared(&y_array, &y_pred);
130 let mut reg_data = Vec::<(X,X)>::with_capacity(n);
131
132 y_pred.into_iter().enumerate().for_each(|(i, x)| {
133 reg_data.push((x_matrix[[i,1]],x));
134 });
135
136 let config_with_both_charts =
137 config_with_scatter.add_series(ChartType::Line, format!("{} regression(R^2 = {:.4})", title, r2), reg_data);
138
139 Ok(config_with_both_charts)
140 }
141
142
143 fn r_squared(y_true: &Array1<X>, y_pred: &Array1<X>) -> f64
144 {
145 let n = y_true.len();
146 if n == 0 {
147 return 0.0; }
149 let n_f = n as f64;
150
151 let y_mean = y_true.iter().map(|&v| v.into()).sum::<f64>() / n_f;
153
154 let ss_res = y_true
156 .iter()
157 .zip(y_pred.iter())
158 .map(|(&y, &y_hat)| {
159 let y_f = y.into();
160 let y_hat_f = y_hat.into();
161 let diff = y_f - y_hat_f;
162 diff * diff
163 })
164 .sum::<f64>();
165
166 let ss_tot = y_true
168 .iter()
169 .map(|&y| {
170 let y_f = y.into();
171 let diff = y_f - y_mean;
172 diff * diff
173 })
174 .sum::<f64>();
175
176 if ss_tot == 0.0 {
177 return if ss_res == 0.0 {
179 1.0 } else {
181 0.0 }
183 }
184
185 1.0 - ss_res / ss_tot
186 }
187}
188
189
190impl<X,Y> Default for ChartDataSection<X,Y>{
191 fn default() -> Self {
192 ChartDataSection {
193 datasets: vec![],
194 }
195 }
196}
197
198impl<X:WithScaleType, Y:WithScaleType> Default for ChartConfig<X,Y>{
199 fn default() -> Self {
200 ChartConfig{
201 data: ChartDataSection::default(),
202 options: ChartOptions{
203 scales: Some(ScalingConfig{
204 x:Some(ScaleConfig{
205 r#type: Some(X::scale_type()),
206 ..ScaleConfig::default()
207 }),
208 y:Some(ScaleConfig{
209 r#type: Some(Y::scale_type()),
210 reverse: Y::scale_type() == ScaleType::Category,
211 ..ScaleConfig::default()
212 }),
213 }),
214 aspect_ratio: None,
215 plugins: Plugins::default(),
216 },
217 }
218 }
219}
220
221
222pub trait WithScaleType{
223 fn scale_type()->ScaleType;
224}
225
226#[macro_export]
227macro_rules! impl_scale_type {
228 ($type:ident for $($t:ty)*) => ($(
229 impl WithScaleType for $t {
230 fn scale_type() -> ScaleType {
231 ScaleType::$type
232 }
233 }
234 )*)
235}
236
237impl_scale_type!(Linear for u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize f32 f64);
238impl_scale_type!(Category for &str String);
239impl_scale_type!(Time for SystemTime Instant);
240
241#[derive(Serialize, Deserialize, Debug, Clone)]
242#[serde(rename_all = "lowercase")]
243pub enum ChartType {
244 Bubble,
245 Bar,
246 Line,
247 Doughnut,
248 Pie,
249 Radar,
250 PolarArea,
251 Scatter
252}
253
254#[derive(Serialize, Deserialize, Debug, Clone)]
255#[serde(rename_all = "camelCase")]
256pub struct ChartDataSection<X,Y>{
257 datasets: Vec<Dataset<X,Y>>
258}
259
260#[derive(Serialize, Deserialize, Debug, Clone)]
261#[serde(rename_all = "camelCase")]
262pub struct Dataset<X,Y>{
263
264 r#type: ChartType,
265
266 label: String,
267
268 data: ChartData<X,Y>,
269
270 #[serde(skip_serializing_if = "Option::is_none")]
271 fill: Option<Fill>,
272
273 #[serde(skip_serializing_if = "Option::is_none")]
274 border_color: Option<Rgb>,
275
276 #[serde(skip_serializing_if = "Option::is_none")]
277 background_color: Option<Rgb>
278}
279
280
281impl<X,Y> From<DataElement<X,Y>> for (X,Y){
282 fn from(value: DataElement<X,Y>) -> Self {
283 (value.x, value.y)
284 }
285}
286
287
288impl<X,Y> From<ChartData<X,Y>> for Vec<(X,Y)>{
289 fn from(value: ChartData<X, Y>) -> Self {
290 match value {
291 Vector2D(v) => v,
292 VectorWithRadius(val)=> val.into_iter().map(|v| v.into()).collect()
293 }
294 }
295}
296
297impl<X,Y> From<Vec<(X,Y)>> for ChartData<X,Y>{
298 fn from(value: Vec<(X, Y)>) -> Self {
299 Vector2D(value)
300 }
301}
302
303impl<const N: usize,X,Y> From<[(X,Y);N]> for ChartData<X,Y> where X:Clone, Y:Clone {
304 fn from(value: [(X,Y);N]) -> Self {
305 Vector2D(value.to_vec())
306 }
307}
308
309#[derive(Deserialize, Debug, Clone)]
310#[serde(untagged)]
311pub enum ChartData<X,Y>{
312 Vector2D(Vec<(X,Y)>),
313 VectorWithRadius(Vec<DataElement<X,Y>>)
314}
315
316
317impl<X,Y> Serialize for ChartData<X,Y> where X:Serialize, Y:Serialize{
318 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer {
319 match self {
320 Vector2D(v) => {
321 let mut v_ser = serializer.serialize_seq(Some(v.len()))?;
322 for (x,y) in v {
323 let point = DataPoint{x,y};
324 v_ser.serialize_element(&point)?;
325 }
326 v_ser.end()
327 },
328 VectorWithRadius(v) => v.serialize(serializer)
329 }
330 }
331}
332
333
334#[derive(Serialize, Deserialize, Debug, Clone)]
335pub struct DataPoint<X,Y>{
336 x: X,
337 y: Y
338}
339
340
341
342#[derive(Serialize, Deserialize, Debug, Clone)]
343pub struct DataElement<X,Y>{
344 x: X,
345 y: Y,
346 r: u32
347}
348
349#[derive(Serialize, Deserialize, Debug, Clone)]
350#[serde(untagged)]
351enum FillVariant{
352 AbsIndex(u8),
353 RelativeIndex(String),
354 Boundary(Boundary),
355 AxisValue(AxisValue)
356}
357
358#[derive(Serialize, Deserialize, Debug, Clone)]
359struct AxisValue{
360 value: AxisValueVariant
361}
362
363#[derive(Serialize, Deserialize, Debug, Clone)]
364enum AxisValueVariant{
365 Str(String),
366 Num(f64)
367}
368
369#[derive(Serialize, Deserialize, Debug, Clone)]
370enum Boundary{
371 Start,
372 End,
373 Origin,
374 Stack,
375 Shape
376}
377
378#[derive(Serialize, Deserialize, Debug, Clone)]
379#[serde(rename_all = "camelCase")]
380struct Fill {
381 target: FillVariant,
382
383 #[serde(skip_serializing_if = "Option::is_none")]
384 above: Option<Rgb>,
385
386 #[serde(skip_serializing_if = "Option::is_none")]
387 below: Option<Rgb>
388}
389
390#[derive(Serialize, Deserialize, Debug, Clone)]
391#[serde(rename_all = "lowercase")]
392enum AxisName{
393 X,
394 Y
395}
396
397#[derive(Serialize, Deserialize, Debug, Clone)]
398#[serde(rename_all = "camelCase")]
399pub struct ChartOptions<X,Y>{
400 #[serde(skip_serializing_if = "Option::is_none")]
401 scales: Option<ScalingConfig<X,Y>>,
402
403 #[serde(skip_serializing_if = "Option::is_none")]
404 aspect_ratio: Option<u8>,
405
406 plugins: Plugins,
407}
408
409impl<X,Y> Default for ChartOptions<X,Y>{
410 fn default() -> Self {
411 ChartOptions{
412 scales: None,
413 aspect_ratio: None,
414 plugins: Plugins::default(),
415 }
416 }
417}
418
419#[derive(Serialize, Deserialize, Debug, Clone)]
420#[serde(rename_all = "camelCase")]
421pub struct ScalingConfig<X,Y>{
422 #[serde(skip_serializing_if = "Option::is_none")]
423 x: Option<ScaleConfig<X>>,
424
425 #[serde(skip_serializing_if = "Option::is_none")]
426 y: Option<ScaleConfig<Y>>
427}
428
429#[derive(Serialize, Deserialize, Debug, Clone)]
430#[serde(rename_all = "camelCase")]
431pub struct ScaleConfig<T>{
432
433 #[serde(skip_serializing_if = "Option::is_none")]
434 pub r#type: Option<ScaleType>,
435
436 #[serde(skip_serializing_if = "Option::is_none")]
437 pub labels: Option<Vec<T>>,
438
439 #[serde(skip_serializing_if = "Option::is_none")]
440 pub align_to_pixels: Option<bool>,
441
442 pub reverse: bool,
443
444 #[serde(skip_serializing_if = "Option::is_none")]
445 pub max: Option<T>,
446
447 #[serde(skip_serializing_if = "Option::is_none")]
448 pub min: Option<T>,
449
450 #[serde(skip_serializing_if = "Option::is_none")]
451 pub title: Option<AxisTitle>
452}
453
454
455impl<T> Default for ScaleConfig<T>{
456 fn default() -> Self {
457 ScaleConfig{
458 r#type: None,
459 labels: None,
460 align_to_pixels: None,
461 reverse: false,
462 max: None,
463 min: None,
464 title: None
465 }
466 }
467}
468
469impl<T> ScaleConfig<T>{
470
471 pub fn new_category(reverse:bool,values: Vec<T>) -> Self {
472 Self {
473 r#type: Some(ScaleType::Category),
474 labels: Some(values),
475 reverse,
476 ..ScaleConfig::<T>::default()
477 }
478 }
479}
480
481
482#[derive(Serialize, Deserialize, Debug, Clone,PartialEq)]
483#[serde(rename_all = "lowercase")]
484pub enum ScaleType{
485 Linear,
486 Logarithmic,
487 Category,
488 Time,
489 TimeSeries,
490 RadialLinear
491}
492
493#[derive(Serialize, Deserialize, Debug, Clone)]
494#[serde(rename_all = "camelCase")]
495pub struct AxisTitle{
496 display: bool,
497 text: String,
498 align: Alignment,
499}
500
501#[derive(Serialize, Deserialize, Debug, Clone)]
502#[serde(rename_all = "lowercase")]
503pub enum Alignment{
504 Start,
505 Center,
506 End
507}
508
509#[derive(Serialize, Deserialize, Debug, Clone)]
510#[serde(rename_all = "lowercase")]
511#[derive(Default)]
512struct Plugins{
513 #[serde(skip_serializing_if = "Option::is_none")]
514 title: Option<Title>,
515
516 #[serde(skip_serializing_if = "Option::is_none")]
517 subtitle: Option<Title>,
518
519 #[serde(skip_serializing_if = "Option::is_none")]
520 legend: Option<Legend>
521
522}
523
524#[derive(Serialize, Deserialize, Debug, Clone)]
525#[serde(rename_all = "camelCase")]
526pub struct Legend{
527 display: bool,
528
529 #[serde(skip_serializing_if = "Option::is_none")]
530 position: Option<Position>,
531
532 #[serde(skip_serializing_if = "Option::is_none")]
533 align: Option<Alignment>,
534
535 pub full_size: bool,
536}
537
538#[derive(Serialize, Deserialize, Debug, Clone)]
539#[serde(rename_all = "lowercase")]
540enum Position{
541 Top,
542 Left,
543 Bottom,
544 Right
545}
546
547#[derive(Serialize, Deserialize, Debug, Clone)]
548#[serde(rename_all = "lowercase")]
549enum Align{
550 Start,
551 Center,
552 End
553}
554
555#[derive(Serialize, Deserialize, Debug, Clone)]
556#[serde(rename_all = "camelCase")]
557pub struct Title{
558 display: bool,
560
561 full_size: bool,
563
564 text: Vec<String>,
566
567 #[serde(skip_serializing_if = "Option::is_none")]
569 padding: Option<Padding>,
570
571 #[serde(skip_serializing_if = "Option::is_none")]
573 position: Option<Position>
574
575}