vegafusion_core/spec/
mark.rs

1use crate::error::{Result, ResultWithContext, VegaFusionError};
2use crate::spec::axis::AxisSpec;
3use crate::spec::chart::{ChartVisitor, MutChartVisitor};
4use crate::spec::data::DataSpec;
5use crate::spec::scale::ScaleSpec;
6use crate::spec::signal::SignalSpec;
7use crate::spec::title::TitleSpec;
8use crate::spec::transform::aggregate::AggregateOpSpec;
9use crate::spec::values::{Field, StringOrStringList};
10use serde::{Deserialize, Serialize};
11use serde_json::{Number, Value};
12use std::collections::HashMap;
13
14#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
15pub struct MarkSpec {
16    #[serde(rename = "type")]
17    pub type_: String,
18
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub name: Option<String>,
21
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub from: Option<MarkFromSpec>,
24
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub sort: Option<MarkSort>,
27
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub encode: Option<MarkEncodeSpec>,
30
31    #[serde(default, skip_serializing_if = "Vec::is_empty")]
32    pub data: Vec<DataSpec>,
33
34    #[serde(default, skip_serializing_if = "Vec::is_empty")]
35    pub signals: Vec<SignalSpec>,
36
37    #[serde(default, skip_serializing_if = "Vec::is_empty")]
38    pub marks: Vec<MarkSpec>,
39
40    #[serde(default, skip_serializing_if = "Vec::is_empty")]
41    pub scales: Vec<ScaleSpec>,
42
43    #[serde(default, skip_serializing_if = "Vec::is_empty")]
44    pub axes: Vec<AxisSpec>,
45
46    #[serde(default, skip_serializing_if = "Vec::is_empty")]
47    pub transform: Vec<Value>,
48
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub title: Option<TitleSpec>,
51
52    #[serde(flatten)]
53    pub extra: HashMap<String, Value>,
54}
55
56impl MarkSpec {
57    pub fn walk(&self, visitor: &mut dyn ChartVisitor, scope: &[u32]) -> Result<()> {
58        // Top-level
59        let scope = Vec::from(scope);
60        for data in &self.data {
61            visitor.visit_data(data, &scope)?;
62        }
63        for scale in &self.scales {
64            visitor.visit_scale(scale, &scope)?;
65        }
66        for axis in &self.axes {
67            visitor.visit_axis(axis, &scope)?;
68        }
69        for signal in &self.signals {
70            visitor.visit_signal(signal, &scope)?;
71        }
72        let mut group_index = 0;
73        for mark in &self.marks {
74            if mark.type_ == "group" {
75                let mut nested_scope = scope.clone();
76                nested_scope.push(group_index);
77
78                visitor.visit_group_mark(mark, &nested_scope)?;
79                mark.walk(visitor, &nested_scope)?;
80
81                group_index += 1;
82            } else {
83                // Keep parent scope
84                visitor.visit_non_group_mark(mark, &scope)?;
85            }
86        }
87
88        Ok(())
89    }
90
91    pub fn walk_mut(&mut self, visitor: &mut dyn MutChartVisitor, scope: &[u32]) -> Result<()> {
92        // Top-level
93        let scope = Vec::from(scope);
94        for data in &mut self.data {
95            visitor.visit_data(data, &scope)?;
96        }
97        for scale in &mut self.scales {
98            visitor.visit_scale(scale, &scope)?;
99        }
100        for axis in &mut self.axes {
101            visitor.visit_axis(axis, &scope)?;
102        }
103        for signal in &mut self.signals {
104            visitor.visit_signal(signal, &scope)?;
105        }
106        let mut group_index = 0;
107        for mark in &mut self.marks {
108            if mark.type_ == "group" {
109                let mut nested_scope = scope.clone();
110                nested_scope.push(group_index);
111
112                visitor.visit_group_mark(mark, &nested_scope)?;
113                mark.walk_mut(visitor, &nested_scope)?;
114
115                group_index += 1;
116            } else {
117                // Keep parent scope
118                visitor.visit_non_group_mark(mark, &scope)?;
119            }
120        }
121
122        Ok(())
123    }
124
125    pub fn get_group(&self, group_index: u32) -> Result<&MarkSpec> {
126        self.marks
127            .iter()
128            .filter(|m| m.type_ == "group")
129            .nth(group_index as usize)
130            .with_context(|| format!("No group with index {group_index}"))
131    }
132
133    pub fn get_group_mut(&mut self, group_index: u32) -> Result<&mut MarkSpec> {
134        self.marks
135            .iter_mut()
136            .filter(|m| m.type_ == "group")
137            .nth(group_index as usize)
138            .with_context(|| format!("No group with index {group_index}"))
139    }
140
141    pub fn get_nested_group_mut(&mut self, path: &[u32]) -> Result<&mut MarkSpec> {
142        if path.is_empty() {
143            return Err(VegaFusionError::internal("Path may not be empty"));
144        }
145        let mut group = self.get_group_mut(path[0])?;
146        for group_index in &path[1..] {
147            group = group.get_group_mut(*group_index)?;
148        }
149        Ok(group)
150    }
151}
152
153#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
154pub struct MarkEncodeSpec {
155    // e.g. enter, update, hover, etc.
156    #[serde(flatten)]
157    pub encodings: HashMap<String, MarkEncodingsSpec>,
158}
159
160#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
161pub struct MarkEncodingsSpec {
162    // e.g. x, fill, width, etc.
163    #[serde(flatten)]
164    pub channels: HashMap<String, MarkEncodingOrList>,
165}
166
167#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
168#[serde(untagged)]
169pub enum MarkEncodingOrList {
170    List(Vec<MarkEncodingSpec>),
171    Scalar(Box<MarkEncodingSpec>),
172}
173
174impl MarkEncodingOrList {
175    pub fn to_vec(&self) -> Vec<MarkEncodingSpec> {
176        match self {
177            MarkEncodingOrList::List(m) => m.clone(),
178            MarkEncodingOrList::Scalar(m) => vec![m.as_ref().clone()],
179        }
180    }
181}
182
183#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
184pub struct MarkEncodingSpec {
185    #[serde(skip_serializing_if = "Option::is_none")]
186    pub value: Option<Value>,
187
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub field: Option<MarkEncodingField>,
190
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub scale: Option<String>,
193
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub band: Option<Number>,
196
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub signal: Option<String>,
199
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub test: Option<String>,
202
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub offset: Option<EncodingOffset>,
205
206    #[serde(flatten)]
207    pub extra: HashMap<String, Value>,
208}
209
210#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
211pub struct MarkFromSpec {
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub data: Option<String>,
214
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub facet: Option<MarkFacetSpec>,
217}
218
219#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
220pub struct MarkFacetSpec {
221    pub data: String,
222    pub name: String,
223
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub groupby: Option<StringOrStringList>,
226
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub aggregate: Option<MarkFacetAggregate>,
229
230    #[serde(flatten)]
231    pub extra: HashMap<String, Value>,
232}
233
234#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
235#[serde(untagged)]
236pub enum MarkEncodingField {
237    Field(String),
238    Object(MarkEncodingFieldObject),
239}
240
241#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
242pub struct MarkEncodingFieldObject {
243    pub signal: Option<String>,
244    pub datum: Option<String>,
245    pub group: Option<String>,
246    pub parent: Option<String>,
247
248    #[serde(flatten)]
249    pub extra: HashMap<String, Value>,
250}
251
252#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
253pub struct MarkSort {
254    pub field: StringOrStringList,
255
256    #[serde(flatten)]
257    pub extra: HashMap<String, Value>,
258}
259
260#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
261pub struct MarkFacetAggregate {
262    #[serde(skip_serializing_if = "Option::is_none")]
263    pub fields: Option<Vec<Option<Field>>>,
264
265    #[serde(skip_serializing_if = "Option::is_none")]
266    pub ops: Option<Vec<AggregateOpSpec>>,
267
268    #[serde(rename = "as", skip_serializing_if = "Option::is_none")]
269    pub as_: Option<Vec<Option<String>>>,
270
271    #[serde(skip_serializing_if = "Option::is_none")]
272    pub cross: Option<bool>,
273}
274
275#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
276#[serde(untagged)]
277pub enum EncodingOffset {
278    Encoding(Box<MarkEncodingSpec>),
279    Value(Value),
280}