Skip to main content

mdbook_plotly/code_handler/
plot_obj_parser.rs

1pub use super::until;
2use super::until::{Color, Map};
3use crate::{translate, translate_enum};
4use anyhow::{Result, anyhow};
5use plotly::{Configuration, Layout, Plot, Trace};
6use serde_json::Value;
7
8pub mod bar_parser;
9pub mod box_plot_parser;
10pub mod candlestick_parser;
11pub mod contour_parser;
12pub mod density_mapbox_parser;
13pub mod heat_map_parser;
14pub mod histogram_parser;
15pub mod image_parser;
16pub mod mesh3d_parser;
17pub mod ohlc_parser;
18pub mod pie_parser;
19pub mod sankey_parser;
20pub mod scatter3d_parser;
21pub mod scatter_geo_parser;
22pub mod scatter_mapbox_parser;
23pub mod scatter_parser;
24pub mod scatter_polar_parser;
25pub mod surface_parser;
26pub mod table_parser;
27
28pub fn parse(plot_obj: &mut Value) -> Result<Plot> {
29    let mut plot = Plot::new();
30
31    let map = if let Some(map_obj) = plot_obj.get_mut("map") {
32        serde_json::from_value::<Map>(map_obj.take())?
33    } else {
34        Map::new()
35    };
36
37    if let Some(config_obj) = plot_obj.get_mut("config")
38        && config_obj.is_object()
39    {
40        let config = parse_config_obj(config_obj, &map)?;
41        plot.set_configuration(config);
42    }
43
44    if let Some(layout_obj) = plot_obj.get_mut("layout")
45        && layout_obj.is_object()
46    {
47        let layout = parse_layout_obj(layout_obj, &map)?;
48        plot.set_layout(layout);
49    }
50
51    if let Some(data_list) = plot_obj.get_mut("data")
52        && data_list.is_array()
53    {
54        for data in data_list.as_array_mut().unwrap_or_else(|| unreachable!()) {
55            let trace = parse_data_obj(data, &map)?;
56            plot.add_trace(trace);
57        }
58    }
59
60    Ok(plot)
61}
62
63fn parse_config_obj(config_obj: &mut Value, map: &Map) -> Result<Configuration> {
64    use plotly::configuration::{DisplayModeBar, DoubleClick};
65
66    let config = translate! {
67        Configuration::new(),
68        config_obj,
69        map,
70        (static_plot, bool),
71        (typeset_math, bool),
72        (editable, bool),
73        (autosizable, bool),
74        (fill_frame, bool),
75        (frame_margins, f64),
76        (scroll_zoom, bool),
77        (show_axis_drag_handles, bool),
78        (show_axis_range_entry_boxes, bool),
79        (show_tips, bool),
80        (show_link, bool),
81        (send_data, bool),
82        (show_edit_in_chart_studio, bool),
83        (double_click_delay, usize),
84        (queue_length, usize),
85        (display_logo, bool),
86        (watermark, bool),
87    }?;
88
89    let config = translate_enum! {
90        config,
91        config_obj,
92        map,
93        (display_mode_bar, {
94            "hover"  => DisplayModeBar::Hover,
95            "true"   => DisplayModeBar::True,
96            "false"  => DisplayModeBar::False,
97        }),
98        (double_click, {
99            "false"         => DoubleClick::False,
100            "reset"         => DoubleClick::Reset,
101            "autosize"      => DoubleClick::AutoSize,
102            "reset+autosize"=> DoubleClick::ResetAutoSize,
103        }),
104    }?;
105
106    Ok(config)
107}
108
109fn parse_layout_obj(layout_obj: &mut Value, map: &Map) -> Result<Layout> {
110    use plotly::layout::{GroupClick, ItemClick, ItemSizing, Legend, Margin, TraceOrder, VAlign};
111
112    let layout = translate! {
113        Layout::new(),
114        layout_obj,
115        map,
116        (title, String),
117        (show_legend, bool),
118        (auto_size, bool),
119        (height, usize),
120        (width, usize),
121        (colorway, Vec<Color>),
122        (plot_background_color, Color),
123        (paper_background_color, Color),
124        (separators, String),
125    }?;
126
127    let layout = if let Some(legend_obj) = layout_obj.get_mut("legend")
128        && legend_obj.is_object()
129    {
130        let legend = translate! {
131            Legend::new(),
132            legend_obj,
133            map,
134            (background_color, Color),
135            (border_color, Color),
136            (border_width, usize),
137            (x, f64),
138            (y, f64),
139            (trace_group_gap, usize),
140            (item_width, usize),
141            (title, String),
142        }?;
143
144        let legend = translate_enum! {
145            legend,
146            legend_obj,
147            map,
148            (trace_order, {
149                "reversed"        => TraceOrder::Reversed,
150                "grouped"         => TraceOrder::Grouped,
151                "reversed+grouped"=> TraceOrder::ReversedGrouped,
152                "normal"          => TraceOrder::Normal,
153            }),
154            (item_sizing, {
155                "trace"    => ItemSizing::Trace,
156                "constant" => ItemSizing::Constant,
157            }),
158            (item_click, {
159                "toggle"       => ItemClick::Toggle,
160                "toggleothers" => ItemClick::ToggleOthers,
161                "false"        => ItemClick::False,
162            }),
163            (item_double_click, {
164                "toggle"       => ItemClick::Toggle,
165                "toggleothers" => ItemClick::ToggleOthers,
166                "false"        => ItemClick::False,
167            }),
168            (valign, {
169                "top"    => VAlign::Top,
170                "middle" => VAlign::Middle,
171                "bottom" => VAlign::Bottom,
172            }),
173            (group_click, {
174                "toggleitem" => GroupClick::ToggleItem,
175                "togglegroup"=> GroupClick::ToggleGroup,
176            }),
177        }?;
178
179        layout.legend(legend)
180    } else {
181        layout
182    };
183
184    let layout = if let Some(margin_obj) = layout_obj.get_mut("margin")
185        && margin_obj.is_object()
186    {
187        let margin = translate! {
188            Margin::new(),
189            margin_obj,
190            map,
191            (left, usize),
192            (right, usize),
193            (top, usize),
194            (bottom, usize),
195            (pad, usize),
196            (auto_expand, bool)
197        }?;
198        layout.margin(margin)
199    } else {
200        layout
201    };
202
203    Ok(layout)
204}
205
206pub fn parse_data_obj(data_obj: &mut Value, map: &Map) -> Result<Box<dyn Trace>> {
207    let data_type = data_obj
208        .get("type")
209        .and_then(|v| v.as_str())
210        .ok_or_else(|| anyhow!("`type` must be a string"))?;
211    match data_type {
212        "bar" => bar_parser::parse_bar_data(data_obj, map).map(|v| v as Box<dyn Trace>),
213        "box" => box_plot_parser::parse_box_plot_data(data_obj, map).map(|v| v as Box<dyn Trace>),
214        "candlestick" => {
215            candlestick_parser::parse_candlestick_data(data_obj, map).map(|v| v as Box<dyn Trace>)
216        }
217        "contour" => contour_parser::parse_contour_data(data_obj, map).map(|v| v as Box<dyn Trace>),
218        "density_mapbox" => density_mapbox_parser::parse_density_mapbox_data(data_obj, map)
219            .map(|v| v as Box<dyn Trace>),
220        "heatmap" => {
221            heat_map_parser::parse_heat_map_data(data_obj, map).map(|v| v as Box<dyn Trace>)
222        }
223        "histogram" => {
224            histogram_parser::parse_histogram_data(data_obj, map).map(|v| v as Box<dyn Trace>)
225        }
226        "ohlc" => ohlc_parser::parse_ohlc_data(data_obj, map).map(|v| v as Box<dyn Trace>),
227        "image" => image_parser::parse_image_data(data_obj, map).map(|v| v as Box<dyn Trace>),
228        "mesh3d" => mesh3d_parser::parse_mesh3d_data(data_obj, map).map(|v| v as Box<dyn Trace>),
229        "pie" => pie_parser::parse_pie_data(data_obj, map).map(|v| v as Box<dyn Trace>),
230        "sankey" => sankey_parser::parse_sankey_data(data_obj, map).map(|v| v as Box<dyn Trace>),
231        "scatter" => scatter_parser::parse_scatter_data(data_obj, map).map(|v| v as Box<dyn Trace>),
232        "scatter3d" => {
233            scatter3d_parser::parse_scatter3d_data(data_obj, map).map(|v| v as Box<dyn Trace>)
234        }
235        "scatter_geo" => {
236            scatter_geo_parser::parse_scatter_geo_data(data_obj, map).map(|v| v as Box<dyn Trace>)
237        }
238        "scatter_mapbox" => scatter_mapbox_parser::parse_scatter_mapbox_data(data_obj, map)
239            .map(|v| v as Box<dyn Trace>),
240        "scatter_polar" => scatter_polar_parser::parse_scatter_polar_data(data_obj, map)
241            .map(|v| v as Box<dyn Trace>),
242        "surface" => surface_parser::parse_surface_data(data_obj, map).map(|v| v as Box<dyn Trace>),
243        "table" => table_parser::parse_table_data(data_obj, map).map(|v| v as Box<dyn Trace>),
244        unexpected => Err(anyhow!("{} isn't a type in data", unexpected)),
245    }
246}