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