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