diff --git a/examples/statistical_charts/src/main.rs b/examples/statistical_charts/src/main.rs index 8bde9c9..f89f076 100644 --- a/examples/statistical_charts/src/main.rs +++ b/examples/statistical_charts/src/main.rs @@ -202,8 +202,9 @@ fn grouped_box_plot() { plot.add_trace(trace2); plot.add_trace(trace3); + let y_axis = Vec::from([Some(Box::new(Axis::new().title("normalized moisture").zero_line(false)))]); let layout = Layout::new() - .y_axis(Axis::new().title("normalized moisture").zero_line(false)) + .y_axis(y_axis) .box_mode(BoxMode::Group); plot.set_layout(layout); @@ -316,9 +317,10 @@ fn grouped_horizontal_box_plot() { plot.add_trace(trace2); plot.add_trace(trace3); + let x_axis = Vec::from([Some(Box::new(Axis::new().title("normalized moisture").zero_line(false)))]); let layout = Layout::new() .title("Grouped Horizontal Box Plot") - .x_axis(Axis::new().title("normalized moisture").zero_line(false)) + .x_axis(x_axis) .box_mode(BoxMode::Group); plot.set_layout(layout); @@ -361,10 +363,9 @@ fn fully_styled_box_plot() { ]; let mut plot = Plot::new(); - let layout = Layout::new() - .title("Points Scored by the Top 9 Scoring NBA Players in 2012") - .y_axis( - Axis::new() + + let y_axis = Vec::from([Some( + Box::new(Axis::new() .auto_range(true) .show_grid(true) .zero_line(true) @@ -372,8 +373,11 @@ fn fully_styled_box_plot() { .grid_color(Rgb::new(255, 255, 255)) .grid_width(1) .zero_line_color(Rgb::new(255, 255, 255)) - .zero_line_width(2), - ) + .zero_line_width(2) + ))]); + let layout = Layout::new() + .title("Points Scored by the Top 9 Scoring NBA Players in 2012") + .y_axis(y_axis ) .margin(Margin::new().left(40).right(30).bottom(80).top(100)) .paper_background_color(Rgb::new(243, 243, 243)) .plot_background_color(Rgb::new(243, 243, 243)) @@ -511,10 +515,13 @@ fn colored_and_styled_histograms() { .opacity(0.75) .auto_bin_x(false) .x_bins(Bins::new(-3.2, 4.0, 0.06)); + + let x_axis = Vec::from([Some(Box::new(Axis::new().title("Value")))]); + let y_axis = Vec::from([Some(Box::new(Axis::new().title("Count")))]); let layout = Layout::new() .title("Colored and Styled Histograms") - .x_axis(Axis::new().title("Value")) - .y_axis(Axis::new().title("Count")) + .x_axis(x_axis) + .y_axis(y_axis) .bar_mode(BarMode::Overlay) .bar_gap(0.05) .bar_group_gap(0.2); diff --git a/examples/subplots/src/main.rs b/examples/subplots/src/main.rs index c78bde5..bf959a5 100644 --- a/examples/subplots/src/main.rs +++ b/examples/subplots/src/main.rs @@ -1,5 +1,6 @@ #![allow(dead_code)] +use std::vec; use plotly::common::{Anchor, AxisSide, Font, Title}; use plotly::layout::{ Annotation, Axis, GridPattern, Layout, LayoutGrid, Legend, RowOrder, TraceOrder, @@ -40,7 +41,7 @@ fn simple_subplot_matches_x_axis() { plot.add_trace(trace1); plot.add_trace(trace2); - let layout = Layout::new().x_axis(Axis::new().matches("x2")).grid( + let layout = Layout::new().x_axis(Vec::from([Some(Box::new(Axis::new().matches("x2")))])).grid( LayoutGrid::new() .rows(1) .columns(2) @@ -50,7 +51,7 @@ fn simple_subplot_matches_x_axis() { plot.show(); } - +// fn simple_subplot_matches_y_axis() { let trace1 = Scatter::new(vec![1, 2, 3], vec![4, 5, 6]).name("trace1"); let trace2 = Scatter::new(vec![20, 30, 40], vec![50, 60, 70]) @@ -62,7 +63,7 @@ fn simple_subplot_matches_y_axis() { plot.add_trace(trace1); plot.add_trace(trace2); - let layout = Layout::new().y_axis(Axis::new().matches("y2")).grid( + let layout = Layout::new().y_axis(Vec::from([Some(Box::new(Axis::new().matches("x2")))])).grid( LayoutGrid::new() .rows(1) .columns(2) @@ -72,7 +73,7 @@ fn simple_subplot_matches_y_axis() { plot.show(); } - +// fn custom_sized_subplot() { let trace1 = Scatter::new(vec![1, 2, 3], vec![4, 5, 6]).name("trace1"); let trace2 = Scatter::new(vec![20, 30, 40], vec![50, 60, 70]) @@ -84,10 +85,14 @@ fn custom_sized_subplot() { plot.add_trace(trace1); plot.add_trace(trace2); + let mut x_axis = Vec::new(); + x_axis.push(Some(Box::new(Axis::new().domain(&[0., 0.7])))); + x_axis.push(Some(Box::new(Axis::new().domain(&[0.8, 1.])))); + let y_axis = Vec::from([Some(Box::new(Axis::new().anchor("x2")))]); + let layout = Layout::new() - .x_axis(Axis::new().domain(&[0., 0.7])) - .y_axis2(Axis::new().anchor("x2")) - .x_axis2(Axis::new().domain(&[0.8, 1.])); + .x_axis(x_axis) + .y_axis(y_axis); plot.set_layout(layout); plot.show(); @@ -162,11 +167,13 @@ fn stacked_subplots_with_shared_x_axis() { plot.add_trace(trace2); plot.add_trace(trace3); + let mut y_axis = Vec::new(); + y_axis.push(Some(Box::new(Axis::new().domain(&[0., 0.33])))); + y_axis.push(Some(Box::new(Axis::new().domain(&[0.33, 0.66])))); + y_axis.push(Some(Box::new(Axis::new().domain(&[0.66, 1.])))); let layout = Layout::new() - .y_axis(Axis::new().domain(&[0., 0.33])) .legend(Legend::new().trace_order(TraceOrder::Reversed)) - .y_axis2(Axis::new().domain(&[0.33, 0.66])) - .y_axis3(Axis::new().domain(&[0.66, 1.])); + .y_axis(y_axis); plot.set_layout(layout); plot.show(); @@ -193,22 +200,29 @@ fn multiple_custom_sized_subplots() { plot.add_trace(trace3); plot.add_trace(trace4); + let mut x_axis = Vec::new(); + x_axis.push(Some(Box::new(Axis::new().domain(&[0., 0.45]).anchor("y1")))); + x_axis.push(Some(Box::new(Axis::new().domain(&[0.55, 1.]).anchor("y2")))); + x_axis.push(Some(Box::new(Axis::new().domain(&[0.55, 1.]).anchor("y3")))); + x_axis.push(Some(Box::new(Axis::new().domain(&[0., 1.]).anchor("y4")))); + + let mut y_axis = Vec::new(); + y_axis.push(Some(Box::new(Axis::new().domain(&[0.5, 1.]).anchor("x1")))); + y_axis.push(Some(Box::new(Axis::new().domain(&[0.8, 1.]).anchor("x2")))); + y_axis.push(Some(Box::new(Axis::new().domain(&[0.5, 0.75]).anchor("x3")))); + y_axis.push(Some(Box::new(Axis::new().domain(&[0., 0.45]).anchor("x4")))); + let layout = Layout::new() .title("Multiple Custom Sized Subplots") - .x_axis(Axis::new().domain(&[0., 0.45]).anchor("y1")) - .y_axis(Axis::new().domain(&[0.5, 1.]).anchor("x1")) - .x_axis2(Axis::new().domain(&[0.55, 1.]).anchor("y2")) - .y_axis2(Axis::new().domain(&[0.8, 1.]).anchor("x2")) - .x_axis3(Axis::new().domain(&[0.55, 1.]).anchor("y3")) - .y_axis3(Axis::new().domain(&[0.5, 0.75]).anchor("x3")) - .x_axis4(Axis::new().domain(&[0., 1.]).anchor("y4")) - .y_axis4(Axis::new().domain(&[0., 0.45]).anchor("x4")); + .x_axis(x_axis) + .y_axis(y_axis); + plot.set_layout(layout); plot.show(); } -// Multiple Axes +// // Multiple Axes fn two_y_axes() { let trace1 = Scatter::new(vec![1, 2, 3], vec![40, 50, 60]).name("trace1"); let trace2 = Scatter::new(vec![2, 3, 4], vec![4, 5, 6]) @@ -219,16 +233,17 @@ fn two_y_axes() { plot.add_trace(trace1); plot.add_trace(trace2); + let mut y_axis = Vec::new(); + y_axis.push(Some(Box::new(Axis::new().title("yaxis title")))); + y_axis.push(Some(Box::new(Axis::new().title(Title::from("yaxis2 title").font(Font::new().color(Rgb::new(148, 103, 189)))) + .tick_font(Font::new().color(Rgb::new(148, 103, 189))) + .overlaying("y") + .side(AxisSide::Right)))); + let layout = Layout::new() .title("Double Y Axis Example") - .y_axis(Axis::new().title("yaxis title")) - .y_axis2( - Axis::new() - .title(Title::from("yaxis2 title").font(Font::new().color(Rgb::new(148, 103, 189)))) - .tick_font(Font::new().color(Rgb::new(148, 103, 189))) - .overlaying("y") - .side(AxisSide::Right), - ); + .y_axis(y_axis); + plot.set_layout(layout); plot.show(); @@ -248,41 +263,38 @@ fn multiple_axes() { plot.add_trace(trace3); plot.add_trace(trace4); - let layout = Layout::new() - .title("multiple y-axes example") - .width(800) - .x_axis(Axis::new().domain(&[0.3, 0.7])) - .y_axis( - Axis::new() + let mut y_axis = Vec::new(); + + y_axis.push(Some(Box::new(Axis::new() .title(Title::from("yaxis title").font(Font::new().color("#1f77b4"))) - .tick_font(Font::new().color("#1f77b4")), - ) - .y_axis2( - Axis::new() + .tick_font(Font::new().color("#1f77b4"))))); + y_axis.push(Some(Box::new(Axis::new() .title(Title::from("yaxis2 title").font(Font::new().color("#ff7f0e"))) .tick_font(Font::new().color("#ff7f0e")) .anchor("free") .overlaying("y") .side(AxisSide::Left) - .position(0.15), - ) - .y_axis3( - Axis::new() + .position(0.15)))); + y_axis.push(Some(Box::new(Axis::new() .title(Title::from("yaxis3 title").font(Font::new().color("#d62728"))) .tick_font(Font::new().color("#d62728")) .anchor("x") .overlaying("y") - .side(AxisSide::Right), - ) - .y_axis4( - Axis::new() + .side(AxisSide::Right)))); + y_axis.push(Some(Box::new(Axis::new() .title(Title::from("yaxis4 title").font(Font::new().color("#9467bd"))) .tick_font(Font::new().color("#9467bd")) .anchor("free") .overlaying("y") .side(AxisSide::Right) - .position(0.85), - ); + .position(0.85)))); + + + let layout = Layout::new() + .title("multiple y-axes example") + .width(800) + .x_axis(Vec::from([Some(Box::new(Axis::new().domain(&[0.3, 0.7])))])) + .y_axis(y_axis); plot.set_layout(layout); plot.show(); diff --git a/plotly/src/layout/mod.rs b/plotly/src/layout/mod.rs index 7a70c23..6e0fac1 100644 --- a/plotly/src/layout/mod.rs +++ b/plotly/src/layout/mod.rs @@ -2,7 +2,7 @@ pub mod themes; pub mod update_menu; use std::borrow::Cow; - +use std::collections::HashMap; use plotly_derive::FieldSetter; use serde::{Serialize, Serializer}; use update_menu::UpdateMenu; @@ -17,6 +17,49 @@ use crate::{ private::{NumOrString, NumOrStringCollection}, }; + +fn serialize_axes(axes: &Option>>>, serializer: S, axis_prefix: &str) -> Result +where + S: Serializer, +{ + let mut map = HashMap::new(); + let axes = axes.as_ref().unwrap(); + + for (i, axis) in axes.iter().enumerate() { + let axe = axis.as_ref().unwrap(); + let key = if i == 0 { + axis_prefix.to_string() + } else { + format!("{}{}", axis_prefix, i + 1) + }; + map.insert(key, axe); + } + + map.serialize(serializer) +} + +fn serialize_x_axes(axes: &Option>>>, serializer: S) -> Result +where + S: Serializer, +{ + serialize_axes(axes, serializer, "xaxis") +} + +fn serialize_y_axes(axes: &Option>>>, serializer: S) -> Result +where + S: Serializer, +{ + serialize_axes(axes, serializer, "yaxis") +} + + +fn serialize_z_axes(axes: &Option>>>, serializer: S) -> Result +where + S: Serializer, +{ + serialize_axes(axes, serializer, "zaxis") +} + #[derive(Serialize, Debug, Clone)] #[serde(rename_all = "lowercase")] pub enum AxisType { @@ -1298,7 +1341,7 @@ pub enum DragMode3D { impl Serialize for DragMode3D { fn serialize(&self, serializer: S) -> Result where - S: serde::Serializer, + S: Serializer, { match *self { Self::Zoom => serializer.serialize_str("zoom"), @@ -1706,38 +1749,10 @@ pub struct LayoutTemplate { grid: Option, calendar: Option, - #[serde(rename = "xaxis")] - x_axis: Option>, - #[serde(rename = "yaxis")] - y_axis: Option>, - #[serde(rename = "xaxis2")] - x_axis2: Option>, - #[serde(rename = "yaxis2")] - y_axis2: Option>, - #[serde(rename = "xaxis3")] - x_axis3: Option>, - #[serde(rename = "yaxis3")] - y_axis3: Option>, - #[serde(rename = "xaxis4")] - x_axis4: Option>, - #[serde(rename = "yaxis4")] - y_axis4: Option>, - #[serde(rename = "xaxis5")] - x_axis5: Option>, - #[serde(rename = "yaxis5")] - y_axis5: Option>, - #[serde(rename = "xaxis6")] - x_axis6: Option>, - #[serde(rename = "yaxis6")] - y_axis6: Option>, - #[serde(rename = "xaxis7")] - x_axis7: Option>, - #[serde(rename = "yaxis7")] - y_axis7: Option>, - #[serde(rename = "xaxis8")] - x_axis8: Option>, - #[serde(rename = "yaxis8")] - y_axis8: Option>, + #[serde(flatten, serialize_with = "serialize_x_axes")] + x_axis: Option>>>, + #[serde(flatten, serialize_with = "serialize_y_axes")] + y_axis: Option>>>, // ternary: Option, scene: Option, @@ -1873,55 +1888,12 @@ pub struct Layout { grid: Option, calendar: Option, - #[serde(rename = "xaxis")] - x_axis: Option>, - #[serde(rename = "yaxis")] - y_axis: Option>, - #[serde(rename = "zaxis")] - z_axis: Option>, - - #[serde(rename = "xaxis2")] - x_axis2: Option>, - #[serde(rename = "yaxis2")] - y_axis2: Option>, - #[serde(rename = "zaxis2")] - z_axis2: Option>, - #[serde(rename = "xaxis3")] - x_axis3: Option>, - #[serde(rename = "yaxis3")] - y_axis3: Option>, - #[serde(rename = "zaxis3")] - z_axis3: Option>, - #[serde(rename = "xaxis4")] - x_axis4: Option>, - #[serde(rename = "yaxis4")] - y_axis4: Option>, - #[serde(rename = "zaxis4")] - z_axis4: Option>, - #[serde(rename = "xaxis5")] - x_axis5: Option>, - #[serde(rename = "yaxis5")] - y_axis5: Option>, - #[serde(rename = "zaxis5")] - z_axis5: Option>, - #[serde(rename = "xaxis6")] - x_axis6: Option>, - #[serde(rename = "yaxis6")] - y_axis6: Option>, - #[serde(rename = "zaxis6")] - z_axis6: Option>, - #[serde(rename = "xaxis7")] - x_axis7: Option>, - #[serde(rename = "yaxis7")] - y_axis7: Option>, - #[serde(rename = "zaxis7")] - z_axis7: Option>, - #[serde(rename = "xaxis8")] - x_axis8: Option>, - #[serde(rename = "yaxis8")] - y_axis8: Option>, - #[serde(rename = "zaxis8")] - z_axis8: Option>, + #[serde(flatten, serialize_with = "serialize_x_axes")] + x_axis: Option>>>, + #[serde(flatten, serialize_with = "serialize_y_axes")] + y_axis: Option>>>, + #[serde(flatten, serialize_with = "serialize_z_axes")] + z_axis: Option>>>, // ternary: Option, scene: Option, @@ -2961,6 +2933,13 @@ mod tests { #[test] fn test_serialize_layout_template() { + let mut xaxis: Vec>> = Vec::new(); + let mut yaxis: Vec>> = Vec::new(); + for _ in 0..8 { + xaxis.push(Some(Box::new(Axis::new()))); + yaxis.push(Some(Box::new(Axis::new()))); + } + let layout_template = LayoutTemplate::new() .title("Title") .show_legend(false) @@ -2987,22 +2966,8 @@ mod tests { .hover_label(Label::new()) .grid(LayoutGrid::new()) .calendar(Calendar::Jalali) - .x_axis(Axis::new()) - .x_axis2(Axis::new()) - .x_axis3(Axis::new()) - .x_axis4(Axis::new()) - .x_axis5(Axis::new()) - .x_axis6(Axis::new()) - .x_axis7(Axis::new()) - .x_axis8(Axis::new()) - .y_axis(Axis::new()) - .y_axis2(Axis::new()) - .y_axis3(Axis::new()) - .y_axis4(Axis::new()) - .y_axis5(Axis::new()) - .y_axis6(Axis::new()) - .y_axis7(Axis::new()) - .y_axis8(Axis::new()) + .x_axis(xaxis) + .y_axis(yaxis) .annotations(vec![Annotation::new()]) .shapes(vec![Shape::new()]) .new_shape(NewShape::new()) @@ -3103,6 +3068,13 @@ mod tests { #[test] fn test_serialize_layout() { + let mut xaxis: Vec>> = Vec::new(); + let mut yaxis: Vec>> = Vec::new(); + for _ in 0..8 { + xaxis.push(Some(Box::new(Axis::new()))); + yaxis.push(Some(Box::new(Axis::new()))); + } + let layout = Layout::new() .title("Title") .title(String::from("Title")) @@ -3132,22 +3104,8 @@ mod tests { .template(Template::new()) .grid(LayoutGrid::new()) .calendar(Calendar::Jalali) - .x_axis(Axis::new()) - .x_axis2(Axis::new()) - .x_axis3(Axis::new()) - .x_axis4(Axis::new()) - .x_axis5(Axis::new()) - .x_axis6(Axis::new()) - .x_axis7(Axis::new()) - .x_axis8(Axis::new()) - .y_axis(Axis::new()) - .y_axis2(Axis::new()) - .y_axis3(Axis::new()) - .y_axis4(Axis::new()) - .y_axis5(Axis::new()) - .y_axis6(Axis::new()) - .y_axis7(Axis::new()) - .y_axis8(Axis::new()) + .x_axis(xaxis) + .y_axis(yaxis) .annotations(vec![Annotation::new()]) .shapes(vec![Shape::new()]) .new_shape(NewShape::new()) @@ -3169,7 +3127,7 @@ mod tests { .extend_pie_colors(true) .sunburst_colorway(vec!["#654654"]) .extend_sunburst_colors(false) - .z_axis(Axis::new()) + .z_axis(Vec::from([Some(Box::new(Axis::new()))])) .scene(LayoutScene::new()); let expected = json!({ diff --git a/plotly/src/layout/themes.rs b/plotly/src/layout/themes.rs index a687caa..e5f0cd5 100644 --- a/plotly/src/layout/themes.rs +++ b/plotly/src/layout/themes.rs @@ -64,22 +64,22 @@ pub static PLOTLY_WHITE: Lazy