1use std::cmp::PartialEq;
2use crate::common::{Padding, Rgb, Size};
3use crate::options::ChartData::Vector2D;
4use crate::render::Chart;
5use crate::ChartData::{VectorWithRadius, VectorWithText};
6use ndarray::{Array1, Array2};
7use ndarray_linalg::error::LinalgError;
8use ndarray_linalg::LeastSquaresSvd;
9use ndarray_linalg::{Lapack, Scalar};
10use serde::Serialize;
11use serde::Deserialize;
12use serde::ser::SerializeSeq;
13use uuid::Uuid;
14use crate::serde::{ValueSerializeWrapper, WithTypeAndSerializer};
15
16const DISPLAY_FN: &'static str = "
17 function(context){
18 context = context[0];
19 let ttp = context.raw.tooltip || '';
20 if(ttp) return ttp;
21 }";
22
23#[derive(Debug, Clone)]
24pub struct ChartConfig<X:WithTypeAndSerializer+Serialize,Y:WithTypeAndSerializer+Serialize>
25{
26 pub data: ChartDataSection<X,Y>,
27 pub options: ChartOptions<X,Y>
28}
29
30impl<X, Y> ChartConfig<X, Y>
31where X:WithTypeAndSerializer+Serialize, Y:WithTypeAndSerializer+Serialize{
32
33 pub fn new(options: ChartOptions<X,Y>) -> Self {
34 Self {
35 data: ChartDataSection::default(),
36 options
37 }
38 }
39
40 pub fn set_x_axis(mut self, conf: ScaleConfig<X>) -> Self {
41 let mut scales = self.options.scales;
42 if scales.is_none() {
43 scales = Some(ScalingConfig{
44 x: Some(conf),
45 y: None
46 });
47 }else {
48 scales.as_mut().unwrap().x = Some(conf);
49 }
50 self.options.scales = scales;
51 self
52 }
53
54 pub fn set_y_axis(mut self, conf: ScaleConfig<Y>) -> Self {
55 let mut scales = self.options.scales;
56 if scales.is_none() {
57 scales = Some(ScalingConfig{
58 x: None,
59 y: Some(conf)
60 });
61 }else {
62 scales.as_mut().unwrap().y = Some(conf);
63 }
64 self.options.scales = scales;
65 self
66 }
67
68
69 pub fn title_str(mut self, text: String) -> Self {
70 self.options.plugins.title = Some(Title{
71 display: true,
72 full_size: false,
73 text: vec![text],
74 padding: None,
75 position: None,
76 });
77 self
78 }
79
80 pub fn with_aspect_ratio(mut self, ratio: f32) -> Self {
81 self.options.aspect_ratio = Some(ratio);
82 self
83 }
84
85 pub fn add_series<T: Into<ChartData<X,Y>>>(mut self, r#type: ChartType, title:String, data: T)->Self{
86 let data = data.into();
87 if let VectorWithText(d) = data {
88 let ttp = self.options.plugins.tooltip.get_or_insert_default();
89 let callbacks = ttp.callbacks.get_or_insert_default();
90 callbacks.title = Some(JsExpr(DISPLAY_FN));
91 self.data.datasets.push(Dataset {
92 r#type,
93 label: title,
94 data: VectorWithText(d),
95 fill: None,
96 border_color: None,
97 background_color: None,
98 });
99 }else {
100 self.data.datasets.push(Dataset {
101 r#type,
102 label: title,
103 data,
104 fill: None,
105 border_color: None,
106 background_color: None,
107 });
108 }
109 self
110 }
111
112
113 pub fn enable_legend(mut self) -> Self{
114 if self.options.plugins.legend.is_none() {
115 self.options.plugins.legend = Some(Legend {
116 display: true,
117 full_size: false,
118 position: None,
119 align: None
120 });
121 }else {
122 self.options.plugins.legend.as_mut().unwrap().display = true;
123 }
124 self
125 }
126
127 pub fn build(self, width: Size, height: Size) -> Chart<X,Y>{
128 Chart::new(Uuid::new_v4().to_string(), width, height, self)
129 }
130}
131
132
133impl<X> ChartConfig<X, X> where X:WithTypeAndSerializer + Scalar + Lapack + Clone + Into<f64> {
134
135 pub fn add_linear_regression_series<T: Into<ChartData<X,X>>>(self, title: &str, data: T) -> Result<Self, LinalgError> {
136 let data:Vec<(X,X)> = data.into().into();
137 let n = data.len();
138 let config_with_scatter = self.add_series(ChartType::Scatter, title.to_string(), data.clone());
139
140 let mut x_matrix = Array2::<X>::zeros((n, 2));
142 let mut y_array = Array1::<X>::zeros(n);
143
144 for (i, (x, y)) in data.into_iter().enumerate() {
145 x_matrix[[i, 0]] = X::one(); x_matrix[[i, 1]] = x;
147 y_array[i] = y;
148 }
149
150 let beta = x_matrix.least_squares(&y_array)?.solution;
151
152 let y_pred = x_matrix.dot(&beta);
153 let r2 = Self::r_squared(&y_array, &y_pred);
154 let mut reg_data = Vec::<(X,X)>::with_capacity(n);
155
156 y_pred.into_iter().enumerate().for_each(|(i, x)| {
157 reg_data.push((x_matrix[[i,1]],x));
158 });
159
160 let config_with_both_charts =
161 config_with_scatter.add_series(ChartType::Line, format!("{} regression(R^2 = {:.4})", title, r2), reg_data);
162
163 Ok(config_with_both_charts)
164 }
165
166
167 fn r_squared(y_true: &Array1<X>, y_pred: &Array1<X>) -> f64
168 {
169 let n = y_true.len();
170 if n == 0 {
171 return 0.0; }
173 let n_f = n as f64;
174
175 let y_mean = y_true.iter().map(|&v| v.into()).sum::<f64>() / n_f;
177
178 let ss_res = y_true
180 .iter()
181 .zip(y_pred.iter())
182 .map(|(&y, &y_hat)| {
183 let y_f = y.into();
184 let y_hat_f = y_hat.into();
185 let diff = y_f - y_hat_f;
186 diff * diff
187 })
188 .sum::<f64>();
189
190 let ss_tot = y_true
192 .iter()
193 .map(|&y| {
194 let y_f = y.into();
195 let diff = y_f - y_mean;
196 diff * diff
197 })
198 .sum::<f64>();
199
200 if ss_tot == 0.0 {
201 return if ss_res == 0.0 {
203 1.0 } else {
205 0.0 }
207 }
208
209 1.0 - ss_res / ss_tot
210 }
211}
212
213
214impl<X,Y> Default for ChartDataSection<X,Y> where X:WithTypeAndSerializer, Y:WithTypeAndSerializer{
215 fn default() -> Self {
216 ChartDataSection {
217 datasets: vec![],
218 }
219 }
220}
221
222impl<X: WithTypeAndSerializer+Serialize, Y: WithTypeAndSerializer+Serialize> Default for ChartConfig<X,Y> {
223 fn default() -> Self {
224 ChartConfig{
225 data: ChartDataSection::default(),
226 options: ChartOptions{
227 scales: Some(ScalingConfig{
228 x:Some(ScaleConfig{
229 r#type: Some(X::scale_type()),
230 ..ScaleConfig::default()
231 }),
232 y:Some(ScaleConfig{
233 r#type: Some(Y::scale_type()),
234 reverse: Y::scale_type() == ScaleType::Category,
235 ..ScaleConfig::default()
236 }),
237 }),
238 aspect_ratio: None,
239 plugins: Plugins::default(),
240 },
241 }
242 }
243}
244
245#[derive(Serialize, Deserialize, Debug, Clone)]
246#[serde(rename_all = "lowercase")]
247pub enum ChartType {
248 Bubble,
249 Bar,
250 Line,
251 Doughnut,
252 Pie,
253 Radar,
254 PolarArea,
255 Scatter
256}
257
258#[derive(Serialize, Debug, Clone)]
259#[serde(rename_all = "camelCase")]
260pub struct ChartDataSection<X:WithTypeAndSerializer,Y:WithTypeAndSerializer>{
261 datasets: Vec<Dataset<X,Y>>
262}
263
264#[derive(Serialize, Debug, Clone)]
265#[serde(rename_all = "camelCase")]
266pub struct Dataset<X:WithTypeAndSerializer,Y:WithTypeAndSerializer>{
267
268 r#type: ChartType,
269
270 label: String,
271
272 data: ChartData<X,Y>,
273
274 #[serde(skip_serializing_if = "Option::is_none")]
275 fill: Option<Fill>,
276
277 #[serde(skip_serializing_if = "Option::is_none")]
278 border_color: Option<Rgb>,
279
280 #[serde(skip_serializing_if = "Option::is_none")]
281 background_color: Option<Rgb>
282}
283
284
285impl<X,Y> From<(X, Y, String)> for DataPointWithTooltip<X,Y>{
286 fn from(value: (X, Y, String)) -> Self {
287 DataPointWithTooltip{
288 x: value.0,
289 y: value.1,
290 tooltip: value.2
291 }
292 }
293}
294
295impl<X,Y> From<(X, Y, &str)> for DataPointWithTooltip<X,Y>{
296 fn from(value: (X, Y, &str)) -> Self {
297 DataPointWithTooltip{
298 x: value.0,
299 y: value.1,
300 tooltip: value.2.to_string()
301 }
302 }
303}
304
305impl<X:WithTypeAndSerializer,Y:WithTypeAndSerializer> From<ChartData<X,Y>> for Vec<(X,Y)>{
306 fn from(value: ChartData<X, Y>) -> Self {
307 match value {
308 Vector2D(v) => v.into_iter().map(|v| (v.0.0,v.1.0)).collect(),
309 VectorWithRadius(val)=> val.into_iter().map(|v| (v.x.0,v.y.0)).collect(),
310 VectorWithText(val)=> val.into_iter().map(|v| (v.x.0,v.y.0)).collect()
311 }
312 }
313}
314
315impl<X,Y> From<Vec<(X,Y,String)>> for ChartData<X,Y> where X:WithTypeAndSerializer, Y:WithTypeAndSerializer{
316 fn from(value: Vec<(X, Y, String)>) -> Self {
317 VectorWithText(value.into_iter().map(|v| (v.0.into(),v.1.into(),v.2).into()).collect())
318 }
319}
320
321impl<const N: usize,X,Y> From<[(X,Y,String);N]> for ChartData<X,Y> where X:WithTypeAndSerializer, Y:WithTypeAndSerializer{
322 fn from(value: [(X,Y,String);N]) -> Self {
323 VectorWithText(value.into_iter().map(|v| (v.0.into(),v.1.into(),v.2).into()).collect())
324 }
325}
326
327impl<X,Y> From<Vec<(X,Y,&str)>> for ChartData<X,Y> where X:WithTypeAndSerializer, Y:WithTypeAndSerializer{
328 fn from(value: Vec<(X, Y,&str)>) -> Self {
329 VectorWithText(value.into_iter().map(|v|(v.0.into(),v.1.into(),v.2).into()).collect())
330 }
331}
332
333impl<const N: usize,X,Y> From<[(X,Y,&str);N]> for ChartData<X,Y> where X:WithTypeAndSerializer, Y:WithTypeAndSerializer{
334 fn from(value: [(X,Y,&str);N]) -> Self {
335 VectorWithText(value.into_iter().map(|v|(v.0.into(),v.1.into(),v.2).into()).collect())
336 }
337}
338
339
340
341impl<X,Y> From<Vec<(X,Y)>> for ChartData<X,Y> where X:WithTypeAndSerializer, Y:WithTypeAndSerializer{
342 fn from(value: Vec<(X, Y)>) -> Self {
343 Vector2D(value.into_iter().map(|v|(v.0.into(),v.1.into())).collect())
344 }
345}
346
347impl<const N: usize,X,Y> From<[(X,Y);N]> for ChartData<X,Y> where X:WithTypeAndSerializer, Y:WithTypeAndSerializer {
348 fn from(value: [(X,Y);N]) -> Self {
349 Vector2D(value.into_iter().map(|v|(v.0.into(),v.1.into())).collect())
350 }
351}
352
353#[derive(Debug, Clone)]
354pub enum ChartData<X,Y> where X:WithTypeAndSerializer, Y:WithTypeAndSerializer{
355 Vector2D(Vec<(ValueSerializeWrapper<X>,ValueSerializeWrapper<Y>)>),
356 VectorWithRadius(Vec<DataPointWithRadius<ValueSerializeWrapper<X>,ValueSerializeWrapper<Y>>>),
357 VectorWithText(Vec<DataPointWithTooltip<ValueSerializeWrapper<X>,ValueSerializeWrapper<Y>>>)
358}
359
360
361impl<X,Y> Serialize for ChartData<X,Y> where X:WithTypeAndSerializer, Y:WithTypeAndSerializer{
362 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer {
363 match self {
364 Vector2D(v) => {
365 let mut v_ser = serializer.serialize_seq(Some(v.len()))?;
366 for (x,y) in v {
367 let point = DataPoint{x,y};
368 v_ser.serialize_element(&point)?;
369 }
370 v_ser.end()
371 },
372 VectorWithRadius(v) => v.serialize(serializer),
373 VectorWithText(v) => v.serialize(serializer)
374 }
375 }
376}
377
378
379#[derive(Serialize, Deserialize, Debug, Clone)]
380pub struct DataPoint<X,Y>{
381 x: X,
382 y: Y
383}
384
385#[derive(Serialize, Deserialize, Debug, Clone)]
386pub struct DataPointWithTooltip<X,Y>{
387 x: X,
388 y: Y,
389 tooltip: String
390}
391
392
393#[derive(Serialize, Deserialize, Debug, Clone)]
394pub struct DataPointWithRadius<X,Y>{
395 x: X,
396 y: Y,
397 r: u32
398}
399
400#[derive(Serialize, Deserialize, Debug, Clone)]
401#[serde(untagged)]
402enum FillVariant{
403 AbsIndex(u8),
404 RelativeIndex(String),
405 Boundary(Boundary),
406 AxisValue(AxisValue)
407}
408
409#[derive(Serialize, Deserialize, Debug, Clone)]
410struct AxisValue{
411 value: AxisValueVariant
412}
413
414#[derive(Serialize, Deserialize, Debug, Clone)]
415enum AxisValueVariant{
416 Str(String),
417 Num(f64)
418}
419
420#[derive(Serialize, Deserialize, Debug, Clone)]
421enum Boundary{
422 Start,
423 End,
424 Origin,
425 Stack,
426 Shape
427}
428
429#[derive(Serialize, Deserialize, Debug, Clone)]
430#[serde(rename_all = "camelCase")]
431struct Fill {
432 target: FillVariant,
433
434 #[serde(skip_serializing_if = "Option::is_none")]
435 above: Option<Rgb>,
436
437 #[serde(skip_serializing_if = "Option::is_none")]
438 below: Option<Rgb>
439}
440
441#[derive(Serialize, Deserialize, Debug, Clone)]
442#[serde(rename_all = "lowercase")]
443enum AxisName{
444 X,
445 Y
446}
447
448#[derive(Debug, Clone)]
449pub struct ChartOptions<X,Y> where X:WithTypeAndSerializer, Y:WithTypeAndSerializer{
450 pub(crate) scales: Option<ScalingConfig<X,Y>>,
451 pub(crate) aspect_ratio: Option<f32>,
452 pub plugins: Plugins,
453}
454
455impl<X,Y> Default for ChartOptions<X,Y> where X:WithTypeAndSerializer, Y:WithTypeAndSerializer{
456 fn default() -> Self {
457 ChartOptions{
458 scales: None,
459 aspect_ratio: None,
460 plugins: Plugins::default(),
461 }
462 }
463}
464
465#[derive(Serialize, Debug, Clone)]
466#[serde(rename_all = "camelCase")]
467pub struct ScalingConfig<X,Y> where X:WithTypeAndSerializer, Y:WithTypeAndSerializer{
468 #[serde(skip_serializing_if = "Option::is_none")]
469 x: Option<ScaleConfig<X>>,
470
471 #[serde(skip_serializing_if = "Option::is_none")]
472 y: Option<ScaleConfig<Y>>
473}
474
475#[derive(Serialize, Debug, Clone)]
476#[serde(rename_all = "camelCase")]
477pub struct ScaleConfig<T> where T:WithTypeAndSerializer{
478
479 #[serde(skip_serializing_if = "Option::is_none")]
480 r#type: Option<ScaleType>,
481
482 #[serde(skip_serializing_if = "Option::is_none")]
483 labels: Option<Vec<ValueSerializeWrapper<T>>>,
484
485 #[serde(skip_serializing_if = "Option::is_none")]
486 align_to_pixels: Option<bool>,
487
488 reverse: bool,
489
490 #[serde(skip_serializing_if = "Option::is_none")]
491 max: Option<ValueSerializeWrapper<T>>,
492
493 #[serde(skip_serializing_if = "Option::is_none")]
494 min: Option<ValueSerializeWrapper<T>>,
495
496 #[serde(skip_serializing_if = "Option::is_none")]
497 title: Option<AxisTitle>
498}
499
500
501impl<T> Default for ScaleConfig<T> where T:WithTypeAndSerializer{
502 fn default() -> Self {
503 ScaleConfig{
504 r#type: None,
505 labels: None,
506 align_to_pixels: None,
507 reverse: false,
508 max: None,
509 min: None,
510 title: None
511 }
512 }
513}
514
515impl<T> ScaleConfig<T> where T:WithTypeAndSerializer{
516
517 pub fn new_category(reverse:bool,values: Vec<T>) -> Self {
518 Self {
519 r#type: Some(ScaleType::Category),
520 labels: Some(values.into_iter().map(|v| v.into()).collect()),
521 reverse,
522 ..ScaleConfig::<T>::default()
523 }
524 }
525
526 pub fn with_type(mut self, r#type: ScaleType) -> Self {
527 self.r#type = Some(r#type);
528 self
529 }
530
531 pub fn with_align_to_pixels(mut self, align_to_pixels: bool) -> Self {
532 self.align_to_pixels = Some(align_to_pixels);
533 self
534 }
535
536 pub fn with_max(mut self, max: T) -> Self {
537 self.max = Some(max.into());
538 self
539 }
540
541 pub fn with_min(mut self, min: T) -> Self {
542 self.min = Some(min.into());
543 self
544 }
545
546 pub fn with_str_title(mut self, title: &str) -> Self {
547 self.title = Some(title.into());
548 self
549 }
550
551 pub fn with_title(mut self, title: AxisTitle) -> Self {
552 self.title = Some(title);
553 self
554 }
555
556 pub fn with_labels(mut self, labels: Vec<T>) -> Self {
557 self.labels = Some(labels.into_iter().map(|v| v.into()).collect());
558 self
559 }
560
561 pub fn with_reverse(mut self, reverse: bool) -> Self {
562 self.reverse = reverse;
563 self
564 }
565
566}
567
568#[derive(Serialize, Deserialize, Debug, Clone,PartialEq)]
569#[serde(rename_all = "lowercase")]
570pub enum ScaleType{
571 Linear,
572 Logarithmic,
573 Category,
574 Time,
575 TimeSeries,
576 RadialLinear
577}
578
579#[derive(Serialize, Deserialize, Debug, Clone)]
580#[serde(rename_all = "camelCase")]
581pub struct AxisTitle{
582 display: bool,
583 text: String,
584 align: Alignment,
585}
586
587
588impl From<&str> for AxisTitle {
589 fn from(value: &str) -> Self {
590 Self{
591 display: true,
592 text: value.to_string(),
593 align: Alignment::Center
594 }
595 }
596}
597
598impl AxisTitle{
599 pub fn new(text: String) -> Self {
600 Self{
601 display: true,
602 text,
603 align: Alignment::Center
604 }
605 }
606
607 pub fn with_display(mut self, display: bool) -> Self {
608 self.display = display;
609 self
610 }
611
612 pub fn with_text(mut self, text: String) -> Self {
613 self.text = text;
614 self
615 }
616
617 pub fn with_align(mut self, align: Alignment) -> Self {
618 self.align = align;
619 self
620 }
621
622}
623
624#[derive(Serialize, Deserialize, Debug, Clone)]
625#[serde(rename_all = "lowercase")]
626pub enum Alignment{
627 Start,
628 Center,
629 End
630}
631
632#[derive(Debug, Clone)]
633#[derive(Default)]
634pub struct Plugins{
635 pub title: Option<Title>,
636 pub subtitle: Option<Title>,
637 pub legend: Option<Legend>,
638 pub tooltip: Option<Tooltip>
639}
640
641#[derive(Serialize, Deserialize, Debug, Clone)]
642#[serde(rename_all = "camelCase")]
643pub struct Legend{
644 display: bool,
645
646 #[serde(skip_serializing_if = "Option::is_none")]
647 position: Option<Position>,
648
649 #[serde(skip_serializing_if = "Option::is_none")]
650 align: Option<Alignment>,
651
652 pub full_size: bool,
653}
654
655
656impl Default for Legend{
657 fn default() -> Self {
658 Legend{
659 display: true,
660 position: None,
661 align: None,
662 full_size: false
663 }
664 }
665}
666
667impl Legend {
668 pub fn new_position(position: Position) -> Self {
669 Self {
670 display: true,
671 position: Some(position),
672 align: None,
673 full_size: false
674 }
675 }
676
677 pub fn with_align(mut self, align: Alignment) -> Self {
678 self.align = Some(align);
679 self
680 }
681
682 pub fn with_full_size(mut self, full_size: bool) -> Self {
683 self.full_size = full_size;
684 self
685 }
686
687 pub fn with_display(mut self, display: bool) -> Self {
688 self.display = display;
689 self
690 }
691
692 pub fn with_position(mut self, position: Position) -> Self {
693 self.position = Some(position);
694 self
695 }
696
697}
698
699#[derive(Serialize, Deserialize, Debug, Clone)]
700#[serde(rename_all = "lowercase")]
701pub enum Position{
702 Top,
703 Left,
704 Bottom,
705 Right
706}
707
708#[derive(Serialize, Deserialize, Debug, Clone)]
709#[serde(rename_all = "lowercase")]
710pub enum Align{
711 Start,
712 Center,
713 End
714}
715
716#[derive(Serialize, Deserialize, Debug, Clone)]
717#[serde(rename_all = "camelCase")]
718pub struct Title{
719 display: bool,
721
722 full_size: bool,
724
725 text: Vec<String>,
727
728 #[serde(skip_serializing_if = "Option::is_none")]
730 padding: Option<Padding>,
731
732 #[serde(skip_serializing_if = "Option::is_none")]
734 position: Option<Position>
735
736}
737
738
739#[derive(Serialize, Deserialize, Debug, Clone)]
740#[serde(rename_all = "lowercase")]
741pub enum TooltipMode{
742 Average,
743 Nearest
744}
745
746
747impl Default for Tooltip {
748 fn default() -> Self {
749 Tooltip{
750 enabled: true,
751 mode: None,
752 background_color: None,
753 title_color: None,
754 callbacks: None
755 }
756 }
757}
758
759#[derive(Debug, Clone)]
760pub struct Tooltip{
761 pub enabled: bool,
762 pub mode: Option<TooltipMode>,
763 pub background_color: Option<Rgb>,
764 pub title_color: Option<Rgb>,
765 pub callbacks: Option<TooltipCallbacks>
766}
767
768#[derive(Debug, Clone,Default)]
769pub struct TooltipCallbacks{
770 pub before_title: Option<JsExpr>,
771 pub title: Option<JsExpr>,
772 pub after_title: Option<JsExpr>,
773 pub before_body: Option<JsExpr>,
774 pub before_label: Option<JsExpr>,
775 pub label: Option<JsExpr>,
776 pub label_color: Option<JsExpr>,
777 pub label_text_color: Option<JsExpr>,
778 pub after_label: Option<JsExpr>,
779 pub after_body: Option<JsExpr>,
780 pub before_footer: Option<JsExpr>,
781 pub footer: Option<JsExpr>,
782 pub after_footer: Option<JsExpr>,
783}
784
785
786#[derive(Debug, Clone)]
787pub struct JsExpr(pub &'static str);
788
789impl std::fmt::Display for JsExpr {
790 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
791 f.write_str(&self.0)
793 }
794}
795