diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 931d256a..a9431a21 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,6 +44,8 @@ jobs: components: clippy targets: wasm32-unknown-unknown # lint plotly_static for all features + - run: cargo check -p plotly_static + - run: cargo test -p plotly_static build_without_driver_feature_returns_error - run: cargo clippy -p plotly_static --features geckodriver,webdriver_download -- -D warnings -A deprecated - run: cargo clippy -p plotly_static --features chromedriver,webdriver_download -- -D warnings -A deprecated # lint the main library workspace for non-wasm target diff --git a/CHANGELOG.md b/CHANGELOG.md index f8f775e0..d1996a8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ### Added +- [[#412](https://github.com/plotly/plotly.rs/pull/412)] Add `Violin` trace type with box, mean line, KDE span, and split/grouped support +- [[#413](https://github.com/plotly/plotly.rs/issues/413)] Add hover (`hovertext`/`hoverinfo`/`hovertemplate`/`customdata`/`hoverlabel`/`text`) and color-scale (`colorscale`/`colorbar`/`showscale`/`reversescale`/`autocolorscale`/`coloraxis`) support, plus `ids`/`meta`/`uirevision`/`below`, to `DensityMapbox` - [[#406](https://github.com/plotly/plotly.rs/issues/406)] Expose `plotly.js` 3.1–3.6 attributes - [[#410](https://github.com/plotly/plotly.rs/pull/410)] Add `Choropleth` (geo subplot) and `ChoroplethMap` (MapLibre `map` subplot) trace types, with a `LocationMode` enum and a dedicated `choropleth::Marker`; add the MapLibre `map` subplot via `LayoutMap`/`MapStyle`/`MapBounds` - [[#410](https://github.com/plotly/plotly.rs/pull/410)] Add `LayoutGeo` options: `fitbounds` (`GeoFitBounds`), `resolution` (`GeoResolution`, 1:110M/1:50M base-layer detail), and `bgcolor` +- [[#414](https://github.com/plotly/plotly.rs/issues/414)] Add `DensityMap` (MapLibre `map` subplot) trace type — density heatmaps with full color-scale and hover support +- Add `ScatterMap` (MapLibre `map` subplot) trace type — the modern counterpart to `ScatterMapbox`, with markers/lines, `Fill`/`Selection`, and full hover support +- Add native point clustering to `ScatterMap` via a `Cluster` option (`enabled`/`color`/`maxzoom`/`opacity`/`size`/`step`) — a `scattermap`-only feature with no `ScatterMapbox` equivalent ### Changed diff --git a/docs/book/src/SUMMARY.md b/docs/book/src/SUMMARY.md index 866124fd..02e3609f 100644 --- a/docs/book/src/SUMMARY.md +++ b/docs/book/src/SUMMARY.md @@ -21,6 +21,7 @@ - [Statistical Charts](./recipes/statistical_charts.md) - [Error Bars](./recipes/statistical_charts/error_bars.md) - [Box Plots](./recipes/statistical_charts/box_plots.md) + - [Violin Plots](./recipes/statistical_charts/violin_plots.md) - [Histograms](./recipes/statistical_charts/histograms.md) - [Scientific Charts](./recipes/scientific_charts.md) - [Contour Plots](./recipes/scientific_charts/contour_plots.md) diff --git a/docs/book/src/recipes/img/violin_plot.png b/docs/book/src/recipes/img/violin_plot.png new file mode 100644 index 00000000..d6c679ab Binary files /dev/null and b/docs/book/src/recipes/img/violin_plot.png differ diff --git a/docs/book/src/recipes/statistical_charts.md b/docs/book/src/recipes/statistical_charts.md index 4f446d1d..136aa7bc 100644 --- a/docs/book/src/recipes/statistical_charts.md +++ b/docs/book/src/recipes/statistical_charts.md @@ -6,4 +6,5 @@ Kind | Link :---|:----: Error Bars |[![Scatter Plots](./img/error_bars.png)](./statistical_charts/error_bars.md) Box Plots | [![Line Charts](./img/box_plot.png)](./statistical_charts/box_plots.md) +Violin Plots | [![Violin Plots](./img/violin_plot.png)](./statistical_charts/violin_plots.md) Histograms | [![Scatter Plots](./img/overlaid_histogram.png)](./statistical_charts/histograms.md) diff --git a/docs/book/src/recipes/statistical_charts/violin_plots.md b/docs/book/src/recipes/statistical_charts/violin_plots.md new file mode 100644 index 00000000..739886ad --- /dev/null +++ b/docs/book/src/recipes/statistical_charts/violin_plots.md @@ -0,0 +1,36 @@ +# Violin Plots + +The following imports have been used to produce the plots below: + +```rust,no_run +use plotly::common::{Line, Orientation}; +use plotly::layout::{Layout, ViolinMode}; +use plotly::violin::{MeanLine, ViolinBox, ViolinPoints, ViolinSide}; +use plotly::{color::NamedColor, Plot, Violin}; +``` + +The `to_inline_html` method is used to produce the html plot displayed in this page. + + +## Basic Violin Plot +```rust,no_run +{{#include ../../../../../examples/statistical_charts/src/main.rs:basic_violin_plot}} +``` + +{{#include ../../../../../examples/statistical_charts/output/inline_basic_violin_plot.html}} + + +## Horizontal Violin Plot +```rust,no_run +{{#include ../../../../../examples/statistical_charts/src/main.rs:horizontal_violin_plot}} +``` + +{{#include ../../../../../examples/statistical_charts/output/inline_horizontal_violin_plot.html}} + + +## Split Violin Plot +```rust,no_run +{{#include ../../../../../examples/statistical_charts/src/main.rs:split_violin_plot}} +``` + +{{#include ../../../../../examples/statistical_charts/output/inline_split_violin_plot.html}} diff --git a/examples/statistical_charts/src/main.rs b/examples/statistical_charts/src/main.rs index f4947cc7..2b4d2da9 100644 --- a/examples/statistical_charts/src/main.rs +++ b/examples/statistical_charts/src/main.rs @@ -6,8 +6,9 @@ use plotly::{ color::{NamedColor, Rgb, Rgba}, common::{ErrorData, ErrorType, Line, Marker, Mode, Orientation}, histogram::{Bins, Cumulative, HistFunc, HistNorm}, - layout::{Axis, BarMode, BoxMode, Layout, Margin}, - Bar, BoxPlot, Histogram, Plot, Scatter, + layout::{Axis, BarMode, BoxMode, Layout, Margin, ViolinMode}, + violin::{MeanLine, ViolinBox, ViolinPoints, ViolinSide}, + Bar, BoxPlot, Histogram, Plot, Scatter, Violin, }; use plotly_utils::write_example_to_html; use rand_distr::{Distribution, Normal, Uniform}; @@ -477,6 +478,98 @@ fn fully_styled_box_plot(show: bool, file_name: &str) { } // ANCHOR_END: fully_styled_box_plot +// Violin Plots +// ANCHOR: basic_violin_plot +fn basic_violin_plot(show: bool, file_name: &str) { + let y = vec![ + 0.2, 0.2, 0.6, 1.0, 0.5, 0.4, 0.2, 0.7, 0.9, 0.1, 0.5, 0.3, 0.8, 0.4, 0.6, + ]; + + let trace = Violin::new(y) + .box_plot(ViolinBox::new().visible(true)) + .mean_line(MeanLine::new().visible(true)) + .name("Total"); + + let layout = Layout::new().title("Basic Violin Plot"); + + let mut plot = Plot::new(); + plot.set_layout(layout); + plot.add_trace(trace); + + let path = write_example_to_html(&plot, file_name); + if show { + plot.show_html(path); + } +} +// ANCHOR_END: basic_violin_plot + +// ANCHOR: horizontal_violin_plot +fn horizontal_violin_plot(show: bool, file_name: &str) { + let x = vec![1.4, 2.1, 1.9, 3.2, 2.7, 2.2, 1.8, 2.5, 3.1, 2.0, 2.6, 1.7]; + + let trace = Violin::::default() + .x(x) + .points(ViolinPoints::All) + .box_plot(ViolinBox::new().visible(true)) + .mean_line(MeanLine::new().visible(true)) + .orientation(Orientation::Horizontal) + .name("Score"); + + let layout = Layout::new().title("Horizontal Violin Plot"); + + let mut plot = Plot::new(); + plot.set_layout(layout); + plot.add_trace(trace); + + let path = write_example_to_html(&plot, file_name); + if show { + plot.show_html(path); + } +} +// ANCHOR_END: horizontal_violin_plot + +// ANCHOR: split_violin_plot +fn split_violin_plot(show: bool, file_name: &str) { + let x = vec![ + "Mon", "Mon", "Mon", "Mon", "Tue", "Tue", "Tue", "Tue", "Wed", "Wed", "Wed", "Wed", + ]; + + let trace1 = Violin::new_xy( + x.clone(), + vec![0.6, 0.9, 0.4, 0.7, 0.8, 1.1, 0.6, 0.9, 1.0, 1.3, 0.8, 1.1], + ) + .legend_group("Yes") + .scale_group("Yes") + .name("Yes") + .side(ViolinSide::Negative) + .line(Line::new().color(NamedColor::Blue)); + + let trace2 = Violin::new_xy( + x, + vec![0.4, 0.7, 0.3, 0.5, 0.6, 0.9, 0.4, 0.7, 0.8, 1.1, 0.6, 0.9], + ) + .legend_group("No") + .scale_group("No") + .name("No") + .side(ViolinSide::Positive) + .line(Line::new().color(NamedColor::Green)); + + let layout = Layout::new() + .title("Split Violin Plot") + .violin_mode(ViolinMode::Overlay); + + let mut plot = Plot::new(); + plot.set_layout(layout); + plot.add_trace(trace1); + plot.add_trace(trace2); + + let path = write_example_to_html(&plot, file_name); + if show { + plot.show_html(path); + } +} +// ANCHOR_END: split_violin_plot + // Histograms fn sample_normal_distribution(n: usize, mean: f64, std_dev: f64) -> Vec { let mut rng = rand::rng(); @@ -729,6 +822,11 @@ fn main() { grouped_horizontal_box_plot(false, "grouped_horizontal_box_plot"); fully_styled_box_plot(false, "fully_styled_box_plot"); + // Violin Plots + basic_violin_plot(false, "basic_violin_plot"); + horizontal_violin_plot(false, "horizontal_violin_plot"); + split_violin_plot(false, "split_violin_plot"); + // Histograms basic_histogram(false, "basic_histogram"); horizontal_histogram(false, "horizontal_histogram"); diff --git a/plotly/Cargo.toml b/plotly/Cargo.toml index a0840115..8d43567c 100644 --- a/plotly/Cargo.toml +++ b/plotly/Cargo.toml @@ -88,6 +88,5 @@ image = "0.25" itertools = ">=0.10, <0.16" itertools-num = "0.1" ndarray = "0.17" -plotly_static = { path = "../plotly_static" } rand_distr = "0.6" base64 = "0.22" diff --git a/plotly/src/common/mod.rs b/plotly/src/common/mod.rs index 513440e5..2f60ecd8 100644 --- a/plotly/src/common/mod.rs +++ b/plotly/src/common/mod.rs @@ -212,6 +212,7 @@ pub enum PlotType { ScatterGL, Scatter3D, ScatterMapbox, + ScatterMap, ScatterGeo, ScatterPolar, ScatterPolarGL, @@ -228,12 +229,14 @@ pub enum PlotType { Sankey, Surface, DensityMapbox, + DensityMap, Table, Pie, Treemap, Sunburst, Choropleth, ChoroplethMap, + Violin, } #[derive(Serialize, Clone, Debug)] diff --git a/plotly/src/lib.rs b/plotly/src/lib.rs index 01c83074..7086da40 100644 --- a/plotly/src/lib.rs +++ b/plotly/src/lib.rs @@ -60,14 +60,14 @@ pub use layout::Layout; pub use plot::{Plot, Trace, Traces}; // Also provide easy access to modules which contain additional trace-specific types pub use traces::{ - box_plot, choropleth, choropleth_map, contour, heat_map, histogram, image, mesh3d, sankey, - scatter, scatter3d, scatter_mapbox, sunburst, surface, treemap, + box_plot, choropleth, choropleth_map, contour, density_map, heat_map, histogram, image, mesh3d, + sankey, scatter, scatter3d, scatter_map, scatter_mapbox, sunburst, surface, treemap, violin, }; // Bring the different trace types into the top-level scope pub use traces::{ - Bar, BoxPlot, Candlestick, Choropleth, ChoroplethMap, Contour, DensityMapbox, HeatMap, - Histogram, Image, Mesh3D, Ohlc, Pie, Sankey, Scatter, Scatter3D, ScatterGeo, ScatterMapbox, - ScatterPolar, Sunburst, Surface, Table, Treemap, + Bar, BoxPlot, Candlestick, Choropleth, ChoroplethMap, Contour, DensityMap, DensityMapbox, + HeatMap, Histogram, Image, Mesh3D, Ohlc, Pie, Sankey, Scatter, Scatter3D, ScatterGeo, + ScatterMap, ScatterMapbox, ScatterPolar, Sunburst, Surface, Table, Treemap, Violin, }; pub trait Restyle: serde::Serialize {} diff --git a/plotly/src/traces/density_map.rs b/plotly/src/traces/density_map.rs new file mode 100644 index 00000000..5cc32ae9 --- /dev/null +++ b/plotly/src/traces/density_map.rs @@ -0,0 +1,207 @@ +//! Density heatmap trace for the MapLibre `map` subplot. + +use plotly_derive::FieldSetter; +use serde::Serialize; + +use crate::common::{ + ColorBar, ColorScale, Dim, HoverInfo, Label, LegendGroupTitle, PlotType, Visible, +}; +use crate::private::{NumOrString, NumOrStringCollection}; +use crate::Trace; + +/// Construct a density heatmap trace drawn on the MapLibre `map` subplot +/// (configured via [`LayoutMap`](crate::layout::LayoutMap)). +/// +/// `DensityMap` is the modern MapLibre-based counterpart to +/// [`DensityMapbox`](crate::DensityMapbox): it draws a weighted kernel-density +/// heatmap from `lat`/`lon` points (with an optional per-point weight `z`) onto +/// the `layout.map` subplot. +/// +/// # Examples +/// +/// ``` +/// use plotly::DensityMap; +/// +/// let trace = DensityMap::new(vec![45.5017], vec![-73.5673], vec![1.0]) +/// .radius(20) +/// .name("montreal"); +/// ``` +#[serde_with::skip_serializing_none] +#[derive(Serialize, Clone, Debug, FieldSetter)] +#[field_setter(box_self, kind = "trace")] +pub struct DensityMap +where + Lat: Serialize + Clone, + Lon: Serialize + Clone, + Z: Serialize + Clone, +{ + #[field_setter(default = "PlotType::DensityMap")] + 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, + /// Determines whether or not an item corresponding to this trace is shown + /// in the legend. + #[serde(rename = "showlegend")] + show_legend: Option, + /// Sets the legend rank for this trace. + #[serde(rename = "legendrank")] + legend_rank: Option, + /// Sets the legend group for this trace. + #[serde(rename = "legendgroup")] + legend_group: Option, + /// Set and style the title to appear for the legend group. + #[serde(rename = "legendgrouptitle")] + legend_group_title: Option, + /// Assigns id labels to each datum. + ids: Option>, + + /// Sets the latitude coordinates (in degrees North). + lat: Option>, + /// Sets the longitude coordinates (in degrees East). + lon: Option>, + /// Sets the points' weight. For example, a value of 10 would be equivalent + /// to having 10 points of weight 1 in the same spot. + z: Option>, + /// Sets the radius of influence of one `lat`/`lon` point in pixels. + /// Increasing the value makes the density map smoother, but less detailed. + radius: Option, + /// Sets the opacity of the trace. + opacity: Option, + + /// Sets the text elements associated with each (lat,lon,z) triplet. + text: Option>, + /// Sets the hover text elements associated with each (lat,lon,z) triplet. + #[serde(rename = "hovertext")] + hover_text: Option>, + /// Determines which trace information appears on hover. + #[serde(rename = "hoverinfo")] + hover_info: Option, + /// Template string used for rendering the information that appears on the + /// hover box. + #[serde(rename = "hovertemplate")] + hover_template: Option>, + #[serde(rename = "hovertemplatefallback")] + hover_template_fallback: Option>, + /// Properties of the hover label. + #[serde(rename = "hoverlabel")] + hover_label: Option