From 2bd98cfb339ea865ee9372302b1f66b8ab77a20f Mon Sep 17 00:00:00 2001 From: Joel Natividad <1980690+jqnatividad@users.noreply.github.com> Date: Sat, 11 Jul 2026 09:06:39 -0400 Subject: [PATCH 1/8] refactor(traces): consolidate Selection/SelectionMarker into common MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The scatter_geo, scatter_map, and scatter_mapbox modules each defined a byte-identical Selection/SelectionMarker pair. Move the single definition to crate::common and re-export it from each module (pub use) so the public paths plotly::traces::scatter_geo::Selection etc. keep resolving — no serialized-output change, all existing trace tests pass unchanged. This is the shared building block for backfilling selected/unselected styling onto the older cartesian traces. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --- plotly/src/common/mod.rs | 40 +++++++++++++++++++++++++++++ plotly/src/traces/scatter_geo.rs | 40 +++-------------------------- plotly/src/traces/scatter_map.rs | 40 +++-------------------------- plotly/src/traces/scatter_mapbox.rs | 40 +++-------------------------- 4 files changed, 52 insertions(+), 108 deletions(-) diff --git a/plotly/src/common/mod.rs b/plotly/src/common/mod.rs index db2fa08e..d7cae3bd 100644 --- a/plotly/src/common/mod.rs +++ b/plotly/src/common/mod.rs @@ -645,6 +645,46 @@ impl Gradient { } } +/// Styles the marker of `selected`/`unselected` points, used by the +/// `selected` and `unselected` attributes of point-marker traces. +#[serde_with::skip_serializing_none] +#[derive(Serialize, Clone, Debug, Default)] +pub struct SelectionMarker { + color: Option>, + opacity: Option, + size: Option>, +} + +/// Sets the style of `selected`/`unselected` points. +#[derive(Serialize, Clone, Debug, Default)] +pub struct Selection { + marker: SelectionMarker, +} + +impl Selection { + pub fn new() -> Self { + Default::default() + } + + /// Sets the marker color of un/selected points. + pub fn color(mut self, color: C) -> Self { + self.marker.color = Some(Box::new(color)); + self + } + + /// Sets the marker opacity of un/selected points. + pub fn opacity(mut self, opacity: f64) -> Self { + self.marker.opacity = Some(opacity); + self + } + + /// Sets the marker size of un/selected points. + pub fn size(mut self, size: usize) -> Self { + self.marker.size = Some(Dim::Scalar(size)); + self + } +} + #[serde_with::skip_serializing_none] #[derive(Serialize, Clone, Debug, FieldSetter)] pub struct TickFormatStop { diff --git a/plotly/src/traces/scatter_geo.rs b/plotly/src/traces/scatter_geo.rs index 6e0cd8c7..0a0dc1ec 100644 --- a/plotly/src/traces/scatter_geo.rs +++ b/plotly/src/traces/scatter_geo.rs @@ -17,42 +17,10 @@ pub enum Fill { ToSelf, } -#[serde_with::skip_serializing_none] -#[derive(Serialize, Clone, Debug, Default)] -pub struct SelectionMarker { - color: Option>, - opacity: Option, - size: Option>, -} - -#[derive(Serialize, Clone, Debug, Default)] -pub struct Selection { - marker: SelectionMarker, -} - -impl Selection { - pub fn new() -> Self { - Default::default() - } - - /// Sets the marker color of un/selected points. - pub fn color(mut self, color: C) -> Self { - self.marker.color = Some(Box::new(color)); - self - } - - /// Sets the marker opacity of un/selected points. - pub fn opacity(mut self, opacity: f64) -> Self { - self.marker.opacity = Some(opacity); - self - } - - /// Sets the marker size of un/selected points. - pub fn size(mut self, size: usize) -> Self { - self.marker.size = Some(Dim::Scalar(size)); - self - } -} +// `Selection` and `SelectionMarker` are defined in `crate::common` and +// re-exported here for backward compatibility (they used to live in this +// module). +pub use crate::common::{Selection, SelectionMarker}; #[serde_with::skip_serializing_none] #[derive(Serialize, Clone, Debug, FieldSetter)] diff --git a/plotly/src/traces/scatter_map.rs b/plotly/src/traces/scatter_map.rs index d9f8708f..b63c1386 100644 --- a/plotly/src/traces/scatter_map.rs +++ b/plotly/src/traces/scatter_map.rs @@ -17,42 +17,10 @@ pub enum Fill { ToSelf, } -#[serde_with::skip_serializing_none] -#[derive(Serialize, Clone, Debug, Default)] -pub struct SelectionMarker { - color: Option>, - opacity: Option, - size: Option>, -} - -#[derive(Serialize, Clone, Debug, Default)] -pub struct Selection { - marker: SelectionMarker, -} - -impl Selection { - pub fn new() -> Self { - Default::default() - } - - /// Sets the marker color of un/selected points. - pub fn color(mut self, color: C) -> Self { - self.marker.color = Some(Box::new(color)); - self - } - - /// Sets the marker opacity of un/selected points. - pub fn opacity(mut self, opacity: f64) -> Self { - self.marker.opacity = Some(opacity); - self - } - - /// Sets the marker size of un/selected points. - pub fn size(mut self, size: usize) -> Self { - self.marker.size = Some(Dim::Scalar(size)); - self - } -} +// `Selection` and `SelectionMarker` are defined in `crate::common` and +// re-exported here for backward compatibility (they used to live in this +// module). +pub use crate::common::{Selection, SelectionMarker}; /// Native point-clustering options for a [`ScatterMap`] trace. /// diff --git a/plotly/src/traces/scatter_mapbox.rs b/plotly/src/traces/scatter_mapbox.rs index ef65b891..8a7f5d4c 100644 --- a/plotly/src/traces/scatter_mapbox.rs +++ b/plotly/src/traces/scatter_mapbox.rs @@ -17,42 +17,10 @@ pub enum Fill { ToSelf, } -#[serde_with::skip_serializing_none] -#[derive(Serialize, Clone, Debug, Default)] -pub struct SelectionMarker { - color: Option>, - opacity: Option, - size: Option>, -} - -#[derive(Serialize, Clone, Debug, Default)] -pub struct Selection { - marker: SelectionMarker, -} - -impl Selection { - pub fn new() -> Self { - Default::default() - } - - /// Sets the marker color of un/selected points. - pub fn color(mut self, color: C) -> Self { - self.marker.color = Some(Box::new(color)); - self - } - - /// Sets the marker opacity of un/selected points. - pub fn opacity(mut self, opacity: f64) -> Self { - self.marker.opacity = Some(opacity); - self - } - - /// Sets the marker size of un/selected points. - pub fn size(mut self, size: usize) -> Self { - self.marker.size = Some(Dim::Scalar(size)); - self - } -} +// `Selection` and `SelectionMarker` are defined in `crate::common` and +// re-exported here for backward compatibility (they used to live in this +// module). +pub use crate::common::{Selection, SelectionMarker}; #[serde_with::skip_serializing_none] #[derive(Serialize, Clone, Debug, FieldSetter)] From 4cf45feb239b8f9b6fed461822be4a7ff9e6e6d8 Mon Sep 17 00:00:00 2001 From: Joel Natividad <1980690+jqnatividad@users.noreply.github.com> Date: Sat, 11 Jul 2026 09:23:24 -0400 Subject: [PATCH 2/8] feat(traces): backfill legendrank/legendwidth/uirevision + grouping Bring the older trace types up to plotly.js 3.7.0 parity on the common "universal" attributes that the newer traces already expose: - legendrank, legendwidth, uirevision on Scatter, Bar, BoxPlot, Violin, Histogram, HeatMap, Contour, Candlestick, Ohlc, Scatter3D, Surface, ScatterPolar (Scatter3D already had legendrank). - uirevision only on Sunburst, Treemap, Table (these do not participate in the legend, so legendrank/legendwidth do not apply). - offsetgroup/alignmentgroup on Scatter (Bar/BoxPlot/Violin/Histogram already had them). All fields are additive Option with serde renames; existing tests pass unchanged. Contour uses a hand-written builder (no FieldSetter derive), so its fields, Default impl, and setters were added by hand. Added focused rename-guard tests for Scatter and Contour. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --- CHANGELOG.md | 1 + plotly/src/traces/bar.rs | 14 +++++++++ plotly/src/traces/box_plot.rs | 14 +++++++++ plotly/src/traces/candlestick.rs | 14 +++++++++ plotly/src/traces/contour.rs | 47 +++++++++++++++++++++++++++++- plotly/src/traces/heat_map.rs | 13 +++++++++ plotly/src/traces/histogram.rs | 14 +++++++++ plotly/src/traces/ohlc.rs | 14 +++++++++ plotly/src/traces/scatter.rs | 38 ++++++++++++++++++++++++ plotly/src/traces/scatter3d.rs | 8 +++++ plotly/src/traces/scatter_polar.rs | 13 +++++++++ plotly/src/traces/sunburst.rs | 4 +++ plotly/src/traces/surface.rs | 14 +++++++++ plotly/src/traces/table.rs | 5 ++++ plotly/src/traces/treemap.rs | 4 +++ plotly/src/traces/violin.rs | 13 +++++++++ 16 files changed, 229 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac055798..ea098163 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - [[#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 - [[#417](https://github.com/plotly/plotly.rs/issues/417)] Add `ScatterMap` (MapLibre `map` subplot) trace type — the modern counterpart to `ScatterMapbox` - [[#418](https://github.com/plotly/plotly.rs/issues/418)] Add native point clustering to `ScatterMap` via a `Cluster` option +- [TBD] Backfill common trace attributes for plotly.js parity: add `legendrank`, `legendwidth`, and `uirevision` to the older trace types that lacked them (`Scatter`, `Bar`, `BoxPlot`, `Violin`, `Histogram`, `HeatMap`, `Contour`, `Candlestick`, `Ohlc`, `Scatter3D`, `Surface`, `ScatterPolar`; `uirevision` only for `Sunburst`, `Treemap`, `Table`), plus `offsetgroup`/`alignmentgroup` on `Scatter`. Consolidate the duplicated `Selection`/`SelectionMarker` structs into `common` (re-exported from their original modules for backward compatibility) ### Changed diff --git a/plotly/src/traces/bar.rs b/plotly/src/traces/bar.rs index a3e91fe0..9d665194 100644 --- a/plotly/src/traces/bar.rs +++ b/plotly/src/traces/bar.rs @@ -8,6 +8,7 @@ use crate::{ Calendar, ConstrainText, Dim, ErrorData, Font, HoverInfo, Label, LegendGroupTitle, Marker, Orientation, PlotType, TextAnchor, TextPosition, Visible, XAxisId, YAxisId, }, + private::NumOrString, Trace, }; @@ -105,6 +106,19 @@ where x_calendar: Option, #[serde(rename = "ycalendar")] y_calendar: Option, + /// Sets the legend rank for this trace. Items and groups with smaller ranks + /// are presented on top/left side while with `"reversed"` + /// `legend.trace_order` they are on bottom/right side. The default + /// legendrank is 1000. + #[serde(rename = "legendrank")] + legend_rank: Option, + /// Sets the width (in px or fraction) of the legend for this trace. + #[serde(rename = "legendwidth")] + legend_width: Option, + /// Controls persistence of user-driven changes to the trace. Defaults to + /// `layout.uirevision`. + #[serde(rename = "uirevision")] + ui_revision: Option, } impl Bar diff --git a/plotly/src/traces/box_plot.rs b/plotly/src/traces/box_plot.rs index f65ff33f..d6aea5b0 100644 --- a/plotly/src/traces/box_plot.rs +++ b/plotly/src/traces/box_plot.rs @@ -9,6 +9,7 @@ use crate::{ Calendar, Dim, HoverInfo, Label, LegendGroupTitle, Line, Marker, Orientation, PlotType, Visible, XAxisId, YAxisId, }, + private::NumOrString, Trace, }; @@ -172,6 +173,19 @@ where x_calendar: Option, #[serde(rename = "ycalendar")] y_calendar: Option, + /// Sets the legend rank for this trace. Items and groups with smaller ranks + /// are presented on top/left side while with `"reversed"` + /// `legend.trace_order` they are on bottom/right side. The default + /// legendrank is 1000. + #[serde(rename = "legendrank")] + legend_rank: Option, + /// Sets the width (in px or fraction) of the legend for this trace. + #[serde(rename = "legendwidth")] + legend_width: Option, + /// Controls persistence of user-driven changes to the trace. Defaults to + /// `layout.uirevision`. + #[serde(rename = "uirevision")] + ui_revision: Option, } impl BoxPlot diff --git a/plotly/src/traces/candlestick.rs b/plotly/src/traces/candlestick.rs index 8728a741..c9ff4e50 100644 --- a/plotly/src/traces/candlestick.rs +++ b/plotly/src/traces/candlestick.rs @@ -3,6 +3,7 @@ use plotly_derive::FieldSetter; use serde::Serialize; +use crate::private::NumOrString; use crate::{ color::NamedColor, common::{ @@ -89,6 +90,19 @@ where hover_label: Option