Skip to content

Add Treemap and Sunburst trace types; upgrade plotly.js from 3.0.1 to 3.6.0#406

Merged
andrei-ng merged 8 commits into
plotly:mainfrom
dathere:feat/treemap-sunburst-traces
Jul 1, 2026
Merged

Add Treemap and Sunburst trace types; upgrade plotly.js from 3.0.1 to 3.6.0#406
andrei-ng merged 8 commits into
plotly:mainfrom
dathere:feat/treemap-sunburst-traces

Conversation

@jqnatividad

@jqnatividad jqnatividad commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Adds two hierarchical trace types — Treemap and Sunburst — to the plotly crate, upgrades the bundled Plotly.js, and exposes the new attributes that came with it. The trace types follow the existing Pie pattern and share Plotly's labels/parents/ids/values data model.

What's added

Trace types

  • Treemap<V>: hierarchy fields (branch_values, count, level, max_depth), domain, full text/hover set, Tiling (Packing) and PathBar (Side) helper structs, and a dedicated treemap::Marker exposing the treemap-only pad, corner_radius, and depth_fade attributes (on top of the shared color/colorscale/colorbar/line/pattern machinery).
  • Sunburst<V>: same hierarchy/text/hover set plus Leaf, rotation, and inside_text_orientation.
  • New PlotType::Treemap / PlotType::Sunburst variants and top-level re-exports; new treemapcolorway / extendtreemapcolors layout options.

Bundled Plotly.js upgrade (#407)

  • Upgraded the vendored/CDN Plotly.js from 3.0.1 → 3.6.0 (latest). Replaced the three vendored plotly.min.js copies (plotly/resource, plotly_static/resource, docs/book) and the pinned CDN version strings in plot.rs, the Jupyter template, plotly_static/template.rs, and the book header.

New 3.1–3.6 attributes exposed

Additive bindings for the user-facing attributes added across Plotly.js 3.1.0 → 3.6.0, following the existing Option<T> + FieldSetter pattern:

  • Layout: hoversort (HoverSort), hoveranywhere, clickanywhere
  • Axis: zerolinelayer (ZeroLineLayer), minorloglabels, modebardisable (ModeBarDisable), ticklabelposition (TickLabelPosition), unifiedhovertitle (UnifiedHoverTitle), and ExponentFormat::SIExtended
  • Legend: maxheight
  • Configuration: displayNotifier
  • common::Label (hover labels): showarrow
  • common::Pattern: path (arbitrary SVG path fill, arrayOk)
  • Candlestick/Ohlc: hovertemplate
  • hovertemplatefallback / texttemplatefallback across applicable traces

Tests & docs

  • Unit tests + doctests for the new trace types and attributes, four basic_charts examples, and book pages.

Compatibility note

Because FieldSetter/layout_structs generate a per-field variant in the Restyle*/RelayoutLayout enums, adding any field is a semver-breaking change (cargo-semver-checks flags enum_variant_added). Combined with the new PlotType and ExponentFormat variants, this targets the next breaking release (0.15.0).

I used Claude Code Opus 4.8 and reviewed the code and tested it with my project - qsv.

See https://github.com/dathere/qsv/wiki/Visualization

Closes #405
Closes #407

jqnatividad and others added 3 commits June 25, 2026 16:20
Add hierarchical Treemap and Sunburst traces, mirroring the existing Pie
trace pattern:

- Treemap<V>: labels/parents/values hierarchy with BranchValues, plus
  Tiling (Packing) and PathBar (Side) helper structs
- Sunburst<V>: same hierarchy plus Leaf, rotation and
  inside_text_orientation
- New Treemap/Sunburst PlotType variants and top-level re-exports
- treemapcolorway/extendtreemapcolors Layout options
- Unit tests, doctests, basic_charts examples and book pages

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the shared common::Marker on Treemap with a treemap-specific
Marker exposing the treemap-only attributes: pad (Pad{t,l,r,b}),
corner_radius, and depth_fade (true/false/"reversed"). The shared
color/colorscale/colorbar/line/pattern machinery is retained, and the
scatter-only fields (size, symbol, ...) that don't apply to treemaps are
dropped. Showcased in the styled_treemap example.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jqnatividad added a commit to dathere/qsv that referenced this pull request Jun 26, 2026
…lone subcommands) (#4080)

* feat(viz): treemap & sunburst hierarchy panels + standalone subcommands

Add categorical part-to-whole hierarchy charts to `viz`, backed by the
Treemap/Sunburst trace types from the plotly.rs fork (PR plotly/plotly.rs#406).

`viz smart` now adds a hierarchy panel when the dataset has 2+ genuine
(String) low-cardinality dimensions, auto-selecting the chart by depth per
visualization best practice: a treemap for a shallow (2-level) hierarchy
(area encodes size for accurate comparison) and a sunburst for a deeper
(3-level) one (rings emphasize parent-child structure). Override with
`--hierarchy-style auto|treemap|sunburst`. The chosen dimensions keep their
own frequency bars; the panel is domain-based, so (like map/geo/3D) it renders
via the inline path. Restricting dims to String type with cardinality >= 3
keeps numeric codes/booleans out and avoids forcing small dashboards inline.

Also add standalone `viz treemap` / `viz sunburst` subcommands (--cols for the
hierarchy levels, optional additive --value/--agg). Treemap tiles use the
fork's treemap-specific Marker (rounded corners, inner padding, white outline).

- one-pass leaf accumulation + pure flat-array builder (top-N per level with an
  "Other (k)" bucket, path-joined ids, rolled-up branchvalues=total)
- 5 unit + 6 integration tests
- regenerated docs/help/viz.md, the qsv-viz MCP skill, and the examples gallery
  (incl. two `viz smart --dictionary infer` dashboards showcasing both charts)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(viz): validate --value measure for treemap/sunburst (roborev #3190)

In value mode, `accumulate_hierarchy_counts` silently coerced parse failures to
0.0 and accepted negative / non-finite values, so a typo'd or non-numeric
--value column produced a blank or misleading area chart while the command
succeeded.

Now non-empty value cells must parse to a finite, non-negative number; empty
cells stay a benign missing measure (skipped). Unusable cells are skipped and
tallied with a warning, and an all-unusable measure column returns a clear
error instead of charting zeros. Adds value-sum and all-invalid-value tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(viz): error on any invalid --value cell for treemap/sunburst (roborev #3191)

Follow-up to #3190: invalid non-empty --value cells only warned (when at least
one valid value existed), so a partially-malformed measure column still
produced a "successful" treemap/sunburst with rows silently dropped — quietly
misstating every part-to-whole proportion.

Make any unusable measure cell (non-numeric, negative, or non-finite) a hard
error reporting the bad-cell count; empty cells remain a benign missing measure
(skipped). Adds a mixed valid/invalid test alongside the all-invalid case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(viz): add standalone treemap & sunburst figures to the examples gallery

The gallery showcased the treemap/sunburst hierarchy only via the `viz smart`
dashboards; add the two standalone chart types as individual figures too:
- treemap: customer_spend plan -> region, sized by summed monthly_spend
  (exercises the validated --value path + the treemap-specific marker)
- sunburst: sales_sample region -> product_category -> payment_method

Regenerated gallery.html (29 figures).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the three vendored plotly.min.js copies (plotly, plotly_static,
docs/book) with v3.6.0 and bumps the pinned CDN version strings in
plot.rs, the jupyter notebook template, plotly_static template, and the
book header.

Closes plotly#407

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jqnatividad jqnatividad changed the title Add Treemap and Sunburst trace types Jun 27, 2026
jqnatividad and others added 2 commits June 27, 2026 07:31
Surfaces the user-facing attributes added across plotly.js 3.1.0 through
3.6.0 (now bundled), following the existing Option<T> + FieldSetter pattern:

- Layout: hoversort (HoverSort), hoveranywhere, clickanywhere
- Axis: zerolinelayer (ZeroLineLayer), minorloglabels, modebardisable
  (ModeBarDisable), ticklabelposition (TickLabelPosition), unifiedhovertitle
  (UnifiedHoverTitle), and ExponentFormat::SIExtended
- Legend: maxheight
- Configuration: displayNotifier
- common::Label (hover labels): showarrow
- common::Pattern: path (arbitrary SVG path fill)
- Candlestick/Ohlc: hovertemplate
- hovertemplatefallback / texttemplatefallback across applicable traces

These are additive (new fields/enums/variants). Because FieldSetter and
layout_structs generate per-field Restyle/Relayout enum variants, adding any
field is a semver-breaking change; this targets the next breaking release
(0.15.0), consistent with the Treemap/Sunburst additions already on this
branch.

Refs plotly#407

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Plotly.js pattern schema marks `path` as arrayOk (pathsrc present),
like shape/bgcolor/fgcolor/size/solidity. Modeling it as Option<String>
prevented per-point custom SVG path fills. Switch to Option<Dim<String>>
so FieldSetter generates both scalar and array setters, and add a
serialize_pattern_path test covering both forms.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jqnatividad

Copy link
Copy Markdown
Contributor Author

Pushed two more commits:

  • 68f99f6 feat: expose plotly.js 3.1–3.6 attributes (additive bindings for the attributes that landed with the bundle upgrade)
  • aeb1e59 fix: make Pattern::path arrayOk (Option<Dim<String>>) — addresses a review finding

Updated the PR description with the Plotly.js 3.0.1→3.6.0 upgrade (#407) and the full list of newly exposed attributes. All builds/clippy/nightly-fmt/tests pass locally (the only failing tests are the pre-existing browser-export ones that need a chromedriver binary).

jqnatividad and others added 2 commits June 27, 2026 09:32
Plotly's `insidetextorientation` attribute expects the full words
`horizontal`/`radial`/`tangential`/`auto`, but the field reused the
general `Orientation` enum which serializes to single-letter codes
(`h`/`v`/`r`/`t`). `Orientation::Radial` emitted `"r"`, which plotly.js
silently coerces to the default `"auto"` — so setting a radial sunburst
orientation was a no-op.

Add a sunburst-specific `InsideTextOrientation` enum
(`#[serde(rename_all = "lowercase")]` -> full words) and switch the
field to it. The shared `Orientation` enum is left untouched so bars,
boxes, legends and sankey keep their correct `h`/`v` codes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Sunburst inside_text_orientation setter takes the Sunburst-specific
InsideTextOrientation enum, not common::Orientation. Update the example
import and call so the basic-charts example crate compiles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jqnatividad added a commit to dathere/qsv that referenced this pull request Jun 27, 2026
…l sunburst text, unified hover) (#4085)

* feat(viz): adopt plotly.js 3.6.0 attributes (richer OHLC hover, radial sunburst text, unified hover)

Upgrades the dathere plotly fork pin to its plotly.js 3.0.1 -> 3.6.0 branch
(feat/treemap-sunburst-traces @ aeb1e59, upstream PR plotly/plotly.rs#406) and
adopts three new additive, non-breaking attributes in `viz`:

- Candlestick/OHLC `hover_template`: clean Open/High/Low/Close readout with
  `<extra></extra>` to drop the trace-name box, plus a defensive
  `hover_template_fallback` (financial traces have known hover-variable gaps).
- Sunburst `inside_text_orientation(Radial)`: label+value+percent runs along
  each ring's spoke so deep-path sectors stay legible (standalone + smart panel).
- `x unified` hover (Layout `hover_mode`) scoped to ordered-x chart kinds
  (line, candlestick, ohlc) — one tooltip per x across series. Excludes
  scatter/bar/box; the smart dashboard builds its own layout and is untouched.

Regenerates the example gallery (now CDN plotly 3.6.0) so the figures reflect
the new attributes. Adds/extends tests for all three (viz_candlestick,
viz_ohlc, viz_sunburst_standalone, new viz_line_unified_hover).

Verified: 126 viz tests pass; clippy clean; candlestick hover confirmed to
resolve %{open}/%{high}/%{low}/%{close} at runtime in a browser.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(viz): emit valid "radial" sunburst inside-text orientation (#3211)

`Sunburst::inside_text_orientation(Orientation::Radial)` serialized to
`"insidetextorientation":"r"` — the shared `Orientation` enum's
single-letter code (correct for bars/boxes/legends/sankey, wrong here).
plotly.js 3.6.0 only accepts the full words
`horizontal`/`radial`/`tangential`/`auto` and silently coerces `"r"` to
the default `"auto"`, so the radial sunburst text was a no-op and the
test locked in the invalid value.

Fix in the dathere plotly fork by adding a sunburst-specific
`InsideTextOrientation` enum that serializes to full words, leaving the
general `Orientation` enum untouched. Re-pin `Cargo.lock` at the fork
branch tip (3c185f8), switch the call site, drop the now-unused
`Orientation` import, update the test to assert `"radial"`, and
regenerate the gallery.

Browser-verified against plotly.js 3.6.0: `_fullData` resolves the
orientation to `"radial"` (not coerced to `"auto"`) and labels render
radially along each ring's spoke.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(viz): regenerate --dictionary infer gallery figures (radial sunburst)

Refresh the two LLM-dependent smart-dashboard gallery artifacts
(smart_dict_treemap.html, smart_dict_sunburst.html) from the current
qsv binary via describegpt `--dictionary infer` (local LM Studio,
google/gemma-3-27b), and rebuild gallery.html to reference them.

The sunburst dict figure now carries the valid
`"insidetextorientation":"radial"` attribute (was the no-op `"r"`);
both pages embed plotly.js 3.6.0 from the CDN. Browser-verified: 14 and
5 panels respectively, 0 render errors, sunburst resolves to radial.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@andrei-ng

Copy link
Copy Markdown
Collaborator

Thanks for the PR. I will review it soon

@jqnatividad

jqnatividad commented Jun 27, 2026

Copy link
Copy Markdown
Contributor Author

Great! FYI, once this lands, I have dathere#2 that's based on this.

BTW, if you want to check out how qsv uses plotly.rs, check https://github.com/dathere/qsv/wiki/Visualization

@andrei-ng

Copy link
Copy Markdown
Collaborator

@jqnatividad Thank you very much for the contribution.
I do ask that next time you make separate PRs if you takle multiple problems.

@andrei-ng andrei-ng merged commit 4ad2c92 into plotly:main Jul 1, 2026
31 of 32 checks passed
@jqnatividad

Copy link
Copy Markdown
Contributor Author

Thanks for merging this PR @andrei-ng .

Per your guidance, I submitted #410 to add choropleth map support.

FYI, I plan on submitting Violin plot support and experimenting on making it easy to create something like the Gapminder animation (https://plotly.com/javascript/gapminder-example/).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants