diff --git a/CHANGELOG.md b/CHANGELOG.md index 01acd648..a47e4247 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - date-axis period positioning (`xperiod`/`xperiod0`/`xperiodalignment` and the `y` variants) on `Scatter`, `Bar`, `BoxPlot`, `HeatMap`, `Contour` (x-only on `Candlestick`, `Ohlc`), via a new `PeriodAlignment` enum - `fillpattern` (reusing `Pattern`) and `fillgradient` (new `FillGradient` struct) on `Scatter` - Consolidate the duplicated `Selection`/`SelectionMarker` structs into `common` (re-exported from their original modules for backward compatibility) +- [[#422](https://github.com/plotly/plotly.rs/issues/422)] Add `Indicator` trace type (number / delta / gauge KPI displays) with `Gauge`, `Delta`, `Number` config +- [[#422](https://github.com/plotly/plotly.rs/issues/422)] Add `Histogram2d` trace type (2D histogram heatmap) with `histfunc`/`histnorm`/`nbinsx`/`nbinsy`/`xbins`/`ybins` +- [[#422](https://github.com/plotly/plotly.rs/issues/422)] Add `Icicle` trace type (hierarchical icicle chart; sibling of `Treemap`/`Sunburst`) +- [[#422](https://github.com/plotly/plotly.rs/issues/422)] Add layout-level `uirevision` to preserve UI state (zoom/pan/selection) across re-renders (complements the trace-level `uirevision` added in #421) ### Changed diff --git a/plotly/src/common/mod.rs b/plotly/src/common/mod.rs index 05345710..b02792fc 100644 --- a/plotly/src/common/mod.rs +++ b/plotly/src/common/mod.rs @@ -224,8 +224,11 @@ pub enum PlotType { Contour, HeatMap, Histogram, + Histogram2d, Histogram2dContour, + Icicle, Image, + Indicator, Mesh3D, Ohlc, Sankey, diff --git a/plotly/src/layout/mod.rs b/plotly/src/layout/mod.rs index 1aef0bb0..027e3075 100644 --- a/plotly/src/layout/mod.rs +++ b/plotly/src/layout/mod.rs @@ -7,6 +7,7 @@ use update_menu::UpdateMenu; use crate::color::Color; use crate::common::{Calendar, ColorScale, Font, Label, Orientation, Title}; +use crate::private::NumOrString; pub mod themes; pub mod update_menu; @@ -257,6 +258,12 @@ pub struct LayoutFields { color_axis: Option, #[serde(rename = "modebar")] mode_bar: Option, + /// Controls persistence of user-driven UI changes (zoom, pan, selection, + /// legend visibility) across `Plotly.react`/`newPlot` calls: state is kept + /// while `uirevision` is unchanged. Trace-level `uirevision` overrides per + /// trace. + #[serde(rename = "uirevision")] + ui_revision: Option, /// Determines the mode of hover interactions. If "closest", a single /// hoverlabel will appear for the "closest" point within the /// `hoverdistance`. If "x" (or "y"), multiple hoverlabels will appear for @@ -654,6 +661,12 @@ mod tests { assert_eq!(to_value(template).unwrap(), expected); } + #[test] + fn serialize_layout_ui_revision() { + let layout = Layout::new().ui_revision("forever"); + assert_eq!(to_value(layout).unwrap()["uirevision"], json!("forever")); + } + #[test] fn serialize_layout() { let layout = Layout::new() diff --git a/plotly/src/lib.rs b/plotly/src/lib.rs index 7086da40..abe8e813 100644 --- a/plotly/src/lib.rs +++ b/plotly/src/lib.rs @@ -60,14 +60,16 @@ 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, density_map, heat_map, histogram, image, mesh3d, - sankey, scatter, scatter3d, scatter_map, scatter_mapbox, sunburst, surface, treemap, violin, + box_plot, choropleth, choropleth_map, contour, density_map, heat_map, histogram, histogram2d, + icicle, image, indicator, 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, DensityMap, DensityMapbox, - HeatMap, Histogram, Image, Mesh3D, Ohlc, Pie, Sankey, Scatter, Scatter3D, ScatterGeo, - ScatterMap, ScatterMapbox, ScatterPolar, Sunburst, Surface, Table, Treemap, Violin, + HeatMap, Histogram, Histogram2d, Icicle, Image, Indicator, 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/histogram2d.rs b/plotly/src/traces/histogram2d.rs new file mode 100644 index 00000000..167f3747 --- /dev/null +++ b/plotly/src/traces/histogram2d.rs @@ -0,0 +1,296 @@ +//! 2D histogram trace + +use plotly_derive::FieldSetter; +use serde::Serialize; + +use crate::{ + common::{ + Calendar, ColorBar, ColorScale, Dim, Font, HoverInfo, Label, LegendGroupTitle, PlotType, + Visible, XAxisId, YAxisId, + }, + private::{NumOrString, NumOrStringCollection}, + traces::heat_map::Smoothing, + traces::histogram::{Bins, HistFunc, HistNorm}, + Trace, +}; + +/// Construct a 2D histogram trace. +/// +/// A `Histogram2d` bins raw `x`/`y` samples into a 2D grid and renders the +/// counts as a heatmap. Provide raw `x`/`y` data (and let Plotly.js bin it via +/// `hist_func`/`n_bins_x`/`n_bins_y`/`x_bins`/`y_bins`), or supply a +/// pre-computed `z` matrix. +/// +/// # Examples +/// +/// ``` +/// use plotly::Histogram2d; +/// +/// let trace = Histogram2d::new(vec![1.0, 2.0, 2.0], vec![1.0, 1.0, 3.0]); +/// +/// let expected = serde_json::json!({ +/// "type": "histogram2d", +/// "x": [1.0, 2.0, 2.0], +/// "y": [1.0, 1.0, 3.0], +/// }); +/// +/// assert_eq!(serde_json::to_value(trace).unwrap(), expected); +/// ``` +#[serde_with::skip_serializing_none] +#[derive(Serialize, Debug, Clone, FieldSetter)] +#[field_setter(box_self, kind = "trace")] +pub struct Histogram2d +where + X: Serialize + Clone, + Y: Serialize + Clone, + Z: Serialize + Clone, +{ + #[field_setter(default = "PlotType::Histogram2d")] + r#type: PlotType, + name: Option, + visible: Option, + #[serde(rename = "showlegend")] + show_legend: Option, + #[serde(rename = "legendgroup")] + legend_group: Option, + #[serde(rename = "legendgrouptitle")] + legend_group_title: Option, + opacity: Option, + x: Option>, + y: Option>, + z: Option>, + #[serde(rename = "xaxis")] + x_axis: Option, + #[serde(rename = "yaxis")] + y_axis: Option, + /// Specifies the binning function used for this histogram trace. + #[serde(rename = "histfunc")] + hist_func: Option, + /// Specifies the type of normalization used for this histogram trace. + #[serde(rename = "histnorm")] + hist_norm: Option, + /// Determines whether or not the x-axis bin attributes are picked by an + /// algorithm. + #[serde(rename = "autobinx")] + auto_bin_x: Option, + /// Determines whether or not the y-axis bin attributes are picked by an + /// algorithm. + #[serde(rename = "autobiny")] + auto_bin_y: Option, + /// Specifies the maximum number of desired bins along the x axis. + #[serde(rename = "nbinsx")] + n_bins_x: Option, + /// Specifies the maximum number of desired bins along the y axis. + #[serde(rename = "nbinsy")] + n_bins_y: Option, + /// Sets the binning across the x axis. + #[serde(rename = "xbins")] + x_bins: Option, + /// Sets the binning across the y axis. + #[serde(rename = "ybins")] + y_bins: Option, + #[serde(rename = "autocolorscale")] + auto_color_scale: Option, + #[serde(rename = "colorbar")] + color_bar: Option, + #[serde(rename = "colorscale")] + color_scale: Option, + #[serde(rename = "reversescale")] + reverse_scale: Option, + #[serde(rename = "showscale")] + show_scale: Option, + zauto: Option, + zmin: Option, + zmax: Option, + zmid: Option, + #[serde(rename = "zhoverformat")] + zhover_format: Option, + zsmooth: Option, + #[serde(rename = "xgap")] + x_gap: Option, + #[serde(rename = "ygap")] + y_gap: Option, + #[serde(rename = "xcalendar")] + x_calendar: Option, + #[serde(rename = "ycalendar")] + y_calendar: Option, + #[serde(rename = "xhoverformat")] + x_hover_format: Option, + #[serde(rename = "yhoverformat")] + y_hover_format: Option, + #[serde(rename = "textfont")] + text_font: Option, + #[serde(rename = "texttemplate")] + text_template: Option>, + #[serde(rename = "texttemplatefallback")] + text_template_fallback: Option>, + #[serde(rename = "hoverinfo")] + hover_info: Option, + #[serde(rename = "hoverlabel")] + hover_label: Option