A datum that renders as several leaves cannot be stated once: `states` on a group is never resolved
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 749
- Forks
- 45
- Avg merge
- 1d 18h
- Merged PRs (30d)
- 26
Description
Summary
A mark that emits several SVG leaves per datum (a candlestick's upper wick, lower
wick and body; anything with a similar internal composition) has no way to express
"this datum is dimmed" once. Every leaf resolves its own state and runs its own
tween. Wrapping the leaves in a group and putting states on the group does not
help, because mark-state.js never resolves state for a group node.
I am not reporting a rendering bug — the output is correct. The cost is per-hover
animation work, and the absence of any way for a consumer to opt out of it.
Version
@tanstack/charts@0.16.0
What happens
dist/mark-state.js:17, the single path used by the motion, SVG, canvas and native
renderers:
const state = node.kind === "group" ? node.states : void 0;
...
const resolved = node.kind !== "group" && nodeDefinitions && nodeData && candidates.length
? resolveNodeState(node, candidates, nodeData, nodeDefinitions, focus, pointer)
: { node };
A group's states only supply definitions and points to its descendants; the group
itself falls through to { node }, unresolved. So a group cannot carry the dim.
The descendants then each resolve anyway, because ownership strips key prefixes —
sceneKeyOwnedPoints walks while (candidate.includes(":")) back through the last
: (dist/scene-point-ownership-internal.js:40-58), so candles:5:body matches
point candles:5. Regrouped or not, three leaves resolve three times and three
tweens run.
Why it matters
Measured on a 1000-candle candlestick, hovering to dim the non-hovered series:
| dim latency | |
|---|---|
hand-written SVG (one <g opacity> per candle, CSS transition) |
75.5 ms |
same chart on @tanstack/charts (three stateful leaves per candle) |
243 ms |
~3.2×, on the same scene, same data, same 150 ms intended transition. The difference
is the number of state resolutions and WAAPI tweens: ~1000 against ~3000.
Both sides settle to the same visual result and the same number of visually-dimmed
elements (3009 vs 2999 — a group's opacity composes onto its leaves, so the rendered
element counts match). The gap is entirely the work done to get there.
What would help, in preference order
- Resolve
stateson a group node. Ifnode.kind === "group"resolved like any
other node, a consumer could wrap the leaves of one datum and dim them in one tween.
The composed-opacity semantics already do the right thing at paint time. - An opt-out from the prefix-stripping ownership fallback — a way to say that
candles:5:bodyshould not inheritcandles:5's state, so a consumer can put
the state exactly where they want it and nowhere else.
Either one is enough. (1) is the smaller change for a consumer to adopt.
What we are not asking for
We are not asking for the leaves to be merged or the mark to emit different SVG. The
composition is ours and it is correct.
Workaround, and why we rejected it
The only mechanism that gives one tween per datum is to bake style.opacity on a
per-candle <g> ourselves: drop the mark states, plumb hover through React state,
re-render, and hand-write a replacement fade for the 150 ms tween that dies with them.
That is a second dim implementation running alongside the engine's, with focus,
tooltip and reveal blast radius. We would rather wait for the engine to be able to
say it.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in dist/mark-state.js, the shared path used by the motion, SVG, canvas, and native renderers, then read dist/scene-point-ownership-internal.js:40-58 to understand prefix ownership. Trace how group states and descendant states are resolved. Done means a grouped datum can express its state once without unnecessary per-leaf state resolutions and tweens, while preserving the existing rendered result.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- data-visualization
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100