apache_echarts_wrapper/
builder.rs

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