From 9dcc9414912fb092b7c49f4913b762a503e5c4ba Mon Sep 17 00:00:00 2001 From: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> Date: Fri, 27 Dec 2024 19:15:43 +0100 Subject: [PATCH 1/3] add Pie Chart trace - add examples - add section in mdBook Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --- docs/book/src/SUMMARY.md | 7 +- docs/book/src/recipes/basic_charts.md | 3 +- .../src/recipes/basic_charts/pie_charts.md | 35 ++ docs/book/src/recipes/img/pie_charts.png | Bin 0 -> 36832 bytes examples/basic_charts/src/main.rs | 137 ++++++- plotly/src/common/mod.rs | 11 + plotly/src/lib.rs | 2 +- plotly/src/plot.rs | 1 + plotly/src/traces/mod.rs | 2 + plotly/src/traces/pie.rs | 378 ++++++++++++++++++ 10 files changed, 567 insertions(+), 9 deletions(-) create mode 100644 docs/book/src/recipes/basic_charts/pie_charts.md create mode 100644 docs/book/src/recipes/img/pie_charts.png create mode 100644 plotly/src/traces/pie.rs diff --git a/docs/book/src/SUMMARY.md b/docs/book/src/SUMMARY.md index a365aec3..4b0e8577 100644 --- a/docs/book/src/SUMMARY.md +++ b/docs/book/src/SUMMARY.md @@ -10,15 +10,16 @@ - [Basic Charts](./recipes/basic_charts.md) - [Scatter Plots](./recipes/basic_charts/scatter_plots.md) - [Line Charts](./recipes/basic_charts/line_charts.md) - - [Bar Charts](./recipes/basic_charts/bar_charts.md) - - [Sankey Diagrams](./recipes/basic_charts/sankey_diagrams.md) + - [Bar Charts](./recipes/basic_charts/bar_charts.md) + - [Pie Charts](./recipes/basic_charts/pie_charts.md) + - [Sankey Diagrams](./recipes/basic_charts/sankey_diagrams.md) - [Statistical Charts](./recipes/statistical_charts.md) - [Error Bars](./recipes/statistical_charts/error_bars.md) - [Box Plots](./recipes/statistical_charts/box_plots.md) - [Histograms](./recipes/statistical_charts/histograms.md) - [Scientific Charts](./recipes/scientific_charts.md) - [Contour Plots](./recipes/scientific_charts/contour_plots.md) - - [Heatmaps](./recipes/scientific_charts/heatmaps.md) + - [Heatmaps](./recipes/scientific_charts/heatmaps.md) - [Financial Charts](./recipes/financial_charts.md) - [Time Series and Date Axes](./recipes/financial_charts/time_series_and_date_axes.md) - [Candlestick Charts](./recipes/financial_charts/candlestick_charts.md) diff --git a/docs/book/src/recipes/basic_charts.md b/docs/book/src/recipes/basic_charts.md index c8e3a77f..aaf9f5a5 100644 --- a/docs/book/src/recipes/basic_charts.md +++ b/docs/book/src/recipes/basic_charts.md @@ -6,5 +6,6 @@ Kind | Link :---|:----: Scatter Plots |[![Scatter Plots](./img/line_and_scatter_plot.png)](./basic_charts/scatter_plots.md) Line Charts | [![Line Charts](./img/line_shape_options_for_interpolation.png)](./basic_charts/line_charts.md) -Bar Charts | [![Scatter Plots](./img/bar_chart_with_error_bars.png)](./basic_charts/scatter_plots.md) +Bar Charts | [![Bar Charts](./img/bar_chart_with_error_bars.png)](./basic_charts/scatter_plots.md) +Pie Charts | [![Pie Charts](./img/pie_charts.png)](./basic_charts/pie_charts.md) Sankey Diagrams | [![Sankey Diagrams](./img/basic_sankey.png)](./basic_charts/sankey_diagrams.md) diff --git a/docs/book/src/recipes/basic_charts/pie_charts.md b/docs/book/src/recipes/basic_charts/pie_charts.md new file mode 100644 index 00000000..941770f1 --- /dev/null +++ b/docs/book/src/recipes/basic_charts/pie_charts.md @@ -0,0 +1,35 @@ +# Pie Charts + +The following imports have been used to produce the plots below: + +```rust,no_run +use plotly::common::{Domain, Font, HoverInfo, Orientation}; +use plotly::layout::{ + Annotation, Layout, LayoutGrid}, +use plotly::layout::Layout; +use plotly::{Pie, Plot}; +``` + +The `to_inline_html` method is used to produce the html plot displayed in this page. + + +## Basic Pie Chart +```rust,no_run +{{#include ../../../../../examples/basic_charts/src/main.rs:basic_pie_chart}} +``` + +{{#include ../../../../../examples/basic_charts/out/basic_pie_chart.html}} + +## Grouped Pie Chart +```rust,no_run +{{#include ../../../../../examples/basic_charts/src/main.rs:grouped_donout_pie_charts}} +``` + +{{#include ../../../../../examples/basic_charts/out/grouped_donout_pie_charts.html}} + +## Pie Chart Text Control +```rust,no_run +{{#include ../../../../../examples/basic_charts/src/main.rs:pie_chart_text_control}} +``` + +{{#include ../../../../../examples/basic_charts/out/pie_chart_text_control.html}} \ No newline at end of file diff --git a/docs/book/src/recipes/img/pie_charts.png b/docs/book/src/recipes/img/pie_charts.png new file mode 100644 index 0000000000000000000000000000000000000000..5e114d76fa8ac08841dd5493654f99f6b3b6fdef GIT binary patch literal 36832 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 Plot { } // ANCHOR_END: table_chart +// Pie Charts +// ANCHOR: basic_pie_chart +fn basic_pie_chart(show: bool) -> Plot { + let values = vec![2, 3, 5]; + let labels = vec!["giraffes", "orangutans", "monkeys"]; + let t = Pie::new(values).labels(labels); + let mut plot = Plot::new(); + plot.add_trace(t); + + if show { + plot.show(); + } + plot +} +// ANCHOR_END: basic_pie_chart + +// ANCHOR: pie_chart_text_control +fn pie_chart_text_control(show: bool) -> Plot { + let values = vec![2, 3, 4, 4]; + let labels = vec!["Wages", "Operating expenses", "Cost of sales", "Insurance"]; + let t = Pie::new(values) + .labels(labels) + .automargin(true) + .show_legend(true) + .text_position(plotly::common::Position::Outside) + .name("Costs") + .text_info("label+percent"); + let mut plot = Plot::new(); + plot.add_trace(t); + + let layout = Layout::new().height(700).width(700).show_legend(true); + plot.set_layout(layout); + + if show { + plot.show(); + } + plot +} +// ANCHOR_END: pie_chart_text_control + +// ANCHOR: grouped_donout_pie_charts +fn grouped_donout_pie_charts(show: bool) -> Plot { + let mut plot = Plot::new(); + + let values = vec![16, 15, 12, 6, 5, 4, 42]; + let labels = vec![ + "US", + "China", + "European Union", + "Russian Federation", + "Brazil", + "India", + "Rest of World", + ]; + let t = Pie::new(values) + .labels(labels) + .name("GHG Emissions") + .hover_info(HoverInfo::All) + .text("GHG") + .hole(0.4) + .domain(Domain::new().column(0)); + plot.add_trace(t); + + let values = vec![27, 11, 25, 8, 1, 3, 25]; + let labels = vec![ + "US", + "China", + "European Union", + "Russian Federation", + "Brazil", + "India", + "Rest of World", + ]; + + let t = Pie::new(values) + .labels(labels) + .name("CO2 Emissions") + .hover_info(HoverInfo::All) + .text("CO2") + .text_position(plotly::common::Position::Inside) + .hole(0.4) + .domain(Domain::new().column(1)); + plot.add_trace(t); + + let layout = Layout::new() + .title("Global Emissions 1990-2011") + .height(400) + .width(600) + .annotations(vec![ + Annotation::new() + .font(Font::new().size(20)) + .show_arrow(false) + .text("GHG") + .x(0.17) + .y(0.5), + Annotation::new() + .font(Font::new().size(20)) + .show_arrow(false) + .text("CO2") + .x(0.82) + .y(0.5), + ]) + .show_legend(false) + .grid( + LayoutGrid::new() + .columns(2) + .rows(1) + .pattern(plotly::layout::GridPattern::Independent), + ); + plot.set_layout(layout); + + if show { + plot.show(); + } + plot +} +// ANCHOR_END: grouped_donout_pie_charts + fn write_example_to_html(plot: Plot, name: &str) { std::fs::create_dir_all("./out").unwrap(); let html = plot.to_inline_html(Some(name)); @@ -869,4 +990,12 @@ fn main() { // Sankey Diagrams write_example_to_html(basic_sankey_diagram(false), "basic_sankey_diagram"); + + // Pie Charts + write_example_to_html(basic_pie_chart(false), "basic_pie_chart"); + write_example_to_html(pie_chart_text_control(false), "pie_chart_text_control"); + write_example_to_html( + grouped_donout_pie_charts(false), + "grouped_donout_pie_charts", + ); } diff --git a/plotly/src/common/mod.rs b/plotly/src/common/mod.rs index 1723ebf7..81b5d551 100644 --- a/plotly/src/common/mod.rs +++ b/plotly/src/common/mod.rs @@ -153,10 +153,16 @@ pub enum ConstrainText { #[derive(Serialize, Clone, Debug)] pub enum Orientation { + #[serde(rename = "a")] + Auto, #[serde(rename = "v")] Vertical, #[serde(rename = "h")] Horizontal, + #[serde(rename = "r")] + Radial, + #[serde(rename = "t")] + Tangential, } #[derive(Serialize, Clone, Debug)] @@ -225,6 +231,7 @@ pub enum PlotType { Surface, DensityMapbox, Table, + Pie, } #[derive(Serialize, Clone, Debug)] @@ -273,6 +280,10 @@ pub enum Position { BottomCenter, #[serde(rename = "bottom right")] BottomRight, + #[serde(rename = "inside")] + Inside, + #[serde(rename = "outside")] + Outside, } #[derive(Serialize, Clone, Debug)] diff --git a/plotly/src/lib.rs b/plotly/src/lib.rs index dbd18add..c9d89c40 100644 --- a/plotly/src/lib.rs +++ b/plotly/src/lib.rs @@ -36,7 +36,7 @@ pub use traces::{ // Bring the different trace types into the top-level scope pub use traces::{ Bar, BoxPlot, Candlestick, Contour, DensityMapbox, HeatMap, Histogram, Image, Mesh3D, Ohlc, - Sankey, Scatter, Scatter3D, ScatterMapbox, ScatterPolar, Surface, Table, + Pie, Sankey, Scatter, Scatter3D, ScatterMapbox, ScatterPolar, Surface, Table, }; pub trait Restyle: serde::Serialize {} diff --git a/plotly/src/plot.rs b/plotly/src/plot.rs index 717cee02..f6213dd2 100644 --- a/plotly/src/plot.rs +++ b/plotly/src/plot.rs @@ -585,6 +585,7 @@ impl PartialEq for Plot { mod tests { use std::path::PathBuf; + #[cfg(feature = "kaleido")] use base64::{engine::general_purpose, Engine as _}; use serde_json::{json, to_value}; diff --git a/plotly/src/traces/mod.rs b/plotly/src/traces/mod.rs index f12305c3..515072b2 100644 --- a/plotly/src/traces/mod.rs +++ b/plotly/src/traces/mod.rs @@ -10,6 +10,7 @@ pub mod histogram; pub mod image; pub mod mesh3d; mod ohlc; +pub mod pie; pub mod sankey; mod scatter; mod scatter3d; @@ -27,6 +28,7 @@ pub use heat_map::HeatMap; pub use histogram::Histogram; pub use mesh3d::Mesh3D; pub use ohlc::Ohlc; +pub use pie::Pie; pub use sankey::Sankey; pub use scatter::Scatter; pub use scatter3d::Scatter3D; diff --git a/plotly/src/traces/pie.rs b/plotly/src/traces/pie.rs new file mode 100644 index 00000000..d387c8a1 --- /dev/null +++ b/plotly/src/traces/pie.rs @@ -0,0 +1,378 @@ +//! Pie chart plot + +use plotly_derive::FieldSetter; +use serde::Serialize; + +use crate::private::{NumOrString, NumOrStringCollection}; +use crate::{ + common::{ + Dim, Domain, Font, HoverInfo, Label, LegendGroupTitle, Marker, Orientation, PlotType, + Position, Visible, + }, + Trace, +}; + +#[derive(Debug, Clone, Serialize)] +pub enum PieDirection { + Clockwise, + CounterClockwise, +} + +/// Construct a Pie Chart trace. +/// +/// # Examples +/// +/// ``` +/// use plotly::Pie; +/// +/// let trace = Pie::new( +/// vec![2, 3, 5]); +/// +/// let expected = serde_json::json!({ +/// "type": "pie", +/// "values": [2, 3, 5], +/// }); +/// +/// assert_eq!(serde_json::to_value(trace).unwrap(), expected); +/// ``` +#[serde_with::skip_serializing_none] +#[derive(Serialize, Clone, Debug, FieldSetter)] +#[field_setter(box_self, kind = "trace")] +pub struct Pie

+where + P: Serialize + Clone, +{ + #[field_setter(default = "PlotType::Pie")] + r#type: PlotType, + domain: Option, + /// Determines whether outside text labels can push the margins. + automargin: Option, + /// Assigns extra data each datum. This may be useful when listening to + /// hover, click and selection events. Note that, “scatter” traces also + /// appends customdata items in the markers DOM elements + #[serde(rename = "customdata")] + custom_data: Option, + /// Specifies the direction at which succeeding sectors follow one another. + /// The 'direction' property is an enumeration that may be specified as + /// One of the following enumeration values: ['clockwise', + /// 'counterclockwise'] + direction: Option, + /// Sets the label step. See label0 for more info. + dlabel: Option, + /// Sets the fraction of the radius to cut out of the pie. Use this to make + /// a donut chart. The 'hole' property is a number and may be specified + /// as a value in the interval [0, 1] + hole: Option, + /// Determines which trace information appear on hover. If none or skip are + /// set, no information is displayed upon hovering. But, if none is set, + /// click and hover events are still fired. + #[serde(rename = "hoverinfo")] + hover_info: Option, + #[serde(rename = "hoverlabel")] + hover_label: Option

Pie

+where + P: Serialize + Clone + 'static, +{ + pub fn new(x: Vec

) -> Box { + Box::new(Self { + values: Some(x), + ..Default::default() + }) + } +} + +impl

Trace for Pie

+where + P: Serialize + Clone, +{ + fn to_json(&self) -> String { + serde_json::to_string(self).unwrap() + } +} + +#[cfg(test)] +mod tests { + use serde_json::{json, to_value}; + + use super::*; + + #[test] + fn test_serialize_scatter_mapbox() { + let scatter_mapbox = Pie::new(vec![45, 55]) + .name("pie") + .automargin(true) + .direction(PieDirection::Clockwise) + .hole(0.2) + .inside_text_font(Font::new().color("#ff7f0e")) + .inside_text_orientation(Orientation::Tangential) + .labels(vec!["a", "b"]) + .sort(true) + .visible(Visible::True) + .show_legend(true) + .legend_rank(1000) + .legend_group("legend group") + .legend_group_title("Legend Group Title") + .opacity(0.5) + .ids(vec!["one"]) + .text("text") + .text_info("label+percent") + .text_array(vec!["text"]) + .text_template("text_template") + .text_template_array(vec!["text_template"]) + .text_font(Font::new()) + .text_position(Position::TopCenter) + .text_position_array(vec![Position::MiddleLeft]) + .hover_text("hover_text") + .hover_text_array(vec!["hover_text"]) + .hover_info(HoverInfo::XAndYAndZ) + .hover_template("hover_template") + .hover_template_array(vec!["hover_template"]) + .meta("meta") + .custom_data(vec!["custom_data"]) + .marker(Marker::new()) + .hover_label(Label::new()) + .ui_revision(6); + let expected = json!({ + "values": [45, 55], + "type": "pie", + "name": "pie", + "automargin": true, + "direction" : "Clockwise", + "hole": 0.2, + "insidetextfont": {"color": "#ff7f0e"}, + "insidetextorientation": "t", + "labels": ["a", "b"], + "sort": true, + "visible": true, + "showlegend": true, + "legendrank": 1000, + "legendgroup": "legend group", + "legendgrouptitle": {"text": "Legend Group Title"}, + "opacity": 0.5, + "ids": ["one"], + "text": ["text"], + "textinfo": "label+percent", + "textfont": {}, + "texttemplate": ["text_template"], + "textposition": ["middle left"], + "hovertext": ["hover_text"], + "hoverinfo": "x+y+z", + "hovertemplate": ["hover_template"], + "meta": "meta", + "customdata": ["custom_data"], + "marker": {}, + "hoverlabel": {}, + "uirevision": 6, + }); + + assert_eq!(to_value(scatter_mapbox).unwrap(), expected); + } +} From 006dfcf6242b0dc4523bad6259199b68966aaef2 Mon Sep 17 00:00:00 2001 From: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> Date: Tue, 31 Dec 2024 11:17:53 +0100 Subject: [PATCH 2/3] fix cargo doc warnings Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --- plotly/src/common/color.rs | 41 ++++++++++++++--------------- plotly/src/configuration.rs | 2 +- plotly/src/layout/mod.rs | 8 +++--- plotly/src/traces/image.rs | 8 +++--- plotly/src/traces/mesh3d.rs | 18 ++++++------- plotly/src/traces/pie.rs | 23 ++++++++-------- plotly/src/traces/sankey.rs | 2 +- plotly/src/traces/scatter.rs | 8 +++--- plotly/src/traces/scatter3d.rs | 19 +++++++------ plotly/src/traces/scatter_mapbox.rs | 6 ++--- plotly/src/traces/scatter_polar.rs | 6 ++--- 11 files changed, 70 insertions(+), 71 deletions(-) diff --git a/plotly/src/common/color.rs b/plotly/src/common/color.rs index 1d7a03af..e03e0ee2 100644 --- a/plotly/src/common/color.rs +++ b/plotly/src/common/color.rs @@ -1,23 +1,22 @@ -//! This module provides several user interfaces for describing a color to be -//! used throughout the rest of the library. The easiest way of describing a -//! colour is to use a `&str` or `String`, which is simply serialized as-is and -//! passed on to the underlying `plotly.js` library. `plotly.js` supports [`CSS -//! color formats`], and will fallback to some default color if the color string -//! is malformed. -//! -//! For a more type-safe approach, the `RGB` or `RGBA` structs can be used to -//! construct a valid color, which will then get serialized to an appropriate -//! string representation. Cross-browser compatible [`predefined colors`] are -//! supported via the `NamedColor` enum. -//! -//! The `Color` trait is public, and so can be implemented for custom colour -//! types. The user can then implement a valid serialization function according -//! to their own requirements. On the whole, that should be largely unnecessary -//! given the functionality already provided within this module. -//! -//! [`CSS color formats`]: https://www.w3schools.com/cssref/css_colors_legal.asp -//! [`predefined colors`]: https://www.w3schools.com/cssref/css_colors.asp - +/// This module provides several user interfaces for describing a color to be +/// used throughout the rest of the library. The easiest way of describing a +/// colour is to use a `&str` or `String`, which is simply serialized as-is and +/// passed on to the underlying `plotly.js` library. `plotly.js` supports [`CSS +/// color formats`], and will fallback to some default color if the color string +/// is malformed. +/// +/// For a more type-safe approach, the `RGB` or `RGBA` structs can be used to +/// construct a valid color, which will then get serialized to an appropriate +/// string representation. Cross-browser compatible [`predefined colors`] are +/// supported via the `NamedColor` enum. +/// +/// The `Color` trait is public, and so can be implemented for custom colour +/// types. The user can then implement a valid serialization function according +/// to their own requirements. On the whole, that should be largely unnecessary +/// given the functionality already provided within this module. +/// +/// [`CSS color formats`]: +/// [`predefined colors`]: use dyn_clone::DynClone; use erased_serde::Serialize as ErasedSerialize; use serde::Serialize; @@ -116,7 +115,7 @@ impl Serialize for Rgba { /// Cross-browser compatible [`predefined colors`]. /// -/// [`predefined colors`]: https://www.w3schools.com/cssref/css_colors.asp +/// [`predefined colors`]: #[derive(Debug, Clone, Copy, Serialize)] #[serde(rename_all = "lowercase")] pub enum NamedColor { diff --git a/plotly/src/configuration.rs b/plotly/src/configuration.rs index 95043caf..36c9c8ce 100644 --- a/plotly/src/configuration.rs +++ b/plotly/src/configuration.rs @@ -212,7 +212,7 @@ impl Configuration { /// When set it determines base URL for the "Edit in Chart Studio" /// `show_edit_in_chart_studio`/`show_send_to_cloud` mode bar button and /// the show_link/send_data on-graph link. To enable sending your data to - /// Chart Studio Cloud, you need to set both `plotly_server_url` to "https://chart-studio.plotly.com" and + /// Chart Studio Cloud, you need to set both `plotly_server_url` to and /// also set `showSendToCloud` to `true`. pub fn plotly_server_url(mut self, plotly_server_url: &str) -> Self { self.plotly_server_url = Some(plotly_server_url.to_string()); diff --git a/plotly/src/layout/mod.rs b/plotly/src/layout/mod.rs index c4c3c87a..e0025b6f 100644 --- a/plotly/src/layout/mod.rs +++ b/plotly/src/layout/mod.rs @@ -929,7 +929,7 @@ pub struct Shape { #[serde(rename = "fillcolor")] fill_color: Option>, /// Determines which regions of complex paths constitute the interior. For - /// more info please visit https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule + /// more info please visit #[serde(rename = "fillrule")] fill_rule: Option, /// Determines whether the shape could be activated for edit or not. Has no @@ -994,7 +994,7 @@ pub struct NewShape { #[serde(rename = "fillcolor")] fill_color: Option>, /// Determines the path's interior. For more info please - /// visit https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule + /// visit #[serde(rename = "fillrule")] fill_rule: Option, /// Sets the opacity of new shapes. Number between or equal to 0 and 1. @@ -1071,8 +1071,8 @@ pub struct Annotation { visible: Option, /// Sets the text associated with this annotation. Plotly uses a subset of /// HTML tags to do things like newline (
), bold (), italics - /// (), hyperlinks (). Tags , , - /// are also supported. + /// (), hyperlinks (). Tags , , + /// are also supported. text: Option, /// Sets the angle at which the `text` is drawn with respect to the /// horizontal. diff --git a/plotly/src/traces/image.rs b/plotly/src/traces/image.rs index d081f9b4..6c056f19 100644 --- a/plotly/src/traces/image.rs +++ b/plotly/src/traces/image.rs @@ -217,7 +217,7 @@ pub struct Image { dy: Option, /// Specifies the data URI of the image to be visualized. The URI consists - /// of "data:image/[][;base64],". + /// of "data:image/[\]\[;base64\],\". source: Option, /// Sets text elements associated with each (x,y) pair. If a single string, @@ -245,12 +245,12 @@ pub struct Image { /// inserted using %{variable}, for example "y: %{y}". Numbers are /// formatted using d3-format's syntax %{variable:d3-format}, for example /// "Price: %{y:$.2f}". - /// https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md#d3_format for details + /// for details /// on the formatting syntax. Dates are formatted using d3-time-format's /// syntax %{variable|d3-time-format}, for example "Day: - /// %{2019-01-01|%A}". https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md#format for details + /// %{2019-01-01|%A}". for details /// on the date formatting syntax. The variables available in - /// `hovertemplate` are the ones emitted as event data described at this link https://plotly.com/javascript/plotlyjs-events/#event-data. + /// `hovertemplate` are the ones emitted as event data described at this link . /// Additionally, every attributes that can be specified per-point (the ones /// that are `arrayOk: true`) are available. Anything contained in tag /// `` is displayed in the secondary box, for example diff --git a/plotly/src/traces/mesh3d.rs b/plotly/src/traces/mesh3d.rs index 223c4a45..c289e58f 100644 --- a/plotly/src/traces/mesh3d.rs +++ b/plotly/src/traces/mesh3d.rs @@ -190,12 +190,12 @@ where /// inserted using %{variable}, for example "y: %{y}". Numbers are /// formatted using d3-format's syntax %{variable:d3-format}, for example /// "Price: %{y:$.2f}". - /// https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md#d3_format for details + /// for details /// on the formatting syntax. Dates are formatted using d3-time-format's /// syntax %{variable|d3-time-format}, for example "Day: - /// %{2019-01-01|%A}". https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md#format for details + /// %{2019-01-01|%A}". for details /// on the date formatting syntax. The variables available in - /// `hovertemplate` are the ones emitted as event data described at this link https://plotly.com/javascript/plotlyjs-events/#event-data. + /// `hovertemplate` are the ones emitted as event data described at this link . /// Additionally, every attributes that can be specified per-point (the ones /// that are `arrayOk: true`) are available. Anything contained in tag /// `` is displayed in the secondary box, for example @@ -204,8 +204,8 @@ where #[serde(rename = "hovertemplate")] hover_template: Option>, /// Sets the hover text formatting rulefor `x` using d3 formatting - /// mini-languages which are very similar to those in Python. For numbers, see: https://github.com/d3/d3-format/tree/v1.4.5#d3-format. And for dates - /// see: https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format. We add two items to d3's date + /// mini-languages which are very similar to those in Python. For numbers, see: . And for dates + /// see: . We add two items to d3's date /// formatter: "%h" for half of the year as a decimal number as well as /// "%{n}f" for fractional seconds with n digits. For example, /// "2016-10-13 09:15:23.456" with tickformat "%H~%M~%S.%2f" would display @@ -214,8 +214,8 @@ where #[serde(rename = "xhoverformat")] x_hover_format: Option, /// Sets the hover text formatting rulefor `y` using d3 formatting - /// mini-languages which are very similar to those in Python. For numbers, see: https://github.com/d3/d3-format/tree/v1.4.5#d3-format. And for dates - /// see: https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format. We add two items to d3's date + /// mini-languages which are very similar to those in Python. For numbers, see: . And for dates + /// see: . We add two items to d3's date /// formatter: "%h" for half of the year as a decimal number as well as /// "%{n}f" for fractional seconds with n digits. For example, /// "2016-10-13 09:15:23.456" with tickformat "%H~%M~%S.%2f" would display @@ -286,8 +286,8 @@ where reverse_scale: Option, /// Sets the hover text formatting rulefor `z` using d3 formatting - /// mini-languages which are very similar to those in Python. For numbers, see: https://github.com/d3/d3-format/tree/v1.4.5#d3-format. And for dates - /// see: https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format. We add two items to d3's date + /// mini-languages which are very similar to those in Python. For numbers, see: . And for dates + /// see: . We add two items to d3's date /// formatter: "%h" for half of the year as a decimal number as well as /// "%{n}f" for fractional seconds with n digits. For example, /// "2016-10-13 09:15:23.456" with tickformat "%H~%M~%S.%2f" would display diff --git a/plotly/src/traces/pie.rs b/plotly/src/traces/pie.rs index d387c8a1..96b4c0c7 100644 --- a/plotly/src/traces/pie.rs +++ b/plotly/src/traces/pie.rs @@ -78,15 +78,15 @@ where /// the first point. An underscore before or after “(x|y)other” will add /// a space on that side, only when this field is shown. Numbers are /// formatted using d3-format’s syntax %{variable:d3-format}, for example - /// “Price: %{y:$.2f}”. https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. - /// Dates are formatted using d3-time-format’s syntax - /// %{variable|d3-time-format}, for example “Day: %{2019-01-01|%A}”. https://github.com/d3/d3-time- format/tree/v2.2.3#locale_format for + /// “Price: %{y:$.2f}”. for details on the formatting syntax. + /// Dates are formatted using d3-time-format’s syntax + /// %{variable|d3-time-format}, for example “Day: %{2019-01-01|%A}”. for /// details on the date formatting syntax. The variables available in - /// hovertemplate are the ones emitted as event data described at this link https://plotly.com/javascript/plotlyjs-events/#event- data. - /// Additionally, every attributes that can be specified per-point (the + /// hovertemplate are the ones emitted as event data described at this link . + /// Additionally, every attributes that can be specified per-point (the /// ones that are arrayOk: true) are available. Finally, the template /// string has access to variables label, color, value, percent and text. - /// Anything contained in tag is displayed in the secondary box, for + /// Anything contained in tag \ is displayed in the secondary box, for /// example “{fullData.name}”. To hide the secondary box /// completely, use an empty tag . #[serde(rename = "hovertemplate")] @@ -144,8 +144,8 @@ where /// axis and colorbar title.text, annotation text rangeselector, /// updatemenues and sliders label text all support meta. To access the /// trace meta values in an attribute in the same trace, simply use - /// %{meta[i]} where i is the index or key of the meta item in question. To - /// access trace meta in layout attributes, use %{data[n[.meta[i]} where i + /// %{meta\[i\]} where i is the index or key of the meta item in question. To + /// access trace meta in layout attributes, use %{data[n[.meta\[i\]} where i /// is the index or key of the meta and n is the trace index. meta: Option, /// Sets the trace name. The trace name appears as the legend item and on @@ -186,9 +186,10 @@ where /// Template string used for rendering the information text that appear on /// points. Note that this will override textinfo. Variables are /// inserted using %{variable}, for example “y: %{y}”. Numbers are formatted - /// using d3-format’s syntax %{variable:d3-format}, for example “Price: %{y:$.2f}”. https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. - /// Dates are formatted using d3-time-format’s syntax - /// %{variable|d3-time-format}, for example “Day: %{2019-01-01|%A}”. https://github.com/d3/d3-time- format/tree/v2.2.3#locale_format for details on the date formatting syntax. + /// using d3-format’s syntax %{variable:d3-format}, for example “Price: %{y:$.2f}”. + /// for details on the formatting syntax. + /// Dates are formatted using d3-time-format’s syntax %{variable|d3-time-format}, for example + /// “Day: %{2019-01-01|%A}”. for details on the date formatting syntax. /// Every attributes that can be specified per-point (the ones that are /// arrayOk: true) are available. Finally, the template string has /// access to variables label, color, value, percent and text. diff --git a/plotly/src/traces/sankey.rs b/plotly/src/traces/sankey.rs index be96afa2..1809b10e 100644 --- a/plotly/src/traces/sankey.rs +++ b/plotly/src/traces/sankey.rs @@ -332,7 +332,7 @@ where #[serde(rename = "textfont")] text_font: Option, /// Sets the value formatting rule using d3 formatting mini-languages which - /// are very similar to those in Python. For numbers, see: https://github.com/d3/d3-format/tree/v1.4.5#d3-format. + /// are very similar to those in Python. For numbers, see: . #[serde(rename = "valueformat")] value_format: Option, /// Adds a unit to follow the value in the hover tooltip. Add a space if a diff --git a/plotly/src/traces/scatter.rs b/plotly/src/traces/scatter.rs index 57d5e8a5..da81526e 100644 --- a/plotly/src/traces/scatter.rs +++ b/plotly/src/traces/scatter.rs @@ -146,12 +146,12 @@ where /// inserted using %{variable}, for example "y: %{y}". Numbers are /// formatted using d3-format's syntax %{variable:d3-format}, for example /// "Price: %{y:$.2f}". - /// https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md#d3_format for details + /// for details /// on the formatting syntax. Dates are formatted using d3-time-format's - /// syntax %{variable|d3-time-format}, for example "Day: - /// %{2019-01-01|%A}". https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md#format for details + /// syntax %{variable|d3-time-format}, for example "Day: %{2019-01-01|%A}". + /// for details /// on the date formatting syntax. The variables available in - /// `hovertemplate` are the ones emitted as event data described at this link https://plotly.com/javascript/plotlyjs-events/#event-data. + /// `hovertemplate` are the ones emitted as event data described at this link . /// Additionally, every attributes that can be specified per-point (the ones /// that are `arrayOk: true`) are available. Anything contained in tag /// `` is displayed in the secondary box, for example diff --git a/plotly/src/traces/scatter3d.rs b/plotly/src/traces/scatter3d.rs index 412ba1f8..e8a0e3b9 100644 --- a/plotly/src/traces/scatter3d.rs +++ b/plotly/src/traces/scatter3d.rs @@ -169,13 +169,12 @@ where /// box. Note that this will override `HoverInfo`. Variables are /// inserted using %{variable}, for example "y: %{y}". Numbers are /// formatted using d3-format's syntax %{variable:d3-format}, for example - /// "Price: %{y:$.2f}". - /// https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md#d3_format for details + /// "Price: %{y:$.2f}". for details /// on the formatting syntax. Dates are formatted using d3-time-format's /// syntax %{variable|d3-time-format}, for example "Day: - /// %{2019-01-01|%A}". https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md#format for details + /// %{2019-01-01|%A}". for details /// on the date formatting syntax. The variables available in - /// `hovertemplate` are the ones emitted as event data described at this link https://plotly.com/javascript/plotlyjs-events/#event-data. + /// `hovertemplate` are the ones emitted as event data described at this link . /// Additionally, every attributes that can be specified per-point (the ones /// that are `arrayOk: true`) are available. Anything contained in tag /// `` is displayed in the secondary box, for example @@ -184,8 +183,8 @@ where #[serde(rename = "hovertemplate")] hover_template: Option>, /// Sets the hover text formatting rulefor `x` using d3 formatting - /// mini-languages which are very similar to those in Python. For numbers, see: https://github.com/d3/d3-format/tree/v1.4.5#d3-format. And for - /// dates see: https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format. We add two items to d3's + /// mini-languages which are very similar to those in Python. For numbers, see: . And for + /// dates see: . We add two items to d3's /// date formatter: "%h" for half of the year as a decimal number as well as /// "%{n}f" for fractional seconds with n digits. For example, /// "2016-10-13 09:15:23.456" with tickformat "%H~%M~%S.%2f" would display @@ -194,8 +193,8 @@ where #[serde(rename = "xhoverformat")] x_hover_format: Option, /// Sets the hover text formatting rulefor `y` using d3 formatting - /// mini-languages which are very similar to those in Python. For numbers, see: https://github.com/d3/d3-format/tree/v1.4.5#d3-format. And for - /// dates see: https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format. We add two items to d3's + /// mini-languages which are very similar to those in Python. For numbers, see: . And for + /// dates see: . We add two items to d3's /// date formatter: "%h" for half of the year as a decimal number as well as /// "%{n}f" for fractional seconds with n digits. For example, /// "2016-10-13 09:15:23.456" with tickformat "%H~%M~%S.%2f" would display @@ -204,8 +203,8 @@ where #[serde(rename = "yhoverformat")] y_hover_format: Option, /// Sets the hover text formatting rulefor `z` using d3 formatting - /// mini-languages which are very similar to those in Python. For numbers, see: https://github.com/d3/d3-format/tree/v1.4.5#d3-format. And for - /// dates see: https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format. We add two items to d3's + /// mini-languages which are very similar to those in Python. For numbers, see: . And for + /// dates see: . We add two items to d3's /// date formatter: "%h" for half of the year as a decimal number as well as /// "%{n}f" for fractional seconds with n digits. For example, /// "2016-10-13 09:15:23.456" with tickformat "%H~%M~%S.%2f" would display diff --git a/plotly/src/traces/scatter_mapbox.rs b/plotly/src/traces/scatter_mapbox.rs index 76348e6a..035632b4 100644 --- a/plotly/src/traces/scatter_mapbox.rs +++ b/plotly/src/traces/scatter_mapbox.rs @@ -148,12 +148,12 @@ where /// inserted using %{variable}, for example "y: %{y}". Numbers are /// formatted using d3-format's syntax %{variable:d3-format}, for example /// "Price: %{y:$.2f}". - /// https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md#d3_format for details + /// for details /// on the formatting syntax. Dates are formatted using d3-time-format's /// syntax %{variable|d3-time-format}, for example "Day: - /// %{2019-01-01|%A}". https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md#format for details + /// %{2019-01-01|%A}". for details /// on the date formatting syntax. The variables available in - /// `hovertemplate` are the ones emitted as event data described at this link https://plotly.com/javascript/plotlyjs-events/#event-data. + /// `hovertemplate` are the ones emitted as event data described at this link . /// Additionally, every attributes that can be specified per-point (the ones /// that are `arrayOk: true`) are available. Anything contained in tag /// `` is displayed in the secondary box, for example diff --git a/plotly/src/traces/scatter_polar.rs b/plotly/src/traces/scatter_polar.rs index 7bb51073..4f9b4f1f 100644 --- a/plotly/src/traces/scatter_polar.rs +++ b/plotly/src/traces/scatter_polar.rs @@ -133,12 +133,12 @@ where /// inserted using %{variable}, for example "y: %{y}". Numbers are /// formatted using d3-format's syntax %{variable:d3-format}, for example /// "Price: %{y:$.2f}". - /// https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md#d3_format for details + /// for details /// on the formatting syntax. Dates are formatted using d3-time-format's /// syntax %{variable|d3-time-format}, for example "Day: - /// %{2019-01-01|%A}". https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md#format for details + /// %{2019-01-01|%A}". for details /// on the date formatting syntax. The variables available in - /// `hovertemplate` are the ones emitted as event data described at this link https://plotly.com/javascript/plotlyjs-events/#event-data. + /// `hovertemplate` are the ones emitted as event data described at this link . /// Additionally, every attributes that can be specified per-point (the ones /// that are `arrayOk: true`) are available. Anything contained in tag /// `` is displayed in the secondary box, for example From 5acb23fd3d5629bcaa8a1a287ac886778c1cd440 Mon Sep 17 00:00:00 2001 From: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> Date: Tue, 31 Dec 2024 12:31:30 +0100 Subject: [PATCH 3/3] add pie unittests and fix docs Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --- .../src/recipes/basic_charts/pie_charts.md | 6 + examples/basic_charts/src/main.rs | 17 ++- plotly/src/layout/mod.rs | 4 +- plotly/src/traces/pie.rs | 107 +++++++++++++++--- 4 files changed, 116 insertions(+), 18 deletions(-) diff --git a/docs/book/src/recipes/basic_charts/pie_charts.md b/docs/book/src/recipes/basic_charts/pie_charts.md index 941770f1..bb17de49 100644 --- a/docs/book/src/recipes/basic_charts/pie_charts.md +++ b/docs/book/src/recipes/basic_charts/pie_charts.md @@ -20,6 +20,12 @@ The `to_inline_html` method is used to produce the html plot displayed in this p {{#include ../../../../../examples/basic_charts/out/basic_pie_chart.html}} +```rust,no_run +{{#include ../../../../../examples/basic_charts/src/main.rs:basic_pie_chart_labels}} +``` + +{{#include ../../../../../examples/basic_charts/out/basic_pie_chart_labels.html}} + ## Grouped Pie Chart ```rust,no_run {{#include ../../../../../examples/basic_charts/src/main.rs:grouped_donout_pie_charts}} diff --git a/examples/basic_charts/src/main.rs b/examples/basic_charts/src/main.rs index 667fa048..0de8a8ca 100644 --- a/examples/basic_charts/src/main.rs +++ b/examples/basic_charts/src/main.rs @@ -825,7 +825,7 @@ fn table_chart(show: bool) -> Plot { // Pie Charts // ANCHOR: basic_pie_chart fn basic_pie_chart(show: bool) -> Plot { - let values = vec![2, 3, 5]; + let values = vec![2, 3, 4]; let labels = vec!["giraffes", "orangutans", "monkeys"]; let t = Pie::new(values).labels(labels); let mut plot = Plot::new(); @@ -838,6 +838,20 @@ fn basic_pie_chart(show: bool) -> Plot { } // ANCHOR_END: basic_pie_chart +// ANCHOR: basic_pie_chart_labels +fn basic_pie_chart_labels(show: bool) -> Plot { + let labels = ["giraffes", "giraffes", "orangutans", "monkeys"]; + let t = Pie::::from_labels(&labels); + let mut plot = Plot::new(); + plot.add_trace(t); + + if show { + plot.show(); + } + plot +} +// ANCHOR_END: basic_pie_chart_labels + // ANCHOR: pie_chart_text_control fn pie_chart_text_control(show: bool) -> Plot { let values = vec![2, 3, 4, 4]; @@ -993,6 +1007,7 @@ fn main() { // Pie Charts write_example_to_html(basic_pie_chart(false), "basic_pie_chart"); + write_example_to_html(basic_pie_chart_labels(false), "basic_pie_chart_labels"); write_example_to_html(pie_chart_text_control(false), "pie_chart_text_control"); write_example_to_html( grouped_donout_pie_charts(false), diff --git a/plotly/src/layout/mod.rs b/plotly/src/layout/mod.rs index e0025b6f..af3eabf6 100644 --- a/plotly/src/layout/mod.rs +++ b/plotly/src/layout/mod.rs @@ -1071,8 +1071,8 @@ pub struct Annotation { visible: Option, /// Sets the text associated with this annotation. Plotly uses a subset of /// HTML tags to do things like newline (
), bold (), italics - /// (), hyperlinks (). Tags , , - /// are also supported. + /// (), hyperlinks (). Tags , , + /// are also supported. text: Option, /// Sets the angle at which the `text` is drawn with respect to the /// horizontal. diff --git a/plotly/src/traces/pie.rs b/plotly/src/traces/pie.rs index 96b4c0c7..d3e951f4 100644 --- a/plotly/src/traces/pie.rs +++ b/plotly/src/traces/pie.rs @@ -35,6 +35,28 @@ pub enum PieDirection { /// /// assert_eq!(serde_json::to_value(trace).unwrap(), expected); /// ``` +/// # Using only labels +/// +/// Build a new Pie Chart by only assigning the labels field. The Pie chart +/// will be generated by counting the number of unique labels, see [Pie::labels] +/// field description. Note that to create a Pie chart by using this +/// function, the type parameter `P` needs to be specialized, this can be +/// done by doing +/// +/// ``` +/// use plotly::Pie; +/// +/// let labels = ["giraffes", "giraffes", "orangutans", "monkeys"]; +/// +/// let trace = Pie::::from_labels(&labels); +/// +/// let expected = serde_json::json!({ +/// "type": "pie", +/// "labels": ["giraffes", "giraffes", "orangutans", "monkeys"], +/// }); +/// +/// assert_eq!(serde_json::to_value(trace).unwrap(), expected); +/// ``` #[serde_with::skip_serializing_none] #[derive(Serialize, Clone, Debug, FieldSetter)] #[field_setter(box_self, kind = "trace")] @@ -86,9 +108,9 @@ where /// Additionally, every attributes that can be specified per-point (the /// ones that are arrayOk: true) are available. Finally, the template /// string has access to variables label, color, value, percent and text. - /// Anything contained in tag \ is displayed in the secondary box, for - /// example “{fullData.name}”. To hide the secondary box - /// completely, use an empty tag . + /// Anything contained in tag \ is displayed in the secondary box, + /// for example “{fullData.name}”. To hide the secondary + /// box completely, use an empty tag . #[serde(rename = "hovertemplate")] hover_template: Option>, /// Sets hover text elements associated with each sector. If a single @@ -144,9 +166,9 @@ where /// axis and colorbar title.text, annotation text rangeselector, /// updatemenues and sliders label text all support meta. To access the /// trace meta values in an attribute in the same trace, simply use - /// %{meta\[i\]} where i is the index or key of the meta item in question. To - /// access trace meta in layout attributes, use %{data[n[.meta\[i\]} where i - /// is the index or key of the meta and n is the trace index. + /// %{meta\[i\]} where i is the index or key of the meta item in question. + /// To access trace meta in layout attributes, use %{data[n[.meta\[i\]} + /// where i is the index or key of the meta and n is the trace index. meta: Option, /// Sets the trace name. The trace name appears as the legend item and on /// hover. @@ -186,10 +208,10 @@ where /// Template string used for rendering the information text that appear on /// points. Note that this will override textinfo. Variables are /// inserted using %{variable}, for example “y: %{y}”. Numbers are formatted - /// using d3-format’s syntax %{variable:d3-format}, for example “Price: %{y:$.2f}”. - /// for details on the formatting syntax. - /// Dates are formatted using d3-time-format’s syntax %{variable|d3-time-format}, for example - /// “Day: %{2019-01-01|%A}”. for details on the date formatting syntax. + /// using d3-format’s syntax %{variable:d3-format}, for example “Price: + /// %{y:$.2f}”. for details on the formatting syntax. + /// Dates are formatted using d3-time-format’s syntax + /// %{variable|d3-time-format}, for example “Day: %{2019-01-01|%A}”. for details on the date formatting syntax. /// Every attributes that can be specified per-point (the ones that are /// arrayOk: true) are available. Finally, the template string has /// access to variables label, color, value, percent and text. @@ -282,9 +304,37 @@ impl

Pie

where P: Serialize + Clone + 'static, { - pub fn new(x: Vec

) -> Box { + /// Build a new Pie Chart by only assigning the values field + pub fn new(values: Vec

) -> Box { + Box::new(Self { + values: Some(values), + ..Default::default() + }) + } + + /// Same as [Pie::new()] + pub fn from_values(values: Vec

) -> Box { Box::new(Self { - values: Some(x), + values: Some(values), + ..Default::default() + }) + } + + /// Build a new Pie Chart by only assigning the labels field. The Pie chart + /// will be generated by counting the number of unique labels, see + /// [Pie::labels] field description. Note that to create a Pie chart by + /// using this function, the type parameter `P` needs to be specialized, + /// this can be done by doing + /// ``` + /// use plotly::Pie; + /// + /// let labels = ["giraffes", "giraffes", "orangutans", "monkeys"]; + /// let trace = Pie::::from_labels(&labels); + /// ``` + pub fn from_labels + ToString>(labels: &[T]) -> Box { + let l = labels.iter().map(|s| s.to_string()).collect(); + Box::new(Self { + labels: Some(l), ..Default::default() }) } @@ -306,8 +356,8 @@ mod tests { use super::*; #[test] - fn test_serialize_scatter_mapbox() { - let scatter_mapbox = Pie::new(vec![45, 55]) + fn serialize_pie() { + let pie_trace = Pie::new(vec![45, 55]) .name("pie") .automargin(true) .direction(PieDirection::Clockwise) @@ -374,6 +424,33 @@ mod tests { "uirevision": 6, }); - assert_eq!(to_value(scatter_mapbox).unwrap(), expected); + assert_eq!(to_value(pie_trace).unwrap(), expected); + } + + #[test] + fn new_from_values() { + let values = vec![2.2, 3.3, 4.4]; + let trace = Pie::from_values(values); + + let expected = serde_json::json!({ + "type": "pie", + "values": [2.2, 3.3, 4.4], + }); + + assert_eq!(to_value(trace).unwrap(), expected); + } + + #[test] + fn new_from_labels() { + let labels = ["giraffes", "giraffes", "orangutans", "monkeys"]; + + let trace = Pie::::from_labels(&labels); + + let expected = serde_json::json!({ + "type": "pie", + "labels": ["giraffes", "giraffes", "orangutans", "monkeys"], + }); + + assert_eq!(to_value(trace).unwrap(), expected); } }