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