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