apache_echarts_wrapper/
options.rs

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