1use super::until::Color;
2use crate::code_handler::parse_context::ParseContext;
3use crate::{translate_enum_with_config, translate_with_config};
4use anyhow::{Result, anyhow};
5use plotly::{Configuration, Layout};
6use serde_json::Value;
7
8pub fn parse_config_obj(
9 config_obj: &mut Value,
10 context: &ParseContext<'_>,
11) -> Result<Configuration> {
12 use plotly::configuration::{DisplayModeBar, DoubleClick};
13
14 let config = translate_with_config! {
15 Configuration::new(),
16 config_obj,
17 context.map(),
18 context.map_eval(),
19 (static_plot, bool),
20 (typeset_math, bool),
21 (editable, bool),
22 (autosizable, bool),
23 (fill_frame, bool),
24 (frame_margins, f64),
25 (scroll_zoom, bool),
26 (show_axis_drag_handles, bool),
27 (show_axis_range_entry_boxes, bool),
28 (show_tips, bool),
29 (show_link, bool),
30 (send_data, bool),
31 (show_edit_in_chart_studio, bool),
32 (double_click_delay, usize),
33 (queue_length, usize),
34 (display_logo, bool),
35 (watermark, bool),
36 }?;
37
38 let config = translate_enum_with_config! {
39 config,
40 config_obj,
41 context.map(),
42 context.map_eval(),
43 (display_mode_bar, {
44 "hover" => DisplayModeBar::Hover,
45 "true" => DisplayModeBar::True,
46 "false" => DisplayModeBar::False,
47 }),
48 (double_click, {
49 "false" => DoubleClick::False,
50 "reset" => DoubleClick::Reset,
51 "autosize" => DoubleClick::AutoSize,
52 "reset+autosize"=> DoubleClick::ResetAutoSize,
53 }),
54 }?;
55
56 Ok(config)
57}
58
59pub fn parse_layout_obj(layout_obj: &mut Value, context: &ParseContext<'_>) -> Result<Layout> {
60 use plotly::layout::{
61 ClickMode, DragMode, GroupClick, HoverMode, ItemClick, ItemSizing, Legend, Margin,
62 TraceOrder, VAlign,
63 };
64
65 let layout = translate_with_config! {
66 Layout::new(),
67 layout_obj,
68 context.map(),
69 context.map_eval(),
70 (title, String),
71 (show_legend, bool),
72 (auto_size, bool),
73 (height, usize),
74 (width, usize),
75 (colorway, Vec<Color>),
76 (plot_background_color, Color),
77 (paper_background_color, Color),
78 (separators, String),
79 (bar_gap, f64),
80 (bar_group_gap, f64),
81 (box_gap, f64),
82 (box_group_gap, f64),
83 }?;
84
85 let layout = translate_enum_with_config! {
86 layout,
87 layout_obj,
88 context.map(),
89 context.map_eval(),
90 (hover_mode, {
91 "x" => HoverMode::X,
92 "y" => HoverMode::Y,
93 "closest" => HoverMode::Closest,
94 "false" => HoverMode::False,
95 "x unified" => HoverMode::XUnified,
96 "y unified" => HoverMode::YUnified,
97 }),
98 (drag_mode, {
99 "zoom" => DragMode::Zoom,
100 "pan" => DragMode::Pan,
101 "select" => DragMode::Select,
102 "lasso" => DragMode::Lasso,
103 "orbit" => DragMode::Orbit,
104 "turntable" => DragMode::Turntable,
105 "false" => DragMode::False,
106 }),
107 (click_mode, {
108 "event" => ClickMode::Event,
109 "select" => ClickMode::Select,
110 "none" => ClickMode::None,
111 }),
112 }?;
113
114 let layout = if let Some(legend_obj) = layout_obj.get_mut("legend")
115 && legend_obj.is_object()
116 {
117 let legend = translate_with_config! {
118 Legend::new(),
119 legend_obj,
120 context.map(),
121 context.map_eval(),
122 (background_color, Color),
123 (border_color, Color),
124 (border_width, usize),
125 (x, f64),
126 (y, f64),
127 (trace_group_gap, usize),
128 (item_width, usize),
129 (title, String),
130 }?;
131
132 let legend = translate_enum_with_config! {
133 legend,
134 legend_obj,
135 context.map(),
136 context.map_eval(),
137 (trace_order, {
138 "reversed" => TraceOrder::Reversed,
139 "grouped" => TraceOrder::Grouped,
140 "reversed+grouped"=> TraceOrder::ReversedGrouped,
141 "normal" => TraceOrder::Normal,
142 }),
143 (item_sizing, {
144 "trace" => ItemSizing::Trace,
145 "constant" => ItemSizing::Constant,
146 }),
147 (item_click, {
148 "toggle" => ItemClick::Toggle,
149 "toggleothers" => ItemClick::ToggleOthers,
150 "false" => ItemClick::False,
151 }),
152 (item_double_click, {
153 "toggle" => ItemClick::Toggle,
154 "toggleothers" => ItemClick::ToggleOthers,
155 "false" => ItemClick::False,
156 }),
157 (valign, {
158 "top" => VAlign::Top,
159 "middle" => VAlign::Middle,
160 "bottom" => VAlign::Bottom,
161 }),
162 (group_click, {
163 "toggleitem" => GroupClick::ToggleItem,
164 "togglegroup"=> GroupClick::ToggleGroup,
165 }),
166 }?;
167
168 layout.legend(legend)
169 } else {
170 layout
171 };
172
173 let layout = if let Some(margin_obj) = layout_obj.get_mut("margin")
174 && margin_obj.is_object()
175 {
176 let margin = translate_with_config! {
177 Margin::new(),
178 margin_obj,
179 context.map(),
180 context.map_eval(),
181 (left, usize),
182 (right, usize),
183 (top, usize),
184 (bottom, usize),
185 (pad, usize),
186 (auto_expand, bool)
187 }?;
188 layout.margin(margin)
189 } else {
190 layout
191 };
192
193 use plotly::common::Font;
194 let layout = if let Some(font_obj) = layout_obj.get_mut("font")
195 && font_obj.is_object()
196 {
197 let font = translate_with_config! {
198 Font::new(),
199 font_obj,
200 context.map(),
201 context.map_eval(),
202 (family, String),
203 (size, usize),
204 (color, Color),
205 }?;
206 layout.font(font)
207 } else {
208 layout
209 };
210
211 use plotly::layout::ColorAxis;
212 let layout = if let Some(ca_obj) = layout_obj.get_mut("coloraxis")
213 && ca_obj.is_object()
214 {
215 let ca = translate_with_config! {
216 ColorAxis::new(),
217 ca_obj,
218 context.map(),
219 context.map_eval(),
220 (cmin, f64),
221 (cmax, f64),
222 (cmid, f64),
223 (auto_color_scale, bool),
224 (reverse_scale, bool),
225 (show_scale, bool),
226 }?;
227 layout.color_axis(ca)
228 } else {
229 layout
230 };
231
232 let layout = if let Some(axis_obj) = layout_obj.get_mut("xaxis")
233 && axis_obj.is_object()
234 {
235 let axis = parse_axis_obj(axis_obj, context)?;
236 layout.x_axis(axis)
237 } else {
238 layout
239 };
240
241 let layout = if let Some(axis_obj) = layout_obj.get_mut("yaxis")
242 && axis_obj.is_object()
243 {
244 let axis = parse_axis_obj(axis_obj, context)?;
245 layout.y_axis(axis)
246 } else {
247 layout
248 };
249
250 let layout = parse_named_axes(layout, layout_obj, context, "x")?;
251 let layout = parse_named_axes(layout, layout_obj, context, "y")?;
252
253 Ok(layout)
254}
255
256fn parse_axis_obj(
257 axis_obj: &mut Value,
258 context: &ParseContext<'_>,
259) -> Result<plotly::layout::Axis> {
260 use crate::code_handler::until::DataPack;
261 use plotly::layout::{Axis, AxisType};
262
263 const AXIS_CONTEXT: &str = "layout axis";
264
265 let axis = translate_with_config! {
266 Axis::new(),
267 axis_obj,
268 context.map(),
269 context.map_eval(),
270 (title, String),
271 (show_grid, bool),
272 (show_line, bool),
273 (zero_line, bool),
274 (visible, bool),
275 (anchor, String),
276 (overlaying, String),
277 (range, Vec<Option<f64>>),
278 (color, Color),
279 (line_color, Color),
280 (grid_color, Color),
281 (tick_prefix, String),
282 (tick_suffix, String),
283 (tick_format, String),
284 (hover_format, String),
285 (category_array, Vec<String>),
286 (fixed_range, bool),
287 (scale_anchor, String),
288 (auto_margin, bool),
289 (show_tick_labels, bool),
290 }?;
291
292 let axis = translate_enum_with_config! {
293 axis,
294 axis_obj,
295 context.map(),
296 context.map_eval(),
297 (category_order, {
298 "trace" => plotly::layout::CategoryOrder::Trace,
299 "category-ascending" => plotly::layout::CategoryOrder::CategoryAscending,
300 "category-descending" => plotly::layout::CategoryOrder::CategoryDescending,
301 "array" => plotly::layout::CategoryOrder::Array,
302 "total-ascending" => plotly::layout::CategoryOrder::TotalAscending,
303 "total-descending" => plotly::layout::CategoryOrder::TotalDescending,
304 "min-ascending" => plotly::layout::CategoryOrder::MinAscending,
305 "min-descending" => plotly::layout::CategoryOrder::MinDescending,
306 "max-ascending" => plotly::layout::CategoryOrder::MaxAscending,
307 "max-descending" => plotly::layout::CategoryOrder::MaxDescending,
308 "sum-ascending" => plotly::layout::CategoryOrder::SumAscending,
309 "sum-descending" => plotly::layout::CategoryOrder::SumDescending,
310 "mean-ascending" => plotly::layout::CategoryOrder::MeanAscending,
311 "mean-descending" => plotly::layout::CategoryOrder::MeanDescending,
312 "median-ascending" => plotly::layout::CategoryOrder::MedianAscending,
313 "median-descending" => plotly::layout::CategoryOrder::MedianDescending,
314 }),
315 }?;
316
317 let axis = if let Some(v) = axis_obj.get_mut("type") {
318 let data = serde_json::from_value::<DataPack<String>>(v.take()).map_err(|e| {
319 anyhow!(
320 "Failed to deserialize field 'type' in {}: {}",
321 AXIS_CONTEXT,
322 e
323 )
324 })?;
325 let s = data.unwrap_from_context(context).map_err(|e| {
326 anyhow!(
327 "Failed to resolve DataPack for field 'type' in {}: {}",
328 AXIS_CONTEXT,
329 e
330 )
331 })?;
332 let at = match s.as_str() {
333 "-" | "linear" => AxisType::Linear,
334 "log" => AxisType::Log,
335 "date" => AxisType::Date,
336 "category" => AxisType::Category,
337 "multicategory" => AxisType::MultiCategory,
338 other => {
339 return Err(anyhow!(
340 "\"{}\" is not a valid value for `type` in {}",
341 other,
342 AXIS_CONTEXT,
343 ));
344 }
345 };
346 axis.type_(at)
347 } else {
348 axis
349 };
350
351 Ok(axis)
352}
353
354fn parse_named_axes(
355 layout: Layout,
356 layout_obj: &mut Value,
357 context: &ParseContext<'_>,
358 prefix: &str,
359) -> Result<Layout> {
360 let mut layout = layout;
361 for i in 2..=8 {
362 let json_key = format!("{}axis{}", prefix, i);
363 let Some(axis_obj) = layout_obj.get_mut(json_key.as_str()) else {
364 continue;
365 };
366 if !axis_obj.is_object() {
367 continue;
368 }
369 let axis = parse_axis_obj(axis_obj, context)?;
370 if prefix == "x" {
371 layout = match i {
372 2 => layout.x_axis2(axis),
373 3 => layout.x_axis3(axis),
374 4 => layout.x_axis4(axis),
375 5 => layout.x_axis5(axis),
376 6 => layout.x_axis6(axis),
377 7 => layout.x_axis7(axis),
378 8 => layout.x_axis8(axis),
379 _ => unreachable!(),
380 };
381 } else {
382 layout = match i {
383 2 => layout.y_axis2(axis),
384 3 => layout.y_axis3(axis),
385 4 => layout.y_axis4(axis),
386 5 => layout.y_axis5(axis),
387 6 => layout.y_axis6(axis),
388 7 => layout.y_axis7(axis),
389 8 => layout.y_axis8(axis),
390 _ => unreachable!(),
391 };
392 }
393 }
394 Ok(layout)
395}