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