Resolve categorical palettes once per trace in authored-marker scatters - #366
Conversation
The authored-marker Canvas2D path resolved each point's palette color through parseColor inside the per-point loop. For non-hex palette entries (named colors, rgb(), var(), oklch()) parseColor probes the DOM (createElement + getComputedStyle + removeChild), forcing a style recalc per point per frame: ~19 ms/frame at 5,000 points in headless Chromium, the entire 60 fps budget spent before drawing a mark. Hex palettes were unaffected (pure-JS hexColor path). Resolve the palette to rgba once per trace per draw, next to the existing per-trace continuous LUT, and index it per point. Measured ~0.2 ms/frame at 5,000 points after the change (~100x); point colors are unchanged, including the modulo cycling and the g.color fallback for empty palettes. Verification: node js/build.mjs clean; render_smoke_nonumpy.py OK (lit 96.586%, all probes pass); 111 focused png-export, authored-style parity, categorical gallery, color pipeline and legend fidelity tests pass; ruff check/format hooks pass (docs-app codespell hook fails on clean main in this environment: codespell binary unavailable).
📝 WalkthroughWalkthroughChangesAnnotation marker palette handling
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@js/src/51_annotations.ts`:
- Around line 293-294: Update the categorical lookup in the color-selection
logic around g.colorMode to validate the computed index before indexing
paletteRgba, covering NaN, negative, and missing codes. If the index is invalid
or produces no palette entry, return the existing default color g.color;
otherwise preserve the current palette lookup behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: f6d320ed-81c7-4622-ad08-dca72434eb68
📒 Files selected for processing (1)
js/src/51_annotations.ts
|
CI failure seems to be unrelated |
yeah the timing-sensitive tests can sometimes be flaky on the gh provisioned runners. thanks for the contribution, looks like a nice easy win |
|
Thank you! |
Adds the merged reflex-dev/xy#366 entry, plus a /update-news skill that syncs merged PRs into src/js/updates.js and gates unknown orgs behind a prompt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Authored-marker scatters (custom
marker_path/marker_glyph/per-item symbols on the Canvas2D path) with a categorical palette resolved every point's color throughparseColorinside the per-point draw loop, every frame. For non-hex palette entries (named colors,rgb(),var(),oklch(), all validated and passed through by_validate.css_color) that means a DOM probe (createElement+getComputedStyle+removeChild) and a forced style recalc per point per frame, ~4 us/point: 20 ms/frame at 5,000 points spends the whole 60 fps budget before drawing a mark, and 100,000 points runs at ~2.5 fps on color resolution alone.The palette is now resolved once per trace per draw, mirroring the existing continuous-color
_authoredLut, and indexed per point, removing the per-point cost entirely (up to ~1000x at 100k points) with identical point colors; hex palettes (including the default) were never affected. Resolving per draw rather than caching across frames keepsvar(--token)theme toggles correct with no invalidation logic, at ~4 probes/frame.Verification
node js/build.mjsclean (typecheck + bundle)python3 scripts/abi_smoke.py140 checks passedpython3 scripts/render_smoke_nonumpy.pyall probes pass, lit fraction 96.586%uv run pytestfull suite: 3354 passed, 74 skippeduv run ruff check .,uv run ruff format --check ., pre-commit hooks (ruff + codespell) passMeasured on: Linux 6.8 (Ubuntu, x86_64), Intel i7-12800H, 30 GB RAM, Node 22, Playwright 1.61.1 bundled Chromium, headless.
Benchmark (headless Chromium via Playwright, median of 20 frames)
Run from the repo root (playwright is already a pinned devDependency):
node bench.mjsSummary by CodeRabbit