mdbook-plotly 0.1.9-beta

An mdbook preprocessor that renders plot code blocks (e.g., ```plot) into interactive or static charts during book build.
Documentation
pub use super::until;
use super::until::{Color, Map};
use crate::{translate, translate_enum};
use anyhow::{Result, anyhow};
use plotly::{Configuration, Layout, Plot, Trace};
use serde_json::Value;

pub mod bar_parser;
pub mod candlestick_parser;
pub mod density_mapbox_parser;
pub mod histogram_parser;
pub mod image_parser;
pub mod ohlc_parser;
pub mod pie_parser;
pub mod sankey_parser;
pub mod scatter_geo_parser;
pub mod scatter_mapbox_parser;
pub mod scatter_parser;
pub mod scatter_polar_parser;
pub mod table_parser;

pub fn parse(plot_obj: &mut Value) -> Result<Plot> {
    let mut plot = Plot::new();

    let map = if let Some(map_obj) = plot_obj.get_mut("map") {
        serde_json::from_value::<Map>(map_obj.take())?
    } else {
        Map::new()
    };

    if let Some(config_obj) = plot_obj.get_mut("config")
        && config_obj.is_object()
    {
        let config = parse_config_obj(config_obj, &map)?;
        plot.set_configuration(config);
    }

    if let Some(layout_obj) = plot_obj.get_mut("layout")
        && layout_obj.is_object()
    {
        let layout = parse_layout_obj(layout_obj, &map)?;
        plot.set_layout(layout);
    }

    if let Some(data_list) = plot_obj.get_mut("data")
        && data_list.is_array()
    {
        for data in data_list.as_array_mut().unwrap_or_else(|| unreachable!()) {
            let trace = parse_data_obj(data, &map)?;
            plot.add_trace(trace);
        }
    }

    Ok(plot)
}

fn parse_config_obj(config_obj: &mut Value, map: &Map) -> Result<Configuration> {
    use plotly::configuration::{DisplayModeBar, DoubleClick};

    let config = translate! {
        Configuration::new(),
        config_obj,
        map,
        (static_plot, bool),
        (typeset_math, bool),
        (editable, bool),
        (autosizable, bool),
        (fill_frame, bool),
        (frame_margins, f64),
        (scroll_zoom, bool),
        (show_axis_drag_handles, bool),
        (show_axis_range_entry_boxes, bool),
        (show_tips, bool),
        (show_link, bool),
        (send_data, bool),
        (show_edit_in_chart_studio, bool),
        (double_click_delay, usize),
        (queue_length, usize),
        (display_logo, bool),
        (watermark, bool),
    }?;

    let config = translate_enum! {
        config,
        config_obj,
        map,
        (display_mode_bar, {
            "hover"  => DisplayModeBar::Hover,
            "true"   => DisplayModeBar::True,
            "false"  => DisplayModeBar::False,
        }),
        (double_click, {
            "false"         => DoubleClick::False,
            "reset"         => DoubleClick::Reset,
            "autosize"      => DoubleClick::AutoSize,
            "reset+autosize"=> DoubleClick::ResetAutoSize,
        }),
    }?;

    Ok(config)
}

fn parse_layout_obj(layout_obj: &mut Value, map: &Map) -> Result<Layout> {
    use plotly::layout::{GroupClick, ItemClick, ItemSizing, Legend, Margin, TraceOrder, VAlign};

    let layout = translate! {
        Layout::new(),
        layout_obj,
        map,
        (title, String),
        (show_legend, bool),
        (auto_size, bool),
        (height, usize),
        (width, usize),
        (colorway, Vec<Color>),
        (plot_background_color, Color),
        (paper_background_color, Color),
        (separators, String),
    }?;

    let layout = if let Some(legend_obj) = layout_obj.get_mut("legend")
        && legend_obj.is_object()
    {
        let legend = translate! {
            Legend::new(),
            legend_obj,
            map,
            (background_color, Color),
            (border_color, Color),
            (border_width, usize),
            (x, f64),
            (y, f64),
            (trace_group_gap, usize),
            (item_width, usize),
            (title, String),
        }?;

        let legend = translate_enum! {
            legend,
            legend_obj,
            map,
            (trace_order, {
                "reversed"        => TraceOrder::Reversed,
                "grouped"         => TraceOrder::Grouped,
                "reversed+grouped"=> TraceOrder::ReversedGrouped,
                "normal"          => TraceOrder::Normal,
            }),
            (item_sizing, {
                "trace"    => ItemSizing::Trace,
                "constant" => ItemSizing::Constant,
            }),
            (item_click, {
                "toggle"       => ItemClick::Toggle,
                "toggleothers" => ItemClick::ToggleOthers,
                "false"        => ItemClick::False,
            }),
            (item_double_click, {
                "toggle"       => ItemClick::Toggle,
                "toggleothers" => ItemClick::ToggleOthers,
                "false"        => ItemClick::False,
            }),
            (valign, {
                "top"    => VAlign::Top,
                "middle" => VAlign::Middle,
                "bottom" => VAlign::Bottom,
            }),
            (group_click, {
                "toggleitem" => GroupClick::ToggleItem,
                "togglegroup"=> GroupClick::ToggleGroup,
            }),
        }?;

        layout.legend(legend)
    } else {
        layout
    };

    let layout = if let Some(margin_obj) = layout_obj.get_mut("margin")
        && margin_obj.is_object()
    {
        let margin = translate! {
            Margin::new(),
            margin_obj,
            map,
            (left, usize),
            (right, usize),
            (top, usize),
            (bottom, usize),
            (pad, usize),
            (auto_expand, bool)
        }?;
        layout.margin(margin)
    } else {
        layout
    };

    Ok(layout)
}

pub fn parse_data_obj(data_obj: &mut Value, map: &Map) -> Result<Box<dyn Trace>> {
    let data_type = data_obj
        .get("type")
        .and_then(|v| v.as_str())
        .ok_or_else(|| anyhow!("`type` must be a string"))?;
    match data_type {
        "bar" => bar_parser::parse_bar_data(data_obj, map).map(|v| v as Box<dyn Trace>),
        "candlestick" => {
            candlestick_parser::parse_candlestick_data(data_obj, map).map(|v| v as Box<dyn Trace>)
        }
        "density_mapbox" => density_mapbox_parser::parse_density_mapbox_data(data_obj, map)
            .map(|v| v as Box<dyn Trace>),
        "histogram" => {
            histogram_parser::parse_histogram_data(data_obj, map).map(|v| v as Box<dyn Trace>)
        }
        "ohlc" => ohlc_parser::parse_ohlc_data(data_obj, map).map(|v| v as Box<dyn Trace>),
        "image" => image_parser::parse_image_data(data_obj, map).map(|v| v as Box<dyn Trace>),
        "pie" => pie_parser::parse_pie_data(data_obj, map).map(|v| v as Box<dyn Trace>),
        "sankey" => sankey_parser::parse_sankey_data(data_obj, map).map(|v| v as Box<dyn Trace>),
        "scatter" => scatter_parser::parse_scatter_data(data_obj, map).map(|v| v as Box<dyn Trace>),
        "scatter_geo" => {
            scatter_geo_parser::parse_scatter_geo_data(data_obj, map).map(|v| v as Box<dyn Trace>)
        }
        "scatter_mapbox" => scatter_mapbox_parser::parse_scatter_mapbox_data(data_obj, map)
            .map(|v| v as Box<dyn Trace>),
        "scatter_polar" => scatter_polar_parser::parse_scatter_polar_data(data_obj, map)
            .map(|v| v as Box<dyn Trace>),
        "table" => table_parser::parse_table_data(data_obj, map).map(|v| v as Box<dyn Trace>),
        unexpected => Err(anyhow!("{} isn't a type in data", unexpected)),
    }
}