chart: Introduce SankeyChart#2538
Merged
Merged
Conversation
madcodelife
marked this pull request as draft
July 6, 2026 09:35
madcodelife
force-pushed
the
chart-sankey
branch
3 times, most recently
from
July 7, 2026 07:21
107ff6f to
aa73e50
Compare
Add a Sankey diagram chart to the plot system: - `plot/shape/sankey.rs`: layout generator ported from d3-sankey (node depth/height via BFS, `Left`/`Right`/`Center`/`Justify` alignments, iterative relaxation with collision resolution), plus a `sankey_link_path()` helper building the filled cubic-bezier ribbon. Circular links return `SankeyError` instead of panicking. - `chart/sankey_chart.rs`: `SankeyChart` with builder-style API (`node_width`, `node_padding`, `node_align`, `node_color`, `node_label`, `value_label`, `link_opacity`, `min_link_width`). Ribbons are filled with a source-to-target color gradient; label margins are reserved via a two-pass layout. - Story: TSLA income statement examples (FY 2025 with sqrt-compressed values, FY 2024 linear) rendered from a fixture shaped like the real API response. - Docs: `SankeyChart` sections in both `en` and `zh-CN` locales. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Remove the no-op `id()` builder: it opted into the `IntoPlot` tooltip machinery (full-window `refresh()` on every hover) while `tooltip_state` was never implemented, so it only caused repaint churn with no tooltip. - Replace the `ScaleOrdinal` color lookup (O(n^2) scan + Vec alloc, with a dead `unwrap_or_else` fallback) with a direct `palette[index % 5]`. - Only reserve the top label band when middle columns exist (`layer_count > 2`), and cap the top/bottom margins like the horizontal ones so a short chart no longer collapses the flow area to 1px. - Deduplicate `compute_node_depths`/`compute_node_heights` into a single `compute_node_ranks(forward)`. - Fix the docs' value-compression example: `FlowNode` now carries the `value` field the `value_label` closure reads (en + zh-CN). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add a `SankeyLabel` line type (text with optional color and font size) and a `labels` builder that returns one styled line per label, taking precedence over `node_label`/`value_label`. The chart keeps handling placement, alignment and margin reservation; the top band and label margins now size to the actual label blocks. The TSLA FY 2025 story example uses it to render the reference design's three-line label: value, year-over-year growth (▲ green / ▼ red), and name. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First/last-column label blocks are vertically centered beside their node, so a short node near the top or bottom edge pushed part of its label outside the plot where it was clipped. Clamp the block into the plot area instead, keeping it beside the node whenever it fits. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Node heights take the max of incoming and outgoing sums, so on an imbalanced graph (e.g. sqrt-compressed link values, where sums are not additive) the smaller side's ribbons no longer covered the node, leaving gaps between the node and its links. Give each ribbon end its own width: the links on a node side share the node's full height in proportion to their values, so both sides are always fully covered and the ribbon transitions smoothly between its two ends. On a balanced graph this is identical to the previous uniform-width stacking. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A first/last-column label wider than its reserved (and capped) margin was drawn at full width, spilling out of the plot area. Truncate it with a trailing ellipsis to the reserved width instead. Middle-column labels sit above interior nodes and never reach the plot edge, so they are left untruncated. Add a reusable `truncate_text_to_width` helper (binary search over char boundaries) to the plot label module. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A crowded column forces a small scale, so sparser columns don't fill the height, and the relaxation aligns them to their flows' weighted center — leaving the trunk sitting high with empty space below. Center each column's stack in the extent by translating it (preserving the relaxation's within-column arrangement and the ribbon fits), so the diagram stays balanced on the canvas. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Centering aligns adjacent single-node columns of equal height exactly, so the ribbon between them is a flat rectangle. Nudge the odd columns of such a run off the center line (bounded by the available slack, so a full-height column stays put and never leaves the extent) to turn the flat ribbon into a gentle S-curve. Only the degenerate flat case is touched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A very long first/last-column label reserved up to a shared 60% of the width, so even after truncation it dominated the chart and squeezed the flow into a narrow band. Cap each side independently at 20% of the width instead, so a long label is truncated to a modest column and the flow area stays dominant. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add `SankeyValueScale { Linear, Sqrt }` and a `value_scale` builder so a
wide value range can be compressed without the caller pre-transforming
its data: a dominant flow no longer dwarfs the small ones and the small
ones stay visible. The scale is applied once at the layout entry, so all
downstream math stays additive in scaled space and every node remains
exactly filled by its ribbons (children match their parent's height)
under either scale. Labels receive the raw throughput, computed from the
input links, not the scaled layout value.
The TSLA story examples now use `.value_scale(Sqrt)` instead of manually
square-rooting the link values.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Follow-ups from an Opus review of the sankey implementation (no correctness bugs were found): - Assert link ends stay attached to their nodes after `stagger_flat_columns` (guards a source/target mix-up in `apply_layer_offsets`), and that `center_columns` keeps every node in the extent. - Test `raw_throughput`: it returns raw per-node sums and, under `Sqrt`, differs from the layout's scaled node value (the label value-leak guard). - Bound middle-column labels by the distance to the plot edge so a runaway one is truncated rather than drawn off-plot, instead of the previous unenforced "never reaches the edge" assumption. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Follow-ups from a comment review (comments were otherwise accurate): - Document the undocumented public items: `SankeyError`, `Sankey::new`, `SankeyLink::new`, `SankeyChart::new`, `SankeyLabel::new`. - State the "unset by default" behavior on `node_label`/`value_label`/ `labels`, and that the value passed to them is the raw (unscaled) throughput. - Fix the first-pass comment: it uses only `node.layer`, not the topology's value (labels come from `raw_throughput`). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
madcodelife
marked this pull request as ready for review
July 13, 2026 03:04
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.