1use crate::error::{Result, ResultWithContext, VegaFusionError};
2use crate::proto::gen::tasks::{Task, TzConfig};
3use crate::spec::axis::AxisSpec;
4use crate::spec::data::DataSpec;
5use crate::spec::mark::MarkSpec;
6use crate::spec::projection::ProjectionSpec;
7use crate::spec::scale::ScaleSpec;
8use crate::spec::signal::SignalSpec;
9use crate::spec::title::TitleSpec;
10use crate::spec::visitors::{
11 DefinitionVarsChartVisitor, InputVarsChartVisitor, MakeTaskScopeVisitor, MakeTasksVisitor,
12 UpdateVarsChartVisitor,
13};
14use crate::task_graph::graph::ScopedVariable;
15use crate::task_graph::scope::TaskScope;
16use itertools::sorted;
17use serde::{Deserialize, Serialize};
18use serde_json::Value;
19use std::collections::HashMap;
20
21#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
22pub struct ChartSpec {
23 #[serde(rename = "$schema", default = "default_schema")]
24 pub schema: String,
25
26 #[serde(default, skip_serializing_if = "Vec::is_empty")]
27 pub data: Vec<DataSpec>,
28
29 #[serde(default, skip_serializing_if = "Vec::is_empty")]
30 pub signals: Vec<SignalSpec>,
31
32 #[serde(default, skip_serializing_if = "Vec::is_empty")]
33 pub marks: Vec<MarkSpec>,
34
35 #[serde(default, skip_serializing_if = "Vec::is_empty")]
36 pub scales: Vec<ScaleSpec>,
37
38 #[serde(default, skip_serializing_if = "Vec::is_empty")]
39 pub projections: Vec<ProjectionSpec>,
40
41 #[serde(default, skip_serializing_if = "Vec::is_empty")]
42 pub axes: Vec<AxisSpec>,
43
44 #[serde(skip_serializing_if = "Option::is_none")]
45 pub title: Option<TitleSpec>,
46
47 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
48 pub usermeta: HashMap<String, Value>,
49
50 #[serde(flatten)]
51 pub extra: HashMap<String, Value>,
52}
53
54pub fn default_schema() -> String {
55 String::from("https://vega.github.io/schema/vega/v5.json")
56}
57
58impl ChartSpec {
59 pub fn walk(&self, visitor: &mut dyn ChartVisitor) -> Result<()> {
60 let scope: Vec<u32> = Vec::new();
62 let mut group_index = 0;
63 for mark in &self.marks {
64 if mark.type_ == "group" {
65 let mut nested_scope = scope.clone();
67 nested_scope.push(group_index);
68
69 visitor.visit_group_mark(mark, &nested_scope)?;
70 mark.walk(visitor, &nested_scope)?;
71 group_index += 1;
72 } else {
73 visitor.visit_non_group_mark(mark, &scope)?;
75 }
76 }
77
78 for data in &self.data {
80 visitor.visit_data(data, &scope)?;
81 }
82 for scale in &self.scales {
83 visitor.visit_scale(scale, &scope)?;
84 }
85 for projection in &self.projections {
86 visitor.visit_projection(projection, &scope)?;
87 }
88 for axis in &self.axes {
89 visitor.visit_axis(axis, &scope)?;
90 }
91 for signal in &self.signals {
92 visitor.visit_signal(signal, &scope)?;
93 }
94
95 visitor.visit_chart(self)?;
97
98 Ok(())
99 }
100
101 pub fn walk_mut(&mut self, visitor: &mut dyn MutChartVisitor) -> Result<()> {
102 visitor.visit_chart(self)?;
104
105 let scope: Vec<u32> = Vec::new();
107 for data in &mut self.data {
108 visitor.visit_data(data, &scope)?;
109 }
110 for scale in &mut self.scales {
111 visitor.visit_scale(scale, &scope)?;
112 }
113 for projection in &mut self.projections {
114 visitor.visit_projection(projection, &scope)?;
115 }
116 for axis in &mut self.axes {
117 visitor.visit_axis(axis, &scope)?;
118 }
119 for signal in &mut self.signals {
120 visitor.visit_signal(signal, &scope)?;
121 }
122
123 let mut group_index = 0;
125 for mark in &mut self.marks {
126 if mark.type_ == "group" {
127 let mut nested_scope = scope.clone();
129 nested_scope.push(group_index);
130
131 visitor.visit_group_mark(mark, &nested_scope)?;
132 mark.walk_mut(visitor, &nested_scope)?;
133 group_index += 1;
134 } else {
135 visitor.visit_non_group_mark(mark, &scope)?;
137 }
138 }
139
140 Ok(())
141 }
142
143 pub fn to_task_scope(&self) -> Result<TaskScope> {
144 let mut visitor = MakeTaskScopeVisitor::new();
145 self.walk(&mut visitor)?;
146 Ok(visitor.task_scope)
147 }
148
149 pub fn to_tasks(
150 &self,
151 tz_config: &TzConfig,
152 dataset_fingerprints: &HashMap<String, String>,
153 ) -> Result<Vec<Task>> {
154 let mut visitor = MakeTasksVisitor::new(tz_config, dataset_fingerprints);
155 self.walk(&mut visitor)?;
156 Ok(visitor.tasks)
157 }
158
159 pub fn get_group(&self, group_index: u32) -> Result<&MarkSpec> {
160 self.marks
161 .iter()
162 .filter(|m| m.type_ == "group")
163 .nth(group_index as usize)
164 .with_context(|| format!("No group with index {group_index}"))
165 }
166
167 pub fn get_nested_group(&self, path: &[u32]) -> Result<&MarkSpec> {
168 if path.is_empty() {
169 return Err(VegaFusionError::internal(
170 "Nested group scope may not be empty",
171 ));
172 }
173 let mut group = self.get_group(path[0])?;
174 for group_index in &path[1..] {
175 group = group.get_group(*group_index)?;
176 }
177 Ok(group)
178 }
179
180 pub fn get_group_mut(&mut self, group_index: u32) -> Result<&mut MarkSpec> {
181 self.marks
182 .iter_mut()
183 .filter(|m| m.type_ == "group")
184 .nth(group_index as usize)
185 .with_context(|| format!("No group with index {group_index}"))
186 }
187
188 pub fn get_nested_group_mut(&mut self, path: &[u32]) -> Result<&mut MarkSpec> {
189 if path.is_empty() {
190 return Err(VegaFusionError::internal("Path may not be empty"));
191 }
192 let mut group = self.get_group_mut(path[0])?;
193 for group_index in &path[1..] {
194 group = group.get_group_mut(*group_index)?;
195 }
196 Ok(group)
197 }
198
199 pub fn get_nested_signal(&self, path: &[u32], name: &str) -> Result<&SignalSpec> {
200 let signals = if path.is_empty() {
201 &self.signals
202 } else {
203 let group = self.get_nested_group(path)?;
204 &group.signals
205 };
206 signals
207 .iter()
208 .find(|s| s.name == name)
209 .with_context(|| format!("No signal named {name} found at path {path:?}"))
210 }
211
212 pub fn get_nested_signal_mut(&mut self, path: &[u32], name: &str) -> Result<&mut SignalSpec> {
213 let signals = if path.is_empty() {
214 &mut self.signals
215 } else {
216 let group = self.get_nested_group_mut(path)?;
217 &mut group.signals
218 };
219 signals
220 .iter_mut()
221 .find(|s| s.name == name)
222 .with_context(|| format!("No signal named {name} found at path {path:?}"))
223 }
224
225 pub fn get_nested_data(&self, path: &[u32], name: &str) -> Result<&DataSpec> {
226 let datasets = if path.is_empty() {
227 &self.data
228 } else {
229 let group = self.get_nested_group(path)?;
230 &group.data
231 };
232 datasets
233 .iter()
234 .find(|s| s.name == name)
235 .with_context(|| format!("No data named {name} found at path {path:?}"))
236 }
237
238 pub fn get_nested_data_mut(&mut self, path: &[u32], name: &str) -> Result<&mut DataSpec> {
239 let signals = if path.is_empty() {
240 &mut self.data
241 } else {
242 let group = self.get_nested_group_mut(path)?;
243 &mut group.data
244 };
245 signals
246 .iter_mut()
247 .find(|s| s.name == name)
248 .with_context(|| format!("No data named {name} found at path {path:?}"))
249 }
250
251 pub fn add_nested_signal(
252 &mut self,
253 path: &[u32],
254 spec: SignalSpec,
255 index: Option<usize>,
256 ) -> Result<()> {
257 let signals = if path.is_empty() {
258 &mut self.signals
259 } else {
260 let group = self.get_nested_group_mut(path)?;
261 &mut group.signals
262 };
263 match index {
264 Some(index) => {
265 signals.insert(index, spec);
266 }
267 None => {
268 signals.push(spec);
269 }
270 }
271 Ok(())
272 }
273
274 pub fn add_nested_data(
275 &mut self,
276 path: &[u32],
277 spec: DataSpec,
278 index: Option<usize>,
279 ) -> Result<()> {
280 let data = if path.is_empty() {
281 &mut self.data
282 } else {
283 let group = self.get_nested_group_mut(path)?;
284 &mut group.data
285 };
286 match index {
287 Some(index) => {
288 data.insert(index, spec);
289 }
290 None => {
291 data.push(spec);
292 }
293 }
294 Ok(())
295 }
296
297 pub fn definition_vars(&self) -> Result<Vec<ScopedVariable>> {
298 let mut visitor = DefinitionVarsChartVisitor::new();
299 self.walk(&mut visitor)?;
300 Ok(sorted(visitor.definition_vars).collect())
301 }
302
303 pub fn update_vars(&self, task_scope: &TaskScope) -> Result<Vec<ScopedVariable>> {
304 let mut visitor = UpdateVarsChartVisitor::new(task_scope);
305 self.walk(&mut visitor)?;
306 Ok(sorted(visitor.update_vars).collect())
307 }
308
309 pub fn input_vars(&self, task_scope: &TaskScope) -> Result<Vec<ScopedVariable>> {
310 let mut visitor = InputVarsChartVisitor::new(task_scope);
311 self.walk(&mut visitor)?;
312 Ok(sorted(visitor.input_vars).collect())
313 }
314}
315
316pub trait ChartVisitor {
317 fn visit_chart(&mut self, _chart: &ChartSpec) -> Result<()> {
318 Ok(())
319 }
320 fn visit_data(&mut self, _data: &DataSpec, _scope: &[u32]) -> Result<()> {
321 Ok(())
322 }
323 fn visit_signal(&mut self, _signal: &SignalSpec, _scope: &[u32]) -> Result<()> {
324 Ok(())
325 }
326 fn visit_scale(&mut self, _scale: &ScaleSpec, _scope: &[u32]) -> Result<()> {
327 Ok(())
328 }
329 fn visit_projection(&mut self, _projection: &ProjectionSpec, _scope: &[u32]) -> Result<()> {
330 Ok(())
331 }
332 fn visit_axis(&mut self, _axis: &AxisSpec, _scope: &[u32]) -> Result<()> {
333 Ok(())
334 }
335 fn visit_non_group_mark(&mut self, _mark: &MarkSpec, _scope: &[u32]) -> Result<()> {
336 Ok(())
337 }
338 fn visit_group_mark(&mut self, _mark: &MarkSpec, _scope: &[u32]) -> Result<()> {
339 Ok(())
340 }
341}
342
343pub trait MutChartVisitor {
344 fn visit_chart(&mut self, _chart: &mut ChartSpec) -> Result<()> {
345 Ok(())
346 }
347 fn visit_data(&mut self, _data: &mut DataSpec, _scope: &[u32]) -> Result<()> {
348 Ok(())
349 }
350 fn visit_signal(&mut self, _signal: &mut SignalSpec, _scope: &[u32]) -> Result<()> {
351 Ok(())
352 }
353 fn visit_scale(&mut self, _scale: &mut ScaleSpec, _scope: &[u32]) -> Result<()> {
354 Ok(())
355 }
356 fn visit_projection(&mut self, _projection: &mut ProjectionSpec, _scope: &[u32]) -> Result<()> {
357 Ok(())
358 }
359 fn visit_axis(&mut self, _axis: &mut AxisSpec, _scope: &[u32]) -> Result<()> {
360 Ok(())
361 }
362 fn visit_non_group_mark(&mut self, _mark: &mut MarkSpec, _scope: &[u32]) -> Result<()> {
363 Ok(())
364 }
365 fn visit_group_mark(&mut self, _mark: &mut MarkSpec, _scope: &[u32]) -> Result<()> {
366 Ok(())
367 }
368}