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 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 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 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 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 #[serde(flatten)]
157 pub encodings: HashMap<String, MarkEncodingsSpec>,
158}
159
160#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
161pub struct MarkEncodingsSpec {
162 #[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}