apache_echarts_wrapper/
builder.rs

1use crate::axis_typing::{AxisKindMarker, ValueAxis};
2use crate::common::Size;
3use crate::options::*;
4use crate::templates::ScriptTemplate;
5use serde::Serialize;
6use serde_json::json;
7use uuid::Uuid;
8use crate::common;
9use crate::options::Position::Percent;
10
11///trait that provides regression methods that are only supported when both x and y are numeric
12pub trait RegressionChartBuilder<X, Y>: Sized
13where X: AxisKindMarker<AxisType=ValueAxis>,
14      Y: AxisKindMarker<AxisType=ValueAxis>,
15      EChartOptions<X,Y>:Serialize
16
17{
18    fn options(&mut self) -> &mut EChartOptions<X,Y>;
19    
20    
21    fn add_linear_regression_dataset(self, data_source_index: usize) -> usize {
22        self.add_regression_dataset(data_source_index, RegressionMethod::Linear, None)
23    }
24
25    fn add_polynomial_regression_dataset(self, data_source_index: usize, order: u8) -> usize {
26        self.add_regression_dataset(data_source_index, RegressionMethod::Polynomial, Some(order))
27    }
28
29    fn add_exponential_regression_dataset(self, data_source_index: usize) -> usize {
30        self.add_regression_dataset(data_source_index, RegressionMethod::Exponential, None)
31    }
32
33    fn add_regression_dataset(mut self, data_source_index: usize, method: RegressionMethod, order: Option<u8>) -> usize {
34        let index = self.options().dataset.as_mut().unwrap().len();
35        let regression_config = RegressionConfig {
36            method: method.clone(),
37            order,
38            extra: None,
39        };
40        let dataset = DatasetTransform::regression(regression_config);
41        self.options().dataset.as_mut().unwrap().push(DatasetComponent::tr(dataset, data_source_index));
42        index
43    }
44
45
46    /// Add a dataset with regression transformation
47    fn add_regression_series<TData: Into<DatasetComponent<X,Y>>>(mut self, series_label: &str, data: TData,
48                             method: RegressionMethod, order: Option<u8>) -> Self {
49        // Create a dataset vector if it doesn't exist
50        if self.options().dataset.is_none() {
51            self.options().dataset = Some(Vec::new());
52        }
53
54        // Add source dataset
55        let datasets = self.options().dataset.as_mut().unwrap();
56        let source_index = datasets.len();
57        datasets.push(data.into());
58
59
60        // Add regression transform dataset
61        let transform_index = datasets.len();
62        let mut regression_config = RegressionConfig {
63            method: method.clone(),
64            order: None,
65            extra: None,
66        };
67
68        // Add polynomial order if provided and the method is polynomial
69        if method == RegressionMethod::Polynomial {
70            regression_config.order = order;
71        }
72
73        datasets.push(
74            DatasetComponent::tr(
75            DatasetTransform::regression(regression_config),
76            source_index
77        )
78        );
79
80        // Add scatter series for original data
81        self.options().series.as_mut().unwrap().push(Series {
82            r#type: Some(SeriesType::Scatter),
83            name: Some(format!("{} (data)", series_label)),
84            smooth: None,
85            area_style: None,
86            data: SeriesDataSource::DatasetIndex(source_index),
87            symbol: Some(DataPointSymbol::Circle),
88            symbol_size: Some(8),
89            extra: None,
90        });
91
92        // Add line series for regression
93        self.options().series.as_mut().unwrap().push(Series {
94            r#type: Some(SeriesType::Line),
95            name: Some(format!("{} (regression)", series_label)),
96            smooth: Some(true),
97            area_style: None,
98            data: SeriesDataSource::DatasetIndex(transform_index),
99            symbol: Some(DataPointSymbol::None),
100            symbol_size: None,
101            extra: None
102        });
103
104        self
105    }
106
107    /// Add a linear regression dataset
108    fn add_linear_regression_series<TData:Into<DatasetComponent<X,Y>>>(self, series_label: &str, data: TData) -> Self {
109        self.add_regression_series(series_label, data, RegressionMethod::Linear, None)
110    }
111
112    /// Add a polynomial regression dataset
113    fn add_polynomial_regression_series<TData:Into<DatasetComponent<X,Y>>>(self, series_label: &str, data: TData, order: u8) -> Self {
114        self.add_regression_series(series_label, data, RegressionMethod::Polynomial, Some(order))
115    }
116
117    /// Add an exponential regression dataset
118    fn add_exponential_regression_series<TData:Into<DatasetComponent<X,Y>>>(self, series_label: &str, data: TData) -> Self
119    {
120        self.add_regression_series(series_label, data, RegressionMethod::Exponential, None)
121    }
122
123    /// Add a logarithmic regression dataset
124    fn add_logarithmic_regression_series<TData:Into<DatasetComponent<X,Y>>>(self, series_label: &str, data: TData) -> Self {
125        self.add_regression_series(series_label, data, RegressionMethod::Logarithmic, None)
126    }
127}
128
129
130impl<X, Y> Default for EChartOptions<X,Y>
131where X: AxisKindMarker, Y: AxisKindMarker, EChartOptions<X,Y>:Serialize{
132    fn default() -> Self {
133        Self {
134            title: None,
135            tooltip: Some(Tooltip {
136                show: true,
137                show_delay: None,
138                hide_delay: None,
139                trigger: Some(TooltipTrigger::Item),
140                formatter: None,
141                axis_pointer: Some(AxisPointer {
142                    r#type: Some(AxisPointerType::Cross),
143                    snap: Some(false),
144                    animation: None,
145                    axis: None,
146                })
147            }),
148            legend: None, grid: None, extra: None, dataset: None,
149            x_axis: Axis::default(),
150            y_axis: Axis::default(),
151            series: Some(Vec::new()),
152        }
153    }
154}
155
156impl<X, Y>  EChartOptions<X,Y>
157where X: AxisKindMarker, Y: AxisKindMarker, EChartOptions<X,Y>:Serialize {
158    pub fn new(x_axis:Axis<X>,y_axis: Axis<Y>) -> Self {
159        Self {
160            title: None,
161            tooltip: Some(Tooltip {
162                show: true,
163                show_delay: None,
164                hide_delay: None,
165                trigger: Some(TooltipTrigger::Item),
166                formatter: None,
167                axis_pointer: Some(AxisPointer {
168                    r#type: Some(AxisPointerType::Cross),
169                    snap: Some(false),
170                    animation: None,
171                    axis: None,
172                })
173            }),
174            legend: None, grid: None, extra: None, dataset: None,
175            x_axis,
176            y_axis,
177            series: Some(Vec::new()),
178        }
179    }
180    
181    
182    pub fn enable_legend(mut self) -> Self{
183        if self.legend.is_none(){
184            self.legend = Some(Legend{
185                data: None,
186                orient: Some("vertical".to_string()),
187                left: None,
188                right: Some(Percent(common::Percent(0.0))),
189                top: Some(Percent(common::Percent(20.0))),
190                bottom: Some(Percent(common::Percent(20.0)))
191            })
192        }else { 
193            let mut legend = self.legend.unwrap();
194            legend.right = Some(Percent(common::Percent(0.0)));
195            legend.top = Some(Percent(common::Percent(20.0)));
196            legend.bottom = Some(Percent(common::Percent(20.0)));
197            legend.orient = Some("vertical".to_string());
198            self.legend = Some(legend);
199        }
200        if self.grid.is_none(){
201            self.grid = Some( Grid{
202                left: None,
203                right: Some(Percent(common::Percent(20.0))),
204                top: None,
205                bottom: None,
206                contain_label: None,
207                background_color: None,
208                border_color: None,
209                border_width: None,
210                show: None,
211                z: None,
212                zlevel: None,
213                extra: None,
214            })
215        }else { 
216            let mut grid = self.grid.unwrap();
217            grid.right = Some(Percent(common::Percent(20.0)));
218            self.grid = Some(grid);
219        }
220        self
221    }
222
223    /// Set chart title
224    pub fn title_str(mut self, title: String) -> Self {
225        let t =  self.title.get_or_insert_default();
226        t.left = Some(Position::Keyword(PositionKeyword::Center));
227        t.text = Some(title);
228        self
229    }
230
231    fn subtitle_str(mut self, subtitle: String) -> Self {
232        self.title.get_or_insert_default().sub_text = Some(subtitle);
233        self
234    }
235
236    fn title(mut self, title: Title) -> Self {
237        self.title = Some(title);
238        self
239    }
240
241
242    fn x_axis_label(mut self, x: String) -> Self {
243        self.x_axis.name = Some(x);
244        self
245    }
246
247    fn y_axis_label(mut self, y: String) -> Self {
248        self.y_axis.name = Some(y);
249        self
250    }
251
252    //add a dataset and get an index
253    fn add_dataset<TData:Into<DatasetComponent<X,Y>>>(mut self, data: TData) -> usize {
254        let index = self.dataset.as_mut().unwrap().len();
255        self.dataset.as_mut().unwrap().push(data.into());
256        index
257    }
258
259    /// Add visualization for a dataset.
260    /// If no datasets exist, or dataset_index is out of range, no datasets will be added
261    fn add_dataset_visualisation(mut self, series_label:String, series_type: SeriesType, dataset_index: usize) -> Self {
262        let datasets = &self.dataset;
263        if let Some(datasets) = datasets {
264            if let Some(dataset) =  datasets.get(dataset_index){
265                match dataset {
266                    DatasetComponent::Source(_) | DatasetComponent::Transform(_) => {
267                        self.series.as_mut().unwrap().push(Series {
268                            r#type: Some(series_type),
269                            name: Some(series_label),
270                            smooth: Some(true),
271                            area_style: None,
272                            symbol: None,
273                            symbol_size: None,
274                            data: SeriesDataSource::DatasetIndex(dataset_index),
275                            extra: None
276                        });
277                    }
278                    DatasetComponent::LabelledSource(_) => {
279                        self.series.as_mut().unwrap().push(Series {
280                            r#type: Some(series_type),
281                            name: Some(series_label),
282                            smooth: Some(true),
283                            area_style: None,
284                            symbol: None,
285                            symbol_size: None,
286                            data: SeriesDataSource::DatasetIndex(dataset_index),
287                            extra: Some(json!(
288                               {"encode": {"tooltip": [2,1], "x": 0, "y": 1 }}
289                           ))
290                        });
291                    }
292                }
293
294            }
295        }
296        self
297    }
298
299
300    pub fn add_series<TData:Into<SeriesDataSource<X,Y>>>(mut self, series_type: SeriesType, series_label:String, data: TData) -> Self {
301        self.series.as_mut().unwrap().push(
302            Series::new(series_label,series_type,data.into())
303        );
304        self
305    }
306
307    pub fn build(self, width: Size, height: Size) -> ScriptTemplate<X,Y>{
308        ScriptTemplate::new(Uuid::new_v4().to_string(), width, height, self)
309    }
310}
311
312
313
314impl <X, Y> RegressionChartBuilder<X, Y> for EChartOptions<X, Y>
315where X: AxisKindMarker<AxisType = ValueAxis> + Serialize, 
316      Y: AxisKindMarker<AxisType = ValueAxis> + Serialize{
317    
318    fn options(&mut self) -> &mut EChartOptions<X,Y> {
319        self
320    }
321    
322}