Skip to main content

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