Skip to content

Commit 0820c33

Browse files
jqnatividadclaude
andcommitted
feat(traces): add Funnel trace type
plotly.js has shipped the `funnel` trace since well before the bundled 3.7.0 build — only the Rust wrapper was missing. `funnel` is bar-like and cartesian, so unlike `funnelarea` (domain-based, pie-like, out of scope here) it composes with subplot grids and static image export. Field set follows `Bar`, filtered to the attributes plotly.js actually declares for `funnel`: `offset` and `width` are scalar-only (`arrayOk: false` in the schema) and there is no `base`, error bars, calendars or point selection. `textinfo` is a plain `String` because plotly.js takes a `+`-joined flaglist; this follows the existing precedent in `pie.rs` rather than enumerating the combinations. Also adds the layout-level `funnelmode`/`funnelgap`/`funnelgroupgap` attributes with a `FunnelMode` enum, mirroring the existing waterfall/violin/box mode fields — without `funnelmode`, stacked and grouped multi-trace funnels are unreachable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 50228ab commit 0820c33

7 files changed

Lines changed: 399 additions & 4 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
1313
- [[#412](https://github.com/plotly/plotly.rs/pull/412)] Add `Violin` trace type with box, mean line, KDE span, and split/grouped support
1414
- [[#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
1515
- [[#417](https://github.com/plotly/plotly.rs/issues/417)] Add `ScatterMap` (MapLibre `map` subplot) trace type — the modern counterpart to `ScatterMapbox`
16+
- Add `Funnel` trace type (bar-like, cartesian) with `Connector` and `FunnelHoverInfo` options, plus the layout-level `funnelmode`/`funnelgap`/`funnelgroupgap` attributes and a `FunnelMode` enum
1617
- [[#418](https://github.com/plotly/plotly.rs/issues/418)] Add native point clustering to `ScatterMap` via a `Cluster` option
1718

1819
### Changed

‎plotly/src/common/mod.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ pub enum PlotType {
222222
Choropleth,
223223
ChoroplethMap,
224224
Contour,
225+
Funnel,
225226
HeatMap,
226227
Histogram,
227228
Histogram2dContour,

‎plotly/src/layout/mod.rs‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ pub use self::legend::{GroupClick, ItemClick, ItemSizing, Legend, TraceOrder};
4444
pub use self::map::{LayoutMap, MapBounds, MapStyle};
4545
pub use self::mapbox::{Center, Mapbox, MapboxStyle};
4646
pub use self::modes::{
47-
AspectMode, BarMode, BarNorm, BoxMode, ClickMode, UniformTextMode, ViolinMode, WaterfallMode,
47+
AspectMode, BarMode, BarNorm, BoxMode, ClickMode, FunnelMode, UniformTextMode, ViolinMode,
48+
WaterfallMode,
4849
};
4950
pub use self::polar::{
5051
AngularAxis, AngularAxisType, AutoRange, AutoRangeOptions, AutoTypeNumbers, AxisLayer,
@@ -379,6 +380,12 @@ pub struct LayoutFields {
379380
waterfall_gap: Option<f64>,
380381
#[serde(rename = "waterfallgroupgap")]
381382
waterfall_group_gap: Option<f64>,
383+
#[serde(rename = "funnelmode")]
384+
funnel_mode: Option<FunnelMode>,
385+
#[serde(rename = "funnelgap")]
386+
funnel_gap: Option<f64>,
387+
#[serde(rename = "funnelgroupgap")]
388+
funnel_group_gap: Option<f64>,
382389
#[serde(rename = "piecolorway")]
383390
pie_colorway: Option<Vec<Box<dyn Color>>>,
384391
#[serde(rename = "extendpiecolors")]

‎plotly/src/layout/modes.rs‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ pub enum WaterfallMode {
7171
Overlay,
7272
}
7373

74+
#[derive(Serialize, Debug, Clone)]
75+
#[serde(rename_all = "lowercase")]
76+
pub enum FunnelMode {
77+
Stack,
78+
Group,
79+
Overlay,
80+
}
81+
7482
#[derive(Debug, Clone)]
7583
pub enum UniformTextMode {
7684
False,
@@ -146,6 +154,13 @@ mod tests {
146154
assert_eq!(to_value(WaterfallMode::Overlay).unwrap(), json!("overlay"));
147155
}
148156

157+
#[test]
158+
fn serialize_funnel_mode() {
159+
assert_eq!(to_value(FunnelMode::Stack).unwrap(), json!("stack"));
160+
assert_eq!(to_value(FunnelMode::Group).unwrap(), json!("group"));
161+
assert_eq!(to_value(FunnelMode::Overlay).unwrap(), json!("overlay"));
162+
}
163+
149164
#[test]
150165
fn serialize_aspect_mode() {
151166
let aspect_mode = AspectMode::default();

‎plotly/src/lib.rs‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,14 @@ pub use layout::Layout;
6060
pub use plot::{Plot, Trace, Traces};
6161
// Also provide easy access to modules which contain additional trace-specific types
6262
pub use traces::{
63-
box_plot, choropleth, choropleth_map, contour, density_map, heat_map, histogram, image, mesh3d,
64-
sankey, scatter, scatter3d, scatter_map, scatter_mapbox, sunburst, surface, treemap, violin,
63+
box_plot, choropleth, choropleth_map, contour, density_map, funnel, heat_map, histogram, image,
64+
mesh3d, sankey, scatter, scatter3d, scatter_map, scatter_mapbox, sunburst, surface, treemap,
65+
violin,
6566
};
6667
// Bring the different trace types into the top-level scope
6768
pub use traces::{
6869
Bar, BoxPlot, Candlestick, Choropleth, ChoroplethMap, Contour, DensityMap, DensityMapbox,
69-
HeatMap, Histogram, Image, Mesh3D, Ohlc, Pie, Sankey, Scatter, Scatter3D, ScatterGeo,
70+
Funnel, HeatMap, Histogram, Image, Mesh3D, Ohlc, Pie, Sankey, Scatter, Scatter3D, ScatterGeo,
7071
ScatterMap, ScatterMapbox, ScatterPolar, Sunburst, Surface, Table, Treemap, Violin,
7172
};
7273

0 commit comments

Comments
 (0)