apache_echarts_wrapper/
options.rs

1use std::fmt::Debug;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use crate::axis_typing::{AxisInfo, AxisKindMarker, ValueSerializeWrapper};
5use crate::common::Percent;
6
7/// Root object for ECharts configuration
8#[derive(Serialize, Debug, Clone)]
9#[serde(rename_all = "camelCase")]
10pub struct EChartOptions<X:AxisKindMarker,Y:AxisKindMarker> {
11    /// Chart title options
12    #[serde(skip_serializing_if = "Option::is_none")]
13    pub(crate) title: Option<Title>,
14
15    /// Grid positioning and style options
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub grid: Option<Grid>,
18
19    /// Tooltip options
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub tooltip: Option<Tooltip>,
22
23    /// Legend options
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub legend: Option<Legend>,
26
27    /// Dataset component for providing data
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub(crate) dataset: Option<Vec<DatasetComponent<X,Y>>>,
30
31    /// X-axis options (Cartesian charts)
32    pub x_axis: Axis<X>,
33
34    /// Y-axis options (Cartesian charts)
35    pub y_axis: Axis<Y>,
36
37    /// Series data
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub(crate) series: Option<Vec<Series<X,Y>>>,
40
41    /// Additional raw options not covered by this binding
42    #[serde(flatten)]
43    pub extra: Option<Value>,
44}
45
46/// Keyword positions supported by ECharts
47#[derive(Serialize, Deserialize, Debug, Clone)]
48#[serde(rename_all = "camelCase")]
49pub enum PositionKeyword {
50    Left,
51    Right,
52    Top,
53    Bottom,
54    Middle,
55    Center,
56    Auto
57}
58
59
60/// Position enum supporting keyword, numeric px, percent, or other strings
61#[derive(Serialize, Deserialize, Debug, Clone)]
62#[serde(untagged)]
63pub enum Position {
64    /// Predefined keyword position
65    Keyword(PositionKeyword),
66    /// Numeric pixel value
67    Number(f64),
68    /// Percentage value (e.g., 50 => "50%")
69    Percent(Percent)
70}
71
72/// Title component
73#[derive(Serialize, Deserialize, Debug, Clone,Default)]
74#[serde(rename_all = "camelCase")]
75pub struct Title {
76    /// Main title text
77    pub text: Option<String>,
78
79    /// Subtitle text
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub sub_text: Option<String>,
82
83    /// Link for title
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub link: Option<String>,
86
87    /// Left position (Keyword, numeric px, percent, or other)
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub left: Option<Position>,
90
91    /// Top position (Keyword, numeric px, percent, or other)
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub top: Option<Position>,
94
95    /// Right position (Keyword, numeric px, percent, or other)
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub right: Option<Position>,
98
99    /// Bottom position (Keyword, numeric px, percent, or other)
100    #[serde(skip_serializing_if = "Option::is_none")]
101    pub bottom: Option<Position>,
102
103    /// Additional raw title options
104    #[serde(flatten)]
105    pub extra: Option<Value>,
106}
107
108impl Title {
109    pub fn new(text: &str) -> Title {
110        Self{
111            text: Some(text.to_string()),
112            sub_text: None,
113            link: None,
114            left: None,
115            top: None,
116            right: None,
117            bottom: None,
118            extra: None,
119        }
120    }
121}
122
123/// Grid component
124#[derive(Serialize, Deserialize, Debug, Clone)]
125#[serde(rename_all = "camelCase")]
126pub struct Grid {
127    /// Distance between grid and left side (Keyword, numeric px, percent, or other)
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub left: Option<Position>,
130
131    /// Distance between grid and right side (Keyword, numeric px, percent, or other)
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub right: Option<Position>,
134
135    /// Distance between grid and top side (Keyword, numeric px, percent, or other)
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub top: Option<Position>,
138
139    /// Distance between grid and bottom side (Keyword, numeric px, percent, or other)
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub bottom: Option<Position>,
142
143    /// Whether the grid area contain the axis labels
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub contain_label: Option<bool>,
146
147    /// Background color of the grid
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub background_color: Option<Value>,
150
151    /// Border color of the grid
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub border_color: Option<Value>,
154
155    /// Border width of the grid
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub border_width: Option<f64>,
158
159    /// Show the border of the grid
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub show: Option<bool>,
162
163    /// z-index of the grid component
164    #[serde(skip_serializing_if = "Option::is_none")]
165    pub z: Option<i32>,
166
167    /// z-level of the grid component
168    #[serde(skip_serializing_if = "Option::is_none")]
169    pub zlevel: Option<i32>,
170
171    /// Additional raw grid options
172    #[serde(flatten)]
173    pub extra: Option<Value>,
174}
175
176#[derive(Serialize, Deserialize, Debug, Clone)]
177#[serde(rename_all = "camelCase")]
178pub enum TooltipTrigger {
179    Item,
180    Axis,
181    None
182}
183
184#[derive(Serialize, Deserialize, Debug, Clone)]
185#[serde(rename_all = "camelCase")]
186pub enum AxisPointerType {
187    Cross,
188    Line,
189    Shadow,
190    None
191}
192
193#[derive(Serialize, Deserialize, Debug, Clone)]
194#[serde(rename_all = "camelCase")]
195pub enum AxisPointerTargetAxis{
196    Auto,
197    X,
198    Y,
199    Radius,
200    Angle
201}
202
203
204#[derive(Serialize, Deserialize, Debug, Clone)]
205#[serde(rename_all = "camelCase")]
206pub struct AxisPointer{
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub r#type: Option<AxisPointerType>,
209
210    #[serde(skip_serializing_if = "Option::is_none")]
211    pub snap : Option<bool>,
212
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub animation : Option<bool>,
215
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub axis: Option<AxisPointerTargetAxis>
218
219
220}
221
222/// Tooltip component
223#[derive(Serialize, Deserialize, Debug, Clone)]
224#[serde(rename_all = "camelCase")]
225pub struct Tooltip {
226    pub show: bool,
227
228    #[serde(skip_serializing_if = "Option::is_none")]
229    pub show_delay: Option<i32>,
230
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub hide_delay: Option<i32>,
233
234    /// Trigger mode: item, axis
235    #[serde(skip_serializing_if = "Option::is_none")]
236    pub trigger: Option<TooltipTrigger>,
237
238    /// Tooltip formatter template or callback
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub formatter: Option<Value>,
241
242    #[serde(skip_serializing_if = "Option::is_none")]
243    pub axis_pointer: Option<AxisPointer>,
244
245}
246
247/// Legend component
248#[derive(Serialize, Deserialize, Debug, Clone)]
249#[serde(rename_all = "camelCase")]
250pub struct Legend {
251    /// Data items in the legend
252    #[serde(skip_serializing_if = "Option::is_none")]
253    pub data: Option<Vec<String>>,
254
255    /// Legend orientation: vertical or horizontal
256    #[serde(skip_serializing_if = "Option::is_none")]
257    pub orient: Option<String>,
258
259    /// Left position (Keyword, numeric px, percent, or other)
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub left: Option<Position>,
262
263    #[serde(skip_serializing_if = "Option::is_none")]
264    pub right: Option<Position>,
265
266    #[serde(skip_serializing_if = "Option::is_none")]
267    pub top: Option<Position>,
268
269    #[serde(skip_serializing_if = "Option::is_none")]
270    pub bottom: Option<Position>
271}
272
273impl Default for Legend {
274    fn default() -> Self {
275        Self{
276            data: None,
277            orient: None,
278            left: None,
279            right:  None,
280            top:  None,
281            bottom: None,
282        }
283    }
284}
285
286
287/// Axis types supported by ECharts
288#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
289#[serde(rename_all = "lowercase")]
290pub enum AxisType {
291    Value,
292    Category,
293    Time,
294    Log
295}
296
297#[derive(Serialize, Debug, Clone)]
298#[serde(rename_all = "camelCase")]
299pub struct  NamedValuePair<X:AxisKindMarker,Y:AxisKindMarker>{
300    value: (ValueSerializeWrapper<X>,ValueSerializeWrapper<Y>),
301    name: String,
302}
303
304impl<X:AxisKindMarker,Y:AxisKindMarker> NamedValuePair<X,Y> {
305    pub fn new(x: X, y: Y, name: String) -> Self {
306        Self{
307            value: (x.into(), y.into()),
308            name,
309        }
310    }
311
312}
313
314#[derive(Serialize, Debug, Clone)]
315#[serde(rename_all = "camelCase")]
316pub struct  NamedValue<X:AxisKindMarker>{
317    value: ValueSerializeWrapper<X>,
318    name: String,
319}
320
321
322/// Axis (cartesian)
323#[derive(Serialize, Debug, Clone)]
324#[serde(rename_all = "camelCase")]
325pub struct Axis<T:AxisKindMarker> {
326    /// Axis type: value, category, time, log
327    pub(crate) r#type: AxisType,
328
329    #[serde(skip_serializing_if = "Option::is_none")]
330    pub(crate) inverse: Option<bool>,
331
332    /// Axis name
333    #[serde(skip_serializing_if = "Option::is_none")]
334    pub(crate) name: Option<String>,
335
336    /// Data for category axis
337    #[serde(skip_serializing_if = "Option::is_none")]
338    pub data: Option<Vec<ValueSerializeWrapper<T>>>,
339
340    /// Additional raw axis options
341    #[serde(flatten)]
342    pub extra: Option<Value>,
343}
344
345impl<T:AxisKindMarker> Default for Axis<T> {
346    fn default() -> Self {
347        Self{
348            r#type: T::AxisType::AXIS_TYPE,
349            inverse: None,
350            name: None,
351            data: None,
352            extra: None,
353        }
354    }
355}
356
357
358impl<T:AxisKindMarker> Axis<T>{
359
360    pub fn new_named(name: String)-> Self{
361        Self{
362            r#type:  T::AxisType::AXIS_TYPE,
363            name: Some(name),
364            inverse: None,
365            data: None,
366            extra: None
367        }
368    }
369
370    pub fn new(name: String, is_log:bool, inverse: bool)-> Self{
371        if T::AxisType::AXIS_TYPE == AxisType::Value && is_log {
372            Self{
373                r#type:  AxisType::Log,
374                name: Some(name),
375                inverse: Some(inverse),
376                data: None,
377                extra: None,
378            }
379        }else {
380            Self{
381                r#type:  T::AxisType::AXIS_TYPE,
382                name: Some(name.to_string()),
383                inverse: Some(inverse),
384                data: None,
385                extra: None,
386            }
387        }
388    }
389}
390
391
392/// Available series types in ECharts
393#[derive(Serialize, Deserialize, Debug, Clone)]
394#[serde(rename_all = "camelCase")]
395pub enum SeriesType {
396    Line,
397    Bar,
398    Pie,
399    Scatter,
400    EffectScatter,
401    Radar,
402    Tree,
403    Treemap,
404    Sunburst,
405    Boxplot,
406    Candlestick,
407    Heatmap,
408    Map,
409    Parallel,
410    Lines,
411    Graph,
412    Sankey,
413    Funnel,
414    Gauge,
415    PictorialBar,
416    ThemeRiver,
417    Custom
418}
419
420
421#[derive(Serialize,  Debug, Clone)]
422#[serde(untagged)]
423pub enum DataVariant<X:AxisKindMarker,Y:AxisKindMarker>{
424    /// Single dimension data (string/int/float)
425    Data(Vec<ValueSerializeWrapper<X>>),
426
427    /// Two-dimensional data: [x, y]
428    Pair(Vec<(ValueSerializeWrapper<X>,ValueSerializeWrapper<Y>)>),
429
430    /// Single dimension data as an object with a name field
431    Named(Vec<NamedValue<X>>),
432
433    /// Two-dimensional data with the name: { value: [x, y], name: "str" }
434    NamedPair(Vec<NamedValuePair<X,Y>>),
435}
436
437/// Internal enum to represent the data source for a series
438#[derive(Serialize, Debug, Clone)]
439#[serde(rename_all = "camelCase")]
440pub enum SeriesDataSource<X:AxisKindMarker,Y:AxisKindMarker>{
441    /// Direct data
442    Data(DataVariant<X,Y>),
443    /// Reference to a dataset by index
444    DatasetIndex(usize)
445}
446
447impl<X:AxisKindMarker,Y:AxisKindMarker> From<Vec<(X, Y)>> for SeriesDataSource<X,Y>
448{
449    fn from(value: Vec<(X, Y)>) -> Self {
450        SeriesDataSource::from_pairs(value)
451    }
452}
453
454impl<const N: usize,X:AxisKindMarker,Y:AxisKindMarker> From<[(X, Y); N]> for SeriesDataSource<X,Y>
455{
456    fn from(value: [(X, Y); N]) -> Self {
457        Self::Data(DataVariant::Pair(value.into_iter().map(|(x, y)| (x.into(), y.into())).collect()))
458    }
459}
460
461impl<X:AxisKindMarker,Y:AxisKindMarker> From<Vec<(X, Y, String)>> for SeriesDataSource<X,Y>
462{
463    fn from(value: Vec<(X, Y, String)>) -> Self {
464        SeriesDataSource::from_tuples_with_label(value)
465    }
466}
467
468impl<X:AxisKindMarker,Y:AxisKindMarker> From<Vec<X>> for SeriesDataSource<X,Y>
469{
470    fn from(value: Vec<X>) -> Self {
471        SeriesDataSource::from_values(value)
472    }
473}
474
475impl<X:AxisKindMarker,Y:AxisKindMarker> SeriesDataSource<X,Y> {
476    /// Creates a new SeriesDataSource that references a dataset by index
477    pub fn from_dataset_index(index: usize) -> Self {
478        Self::DatasetIndex(index)
479    }
480
481    /// Creates a SeriesDataSource with a simple array of values
482    pub fn from_values(values: Vec<X>) -> Self {
483        Self::Data(DataVariant::Data(values.into_iter().map(ValueSerializeWrapper::from).collect()))
484    }
485
486    /// Creates a SeriesDataSource with an array of [x,y] pairs
487    pub fn from_pairs(pairs: Vec<(X,Y)>) -> Self {
488        Self::Data(DataVariant::Pair(pairs.into_iter().map(|(x,y)| (x.into(),y.into())).collect()))
489    }
490
491   pub fn from_named_value_pairs(named_pairs: Vec<NamedValuePair<X,Y>>) -> Self{
492       Self::Data(DataVariant::NamedPair(named_pairs))
493   }
494
495
496    /// Creates a SeriesDataSource with named values
497    pub fn with_named_values(named_values: Vec<NamedValue<X>>) -> Self {
498        Self::Data(DataVariant::Named(named_values))
499    }
500
501    pub fn from_labeled_values(values: Vec<(X, String)>) -> Self
502    {
503        let data_named_values = values.into_iter().map(|(x,label)| NamedValue{
504            value: x.into(),
505            name: label
506        }).collect();
507        Self::with_named_values(data_named_values)
508    }
509
510    /// Creates a SeriesDataSource with named pairs
511    pub fn from_tuples_with_label(values: Vec<(X, Y, String)>) -> Self
512    {
513        let data_named_pairs = values.into_iter()
514            .map(|(x, y,label) | NamedValuePair::new(x.into(), y.into(),label))
515            .collect();
516        Self::from_named_value_pairs(data_named_pairs)
517    }
518
519}
520
521
522#[derive(Serialize, Deserialize, Debug, Clone)]
523#[serde(rename_all = "camelCase")]
524pub enum DataPointSymbol {
525    Circle,
526    Rect,
527    RoundRect,
528    Triangle,
529    Diamond,
530    Pin,
531    Arrow,
532    None
533}
534
535/// Series definition
536#[derive(Serialize, Debug, Clone)]
537#[serde(rename_all = "camelCase")]
538pub struct Series<X:AxisKindMarker,Y:AxisKindMarker> {
539    /// Chart type
540    #[serde(skip_serializing_if = "Option::is_none")]
541    pub r#type: Option<SeriesType>,
542
543    /// Series name
544    #[serde(skip_serializing_if = "Option::is_none")]
545    pub name: Option<String>,
546
547    #[serde(skip_serializing_if = "Option::is_none")]
548    pub smooth: Option<bool>,
549
550    #[serde(skip_serializing_if = "Option::is_none")]
551    pub area_style: Option<AreaStyle>,
552
553    /// Data array
554    #[serde(flatten)]
555    pub data: SeriesDataSource<X,Y>,
556
557    #[serde(skip_serializing_if = "Option::is_none")]
558    pub symbol: Option<DataPointSymbol>,
559
560    #[serde(skip_serializing_if = "Option::is_none")]
561    pub symbol_size: Option<usize>,
562
563    /// Additional raw series options
564    #[serde(flatten)]
565    pub extra: Option<Value>,
566}
567
568impl<X:AxisKindMarker,Y:AxisKindMarker> Series <X,Y>{
569    pub fn new(name:String, r#type: SeriesType, data: SeriesDataSource<X,Y>) -> Series<X,Y> {
570        Self{
571            r#type: Some(r#type),
572            name: Some(name),
573            smooth: None,
574            area_style: None,
575            data,
576            symbol: None,
577            symbol_size: None,
578            extra: None,
579        }
580    }
581}
582
583
584#[derive(Serialize, Deserialize, Debug, Clone)]
585#[serde(rename_all = "camelCase")]
586pub enum AxisFillOrigin{
587    Auto,
588    Start,
589    End,
590    Number
591}
592
593#[derive(Serialize, Deserialize, Debug, Clone)]
594#[serde(rename_all = "camelCase")]
595pub struct AreaStyle{
596    #[serde(skip_serializing_if = "Option::is_none")]
597    color: Option<Value>,
598    #[serde(skip_serializing_if = "Option::is_none")]
599    origin: Option<AxisFillOrigin>
600}
601
602#[derive(Serialize, Deserialize, Debug, Clone)]
603#[serde(rename_all = "camelCase")]
604pub struct Transform{
605    pub transform: Vec<DatasetTransform>,
606
607    #[serde(skip_serializing_if = "Option::is_none")]
608    pub from_dataset_index: Option<usize>
609}
610
611#[derive(Serialize, Debug, Clone)]
612#[serde(rename_all = "camelCase")]
613pub struct Source<X:AxisKindMarker,Y:AxisKindMarker>{
614    pub source: Vec<(ValueSerializeWrapper<X>,ValueSerializeWrapper<Y>)>,
615}
616
617impl<X:AxisKindMarker,Y:AxisKindMarker> From<Vec<(X,Y)>> for Source<X,Y>{
618    fn from(value: Vec<(X,Y)>) -> Self {
619        Self{
620            source: value.into_iter().map(|(x,y)| (x.into(),y.into())).collect()
621        }
622    }
623}
624
625impl<const N:usize,X:AxisKindMarker,Y:AxisKindMarker> From<[(X,Y);N]> for Source<X,Y>
626{
627    fn from(value: [(X,Y);N]) -> Self {
628        Self{
629            source: value.into_iter().map(|(x,y)| (x.into(),y.into())).collect()
630        }
631    }
632}
633
634
635#[derive(Serialize, Debug, Clone)]
636#[serde(rename_all = "camelCase")]
637pub struct LabelledSource<X:AxisKindMarker,Y:AxisKindMarker>{
638    pub source: Vec<(ValueSerializeWrapper<X>,ValueSerializeWrapper<Y>,String)>,
639}
640
641
642
643
644
645/// Dataset component for providing and transforming data
646#[derive(Serialize, Debug, Clone)]
647#[serde(rename_all = "camelCase")]
648#[serde(untagged)]
649pub enum DatasetComponent<X:AxisKindMarker,Y:AxisKindMarker> {
650    Source(Source<X,Y>),
651    LabelledSource(LabelledSource<X,Y>),
652    Transform(Transform)
653}
654
655
656impl<const N:usize,X:AxisKindMarker,Y:AxisKindMarker> From<[(X,Y);N]> for DatasetComponent<X,Y>
657{
658    fn from(value: [(X,Y);N]) -> Self {
659        DatasetComponent::Source(value.into())
660    }
661}
662
663impl<X:AxisKindMarker,Y:AxisKindMarker> From<Vec<(X,Y)>> for DatasetComponent<X,Y>
664{
665    fn from(value: Vec<(X, Y)>) -> Self {
666        DatasetComponent::src(value)
667    }
668}
669
670
671impl<const N:usize,X:AxisKindMarker,Y:AxisKindMarker>  From<[(X,Y,String);N]> for DatasetComponent<X,Y>
672{
673    fn from(value: [(X,Y,String);N] ) -> Self {
674        Self::LabelledSource(
675            LabelledSource{
676                source: value.into_iter().map(|(x,y,label)| (x.into(),y.into(),label)).collect()
677            }
678        )
679    }
680}
681
682impl<X:AxisKindMarker,Y:AxisKindMarker> From<Vec<(X,Y,String)>> for DatasetComponent<X,Y>
683{
684    fn from(value: Vec<(X, Y, String)>) -> Self {
685        DatasetComponent::labelled_source(value)
686    }
687}
688
689
690impl<X:AxisKindMarker,Y:AxisKindMarker> DatasetComponent<X,Y> {
691    pub fn tr(transform: DatasetTransform, index: usize) -> Self{
692        Self::Transform(Transform {
693            transform: vec![transform],
694            from_dataset_index: Some(index),
695        })
696    }
697
698    pub fn trs(transform: Vec<DatasetTransform>, index: usize) -> Self{
699        Self::Transform(
700            Transform {
701                transform,
702                from_dataset_index: Some(index)
703            }
704        )
705    }
706
707    pub fn src(source: Vec<(X,Y)>) -> Self {
708        Self::Source(source.into())
709    }
710
711    /// Constructor for the labeled source. Always put the label last
712    pub fn labelled_source(source: Vec<(X,Y,String)>) -> Self {
713        Self::LabelledSource(
714            LabelledSource{
715                source: source.into_iter().map(|(x,y,label)| (x.into(),y.into(),label)).collect()
716            }
717        )
718    }
719}
720
721#[derive(Serialize, Deserialize, Debug, Clone)]
722#[serde(rename_all = "camelCase")]
723pub enum DatasetTransformType {
724    Filter,
725    Sort,
726    Boxplot,
727    #[serde(rename = "ecStat:regression")]
728    Regression,
729    #[serde(rename = "ecStat:clustering")]
730    Clustering
731}
732
733/// Transform applied to a dataset
734#[derive(Serialize, Deserialize, Debug, Clone)]
735#[serde(rename_all = "camelCase")]
736pub struct DatasetTransform {
737    /// Transform type
738    r#type: DatasetTransformType,
739
740    /// Transform configuration
741    config: DatasetTransformConfig,
742
743}
744
745
746impl DatasetTransform {
747
748    pub fn regression(config: RegressionConfig) -> Self {
749        Self{
750            r#type: DatasetTransformType::Regression,
751            config: DatasetTransformConfig::Regression(config)
752        }
753    }
754
755    pub fn clustering(clustering_config: ClusteringConfig)->Self{
756        Self{
757            r#type: DatasetTransformType::Clustering,
758            config: DatasetTransformConfig::Clustering(clustering_config)
759        }
760    }
761
762    pub fn sort(sort_config: SortConfig) -> Self {
763        Self{
764            r#type: DatasetTransformType::Sort,
765            config: DatasetTransformConfig::Sort(sort_config)
766        }
767    }
768
769}
770
771
772#[derive(Serialize, Deserialize, Debug, Clone)]
773#[serde(untagged)]
774enum DatasetTransformConfig {
775    Regression(RegressionConfig),
776    Clustering(ClusteringConfig),
777    Sort(SortConfig)
778}
779
780
781/// regression methods supported by ecStat
782#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
783#[serde(rename_all = "lowercase")]
784pub enum RegressionMethod {
785    Linear,
786    Exponential,
787    Logarithmic,
788    Polynomial,
789}
790
791#[derive(Serialize, Deserialize, Debug, Clone)]
792#[serde(rename_all = "camelCase")]
793pub struct SortConfig {
794    dimension: u8,
795    order : Order
796}
797
798#[derive(Serialize, Deserialize, Debug, Clone)]
799#[serde(rename_all = "camelCase")]
800pub enum Order {
801    Asc,
802    Desc
803}
804
805
806/// Configuration for regression transforms
807#[derive(Serialize, Deserialize, Debug, Clone)]
808#[serde(rename_all = "camelCase")]
809pub struct RegressionConfig {
810    /// Regression method
811    pub method: RegressionMethod,
812
813    /// Polynomial order (only used when the method is Polynomial)
814    #[serde(skip_serializing_if = "Option::is_none")]
815    pub order: Option<u8>,
816
817    /// Additional raw regression config options
818    #[serde(flatten)]
819    pub extra: Option<Value>,
820}
821
822
823
824/// Configuration for clustering transforms
825#[derive(Serialize, Deserialize, Debug, Clone)]
826#[serde(rename_all = "camelCase")]
827pub struct ClusteringConfig {
828
829    /// The number of clusters to generate (must be > 1).
830    pub cluster_count: u8,
831
832    ///dimension to which the cluster index will be written
833    pub output_cluster_index_dimension: u8,
834
835    /// dimensions of source data that will be used in calculation of a cluster
836    pub dimensions : Option<Vec<usize>>,
837
838}