diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ec42fd0..30903a11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a https://github.com/plotly/plotly.rs/pull/350 +## [Unreleased] + +### Added + +- [[#NNN](https://github.com/plotly/plotly.rs/pull/NNN)] Add `Treemap` trace type, with `Tiling`/`PathBar` helpers, a dedicated `treemap::Marker` (`pad`/`corner_radius`/`depth_fade`), and `treemapcolorway`/`extendtreemapcolors` layout options +- [[#NNN](https://github.com/plotly/plotly.rs/pull/NNN)] Add `Sunburst` trace type + ## [0.14.1] - 2026-02-15 ### Fixed diff --git a/docs/book/src/SUMMARY.md b/docs/book/src/SUMMARY.md index 56495bf9..8a9b7e7c 100644 --- a/docs/book/src/SUMMARY.md +++ b/docs/book/src/SUMMARY.md @@ -16,6 +16,8 @@ - [Bar Charts](./recipes/basic_charts/bar_charts.md) - [Pie Charts](./recipes/basic_charts/pie_charts.md) - [Sankey Diagrams](./recipes/basic_charts/sankey_diagrams.md) + - [Treemap Charts](./recipes/basic_charts/treemap_charts.md) + - [Sunburst Charts](./recipes/basic_charts/sunburst_charts.md) - [Statistical Charts](./recipes/statistical_charts.md) - [Error Bars](./recipes/statistical_charts/error_bars.md) - [Box Plots](./recipes/statistical_charts/box_plots.md) diff --git a/docs/book/src/recipes/basic_charts.md b/docs/book/src/recipes/basic_charts.md index aaf9f5a5..ca8204c9 100644 --- a/docs/book/src/recipes/basic_charts.md +++ b/docs/book/src/recipes/basic_charts.md @@ -9,3 +9,5 @@ Line Charts | [![Line Charts](./img/line_shape_options_for_interpolation.png)](. 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) +Treemap Charts | [Treemap Charts](./basic_charts/treemap_charts.md) +Sunburst Charts | [Sunburst Charts](./basic_charts/sunburst_charts.md) diff --git a/docs/book/src/recipes/basic_charts/sunburst_charts.md b/docs/book/src/recipes/basic_charts/sunburst_charts.md new file mode 100644 index 00000000..568d95e2 --- /dev/null +++ b/docs/book/src/recipes/basic_charts/sunburst_charts.md @@ -0,0 +1,27 @@ +# Sunburst Charts + +The following imports have been used to produce the plots below: + +```rust,no_run +use plotly::common::Orientation; +use plotly::sunburst::Leaf; +use plotly::treemap::BranchValues; +use plotly::{Plot, Sunburst}; +``` + +The `to_inline_html` method is used to produce the html plot displayed in this page. + +## Basic Sunburst +```rust,no_run +{{#include ../../../../../examples/basic_charts/src/main.rs:basic_sunburst}} +``` + +{{#include ../../../../../examples/basic_charts/output/inline_basic_sunburst.html}} + + +## Styled Sunburst with Branch Values and Leaf Opacity +```rust,no_run +{{#include ../../../../../examples/basic_charts/src/main.rs:styled_sunburst}} +``` + +{{#include ../../../../../examples/basic_charts/output/inline_styled_sunburst.html}} diff --git a/docs/book/src/recipes/basic_charts/treemap_charts.md b/docs/book/src/recipes/basic_charts/treemap_charts.md new file mode 100644 index 00000000..7a6c9f5d --- /dev/null +++ b/docs/book/src/recipes/basic_charts/treemap_charts.md @@ -0,0 +1,25 @@ +# Treemap Charts + +The following imports have been used to produce the plots below: + +```rust,no_run +use plotly::treemap::{BranchValues, Packing, PathBar, Side, Tiling}; +use plotly::{Plot, Treemap}; +``` + +The `to_inline_html` method is used to produce the html plot displayed in this page. + +## Basic Treemap +```rust,no_run +{{#include ../../../../../examples/basic_charts/src/main.rs:basic_treemap}} +``` + +{{#include ../../../../../examples/basic_charts/output/inline_basic_treemap.html}} + + +## Styled Treemap with Tiling and Path Bar +```rust,no_run +{{#include ../../../../../examples/basic_charts/src/main.rs:styled_treemap}} +``` + +{{#include ../../../../../examples/basic_charts/output/inline_styled_treemap.html}} diff --git a/examples/basic_charts/src/main.rs b/examples/basic_charts/src/main.rs index cbda8db4..2b6b4258 100644 --- a/examples/basic_charts/src/main.rs +++ b/examples/basic_charts/src/main.rs @@ -13,10 +13,12 @@ use plotly::{ TicksDirection, TraceOrder, }, sankey::{Line as SankeyLine, Link, Node}, + sunburst::Leaf, traces::table::{ Align as TableAlign, Cells, Fill as TableFill, Font as TableFont, Header, Line as TableLine, }, - Bar, Pie, Plot, Sankey, Scatter, ScatterPolar, Table, + treemap::{BranchValues, Marker as TreemapMarker, Packing, PathBar, Side, Tiling}, + Bar, Pie, Plot, Sankey, Scatter, ScatterPolar, Sunburst, Table, Treemap, }; use plotly_utils::write_example_to_html; use rand_distr::{Distribution, Normal, Uniform}; @@ -1043,6 +1045,108 @@ fn grouped_donout_pie_charts(show: bool, file_name: &str) { } // ANCHOR_END: grouped_donout_pie_charts +// ANCHOR: basic_treemap +fn basic_treemap(show: bool, file_name: &str) { + let labels = vec![ + "Eve", "Cain", "Seth", "Enos", "Noam", "Abel", "Awan", "Enoch", "Azura", + ]; + let parents = vec![ + "", "Eve", "Eve", "Seth", "Seth", "Eve", "Eve", "Awan", "Eve", + ]; + let trace = Treemap::new(labels, parents) + .values(vec![10.0, 14.0, 12.0, 10.0, 2.0, 6.0, 6.0, 4.0, 4.0]) + .text_info("label+value"); + + let mut plot = Plot::new(); + plot.add_trace(trace); + + let path = write_example_to_html(&plot, file_name); + if show { + plot.show_html(path); + } +} +// ANCHOR_END: basic_treemap + +// ANCHOR: styled_treemap +fn styled_treemap(show: bool, file_name: &str) { + let labels = vec![ + "Total", + "Tech", + "Health", + "Finance", + "Software", + "Hardware", + "Pharma", + "Devices", + "Banking", + "Insurance", + ]; + let parents = vec![ + "", "Total", "Total", "Total", "Tech", "Tech", "Health", "Health", "Finance", "Finance", + ]; + let trace = Treemap::new(labels, parents) + .values(vec![0.0, 0.0, 0.0, 0.0, 40.0, 30.0, 25.0, 15.0, 20.0, 18.0]) + .branch_values(BranchValues::Remainder) + .marker( + TreemapMarker::new() + .corner_radius(5.0) + .line(Line::new().width(1.0).color(NamedColor::White)), + ) + .tiling(Tiling::new().packing(Packing::Binary).pad(2.0)) + .path_bar(PathBar::new().visible(true).side(Side::Top)) + .text_info("label+value+percent parent"); + + let mut plot = Plot::new(); + plot.add_trace(trace); + + let path = write_example_to_html(&plot, file_name); + if show { + plot.show_html(path); + } +} +// ANCHOR_END: styled_treemap + +// ANCHOR: basic_sunburst +fn basic_sunburst(show: bool, file_name: &str) { + let labels = vec![ + "Eve", "Cain", "Seth", "Enos", "Noam", "Abel", "Awan", "Enoch", "Azura", + ]; + let parents = vec![ + "", "Eve", "Eve", "Seth", "Seth", "Eve", "Eve", "Awan", "Eve", + ]; + let trace = Sunburst::new(labels, parents) + .values(vec![10.0, 14.0, 12.0, 10.0, 2.0, 6.0, 6.0, 4.0, 4.0]); + + let mut plot = Plot::new(); + plot.add_trace(trace); + + let path = write_example_to_html(&plot, file_name); + if show { + plot.show_html(path); + } +} +// ANCHOR_END: basic_sunburst + +// ANCHOR: styled_sunburst +fn styled_sunburst(show: bool, file_name: &str) { + let labels = vec!["Root", "Branch A", "Branch B", "Leaf A1", "Leaf B1"]; + let parents = vec!["", "Root", "Root", "Branch A", "Branch B"]; + let trace = Sunburst::new(labels, parents) + .values(vec![8.0, 3.0, 5.0, 3.0, 5.0]) + .branch_values(BranchValues::Total) + .leaf(Leaf::new().opacity(0.7)) + .inside_text_orientation(Orientation::Radial); + + let mut plot = Plot::new(); + plot.add_trace(trace); + + let path = write_example_to_html(&plot, file_name); + if show { + plot.show_html(path); + } +} +// ANCHOR_END: styled_sunburst + // ANCHOR: set_lower_or_upper_bound_on_axis fn set_lower_or_upper_bound_on_axis(show: bool, file_name: &str) { use std::fs::File; @@ -1200,6 +1304,14 @@ fn main() { grouped_donout_pie_charts(false, "grouped_donout_pie_charts"); + // Treemap Charts + basic_treemap(false, "basic_treemap"); + styled_treemap(false, "styled_treemap"); + + // Sunburst Charts + basic_sunburst(false, "basic_sunburst"); + styled_sunburst(false, "styled_sunburst"); + // Set Lower or Upper Bound on Axis set_lower_or_upper_bound_on_axis(false, "set_lower_or_upper_bound_on_axis"); } diff --git a/plotly/src/common/mod.rs b/plotly/src/common/mod.rs index c5a4d98e..8bb61d03 100644 --- a/plotly/src/common/mod.rs +++ b/plotly/src/common/mod.rs @@ -230,6 +230,8 @@ pub enum PlotType { DensityMapbox, Table, Pie, + Treemap, + Sunburst, } #[derive(Serialize, Clone, Debug)] diff --git a/plotly/src/layout/mod.rs b/plotly/src/layout/mod.rs index 69116156..e2a4f590 100644 --- a/plotly/src/layout/mod.rs +++ b/plotly/src/layout/mod.rs @@ -377,6 +377,10 @@ pub struct LayoutFields { sunburst_colorway: Option>>, #[serde(rename = "extendsunburstcolors")] extend_sunburst_colors: Option, + #[serde(rename = "treemapcolorway")] + treemap_colorway: Option>>, + #[serde(rename = "extendtreemapcolors")] + extend_treemap_colors: Option, mapbox: Option, #[serde(rename = "updatemenus")] update_menus: Option>, @@ -545,6 +549,8 @@ mod tests { .extend_pie_colors(true) .sunburst_colorway(vec!["#654654"]) .extend_sunburst_colors(false) + .treemap_colorway(vec!["#321321"]) + .extend_treemap_colors(true) .mapbox(Mapbox::new()) .update_menus(vec![UpdateMenu::new()]) .sliders(vec![Slider::new()]); @@ -620,6 +626,8 @@ mod tests { "extendpiecolors": true, "sunburstcolorway": ["#654654"], "extendsunburstcolors": false, + "treemapcolorway": ["#321321"], + "extendtreemapcolors": true, "mapbox": {}, "updatemenus": [{}], "sliders": [{}], @@ -704,6 +712,8 @@ mod tests { .extend_pie_colors(true) .sunburst_colorway(vec!["#654654"]) .extend_sunburst_colors(false) + .treemap_colorway(vec!["#321321"]) + .extend_treemap_colors(true) .z_axis(Axis::new()) .scene(LayoutScene::new()); @@ -771,6 +781,8 @@ mod tests { "extendpiecolors": true, "sunburstcolorway": ["#654654"], "extendsunburstcolors": false, + "treemapcolorway": ["#321321"], + "extendtreemapcolors": true, "zaxis": {}, "scene": {} }); diff --git a/plotly/src/lib.rs b/plotly/src/lib.rs index 6d436e71..1340271b 100644 --- a/plotly/src/lib.rs +++ b/plotly/src/lib.rs @@ -61,12 +61,13 @@ pub use plot::{Plot, Trace, Traces}; // Also provide easy access to modules which contain additional trace-specific types pub use traces::{ box_plot, contour, heat_map, histogram, image, mesh3d, sankey, scatter, scatter3d, - scatter_mapbox, surface, + scatter_mapbox, sunburst, surface, treemap, }; // Bring the different trace types into the top-level scope pub use traces::{ Bar, BoxPlot, Candlestick, Contour, DensityMapbox, HeatMap, Histogram, Image, Mesh3D, Ohlc, - Pie, Sankey, Scatter, Scatter3D, ScatterGeo, ScatterMapbox, ScatterPolar, Surface, Table, + Pie, Sankey, Scatter, Scatter3D, ScatterGeo, ScatterMapbox, ScatterPolar, Sunburst, Surface, + Table, Treemap, }; pub trait Restyle: serde::Serialize {} diff --git a/plotly/src/traces/mod.rs b/plotly/src/traces/mod.rs index 85b0a294..adc2efb4 100644 --- a/plotly/src/traces/mod.rs +++ b/plotly/src/traces/mod.rs @@ -17,8 +17,10 @@ pub mod scatter3d; pub mod scatter_geo; pub mod scatter_mapbox; mod scatter_polar; +pub mod sunburst; pub mod surface; pub mod table; +pub mod treemap; pub use bar::Bar; pub use box_plot::BoxPlot; @@ -36,7 +38,9 @@ pub use scatter3d::Scatter3D; pub use scatter_geo::ScatterGeo; pub use scatter_mapbox::ScatterMapbox; pub use scatter_polar::ScatterPolar; +pub use sunburst::Sunburst; pub use surface::Surface; pub use table::Table; +pub use treemap::Treemap; pub use self::image::Image; diff --git a/plotly/src/traces/sunburst.rs b/plotly/src/traces/sunburst.rs new file mode 100644 index 00000000..20ea281a --- /dev/null +++ b/plotly/src/traces/sunburst.rs @@ -0,0 +1,257 @@ +//! Sunburst trace + +use plotly_derive::FieldSetter; +use serde::Serialize; + +use crate::private::{NumOrString, NumOrStringCollection}; +use crate::traces::treemap::BranchValues; +use crate::{ + common::{Dim, Domain, Font, HoverInfo, Label, Marker, Orientation, PlotType}, + Trace, +}; + +/// Configures the appearance of the leaf nodes of a [`Sunburst`]. +#[serde_with::skip_serializing_none] +#[derive(Serialize, Clone, Debug, FieldSetter)] +pub struct Leaf { + /// Sets the opacity of the leaves. With colorscale it is defaulted to `1`; + /// otherwise it is defaulted to `0.7`. + opacity: Option, +} + +impl Leaf { + pub fn new() -> Self { + Default::default() + } +} + +/// Construct a Sunburst trace. +/// +/// Sunburst charts visualize hierarchical data spanning outwards radially from +/// the root to the leaves. The hierarchy is defined via the `labels` and +/// `parents` fields (the root is the item whose parent is an empty string). +/// +/// # Examples +/// +/// ``` +/// use plotly::Sunburst; +/// +/// let trace = Sunburst::new( +/// vec!["Eve", "Cain", "Seth"], +/// vec!["", "Eve", "Eve"], +/// ) +/// .values(vec![10, 14, 12]); +/// +/// let expected = serde_json::json!({ +/// "type": "sunburst", +/// "labels": ["Eve", "Cain", "Seth"], +/// "parents": ["", "Eve", "Eve"], +/// "values": [10, 14, 12], +/// }); +/// +/// 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 Sunburst +where + V: Serialize + Clone, +{ + #[field_setter(default = "PlotType::Sunburst")] + r#type: PlotType, + /// Sets the trace name. The trace name appears as the legend item and on + /// hover. + name: Option, + /// Determines whether or not this trace is visible. + visible: Option, + /// Sets the opacity of the trace. + opacity: Option, + /// Assigns id labels to each datum. These ids are for object constancy of + /// data points during animation. + ids: Option>, + /// Sets the labels of each of the sectors. + labels: Option>, + /// Sets the parent sectors for each of the sectors. Empty string items + /// `""` are understood to reference the root node in the hierarchy. + parents: Option>, + /// Sets the values associated with each of the sectors. Use with + /// `branch_values` to determine how the values are summed. + values: Option>, + /// Determines how the items in `values` are summed. When set to + /// `Remainder`, the value of a parent is the sum of its `values` plus those + /// of its children. When set to `Total`, the value of a parent is the total + /// of its children. + #[serde(rename = "branchvalues")] + branch_values: Option, + /// Determines default for `values` when it is not provided, by inferring a + /// `count`, i.e. the number of `"branches"` or `"leaves"`. + count: Option, + /// Sets the level from which this trace hierarchy is rendered. Set `level` + /// to `""` to start from the root node in the hierarchy. + level: Option, + /// Sets the number of rendered sectors from any given `level`. Set + /// `max_depth` to `-1` to render all the levels in the hierarchy. + #[serde(rename = "maxdepth")] + max_depth: Option, + /// Sets the domain within which this trace is drawn. + domain: Option, + marker: Option, + /// Sets the styling of the leaves of the sunburst. + leaf: Option, + /// Rotates the whole diagram counterclockwise by some angle. By default the + /// first slice starts at 3 o'clock. The 'rotation' property is a number + /// between -360 and 360. + rotation: Option, + /// Determines whether or not the sectors are reordered from largest to + /// smallest. + sort: Option, + /// Sets text elements associated with each sector. If trace `text_info` + /// contains a `"text"` flag, these elements will be seen on the chart. + text: Option>, + /// Determines which trace information appears on the graph. + #[serde(rename = "textinfo")] + text_info: Option, + /// Template string used for rendering the information text that appears on + /// points. + #[serde(rename = "texttemplate")] + text_template: Option>, + /// Sets the font used for `text_info`. + #[serde(rename = "textfont")] + text_font: Option, + /// Sets the font used for `text_info` lying inside the sector. + #[serde(rename = "insidetextfont")] + inside_text_font: Option, + /// Sets the font used for `text_info` lying outside the sector. + #[serde(rename = "outsidetextfont")] + outside_text_font: Option, + /// Controls the orientation of the text inside chart sectors. + #[serde(rename = "insidetextorientation")] + inside_text_orientation: Option, + /// Determines which trace information appears on hover. + #[serde(rename = "hoverinfo")] + hover_info: Option, + #[serde(rename = "hoverlabel")] + hover_label: Option