Skip to content

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

Open
jqnatividad wants to merge 8 commits into
plotly:mainfrom
dathere:feat/treemap-sunburst-traces
Open

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

Conversation

@jqnatividad

@jqnatividad jqnatividad commented Jun 26, 2026

Copy link
Copy Markdown

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 Add Treemap and Sunburst trace types; upgrade plotly.js from 3.0.1 to 3.6.0 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
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
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

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Upgrade bundled plotly.js to 3.6 Add Treemap and Sunburst types

2 participants