Skip to content

ENG-10444: Add high-level theme presets to the declarative API#114

Open
FarhanAliRaza wants to merge 11 commits into
mainfrom
farhan/eng-10444-add-high-level-themes-to-the-declarative-xy-api
Open

ENG-10444: Add high-level theme presets to the declarative API#114
FarhanAliRaza wants to merge 11 commits into
mainfrom
farhan/eng-10444-add-high-level-themes-to-the-declarative-xy-api

Conversation

@FarhanAliRaza

Copy link
Copy Markdown
Contributor

Summary

Adds an ergonomic high-level theming layer to xy.theme() while keeping the existing token-based escape hatches and export behavior intact:

xy.theme(
    preset="dashboard",       # xy | minimal | dashboard | publication | high_contrast
    color_scheme="dark",      # light | dark | system
    palette="vibrant",        # xy | okabe_ito/colorblind | vibrant | muted
    accent="#8b5cf6",         # selection / focus / active emphasis
    contrast="normal",        # normal | high (CVD-safe palette + stronger chrome)
)

Design

  • Everything resolves Python-side (new pure python/xy/_themes.py: immutable preset catalog + resolver). Presets emit only the implemented --chart-* token vocabulary plus root background, and a default mark palette threaded through Figure.default_palette into mark building — so browser, standalone HTML, Python SVG, native PNG, and Chromium export all agree by construction. No new JS token vocabulary.
  • color_scheme="system" ships both variants on the wire (dom.style light + dom.styleDark); the render client swaps them on the existing prefers-color-scheme listener. Browser-free exports (SVG, native PNG) and headless Chromium deterministically use the light variant — pass color_scheme="dark" to export dark. Documented.
  • Resolution order (later wins), per the issue and now documented in docs/styling/themes-and-tokens.md: engine defaults → preset → color scheme → palette/accent/contrast → named low-level args → theme(style={}) → chart style={} → explicit mark colors.
  • Validation & discovery: unknown preset/palette/scheme/contrast/accent values raise ValueError listing the valid options; xy.theme_presets() / xy.theme_palettes() enumerate the catalog. Matplotlib-compatible stylesheet names stay in xy.pyplot.style.
  • Presets always set root background in both schemes, preserving the data-xy-own-bg / VS Code white-card behavior.

Docs

  • "Built-in presets" section in Themes and Tokens (knobs, precedence, export behavior).
  • New Theme Preset Gallery page (docs/styling/theme-presets.md) rendering all five presets in light and dark, wired into the docs nav.

Testing

  • New tests/test_themes.py: catalog integrity (implemented-tokens-only, hex palettes), resolver purity/immutability, actionable validation errors, backward compat, full precedence chain, system dual-variant wire spec, palette→marks/categorical with explicit colors winning.
  • PNG/SVG export tests assert dark preset output and the deterministic light fallback for system.
  • Full suite: 1877 passed, 66 skipped (reflex-dependent skips); ruff check/format clean; node js/build.mjs reproduces the committed bundles byte-identically; scripts/render_smoke_nonumpy.py passes.
  • Browser-verified the live system toggle with a headless-Chromium probe (stubbed matchMedia): tokens flip light→dark→light on the change event with no errors and no MutationObserver feedback loop.

Closes ENG-10444.

xy.theme() gains preset/color_scheme/palette/accent/contrast on top of
the existing token escape hatches. Presets resolve Python-side into the
implemented --chart-* vocabulary and a default mark palette threaded
through Figure, so browser, standalone HTML, SVG, native PNG, and
Chromium exports all agree by construction. color_scheme="system" ships
both variants (dom.style + dom.styleDark) and the client swaps them on
prefers-color-scheme; browser-free exports deterministically use light.
Unknown names fail with actionable errors; xy.theme_presets() and
xy.theme_palettes() cover discovery. Includes docs, a preset gallery
page, and catalog/precedence/wire/export tests.
@linear-code

linear-code Bot commented Jul 20, 2026

Copy link
Copy Markdown

ENG-10444

@codspeed-hq

codspeed-hq Bot commented Jul 20, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 102 untouched benchmarks
⏩ 1 skipped benchmark1


Comparing farhan/eng-10444-add-high-level-themes-to-the-declarative-xy-api (685e395) with main (cb0c5a1)

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

@masenf masenf left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after rebasing on main, which had the light/dark on html root fix, we are rendering the chart area with the outer page's color mode class rather than the chart-specific one

in light mode:

Image

in dark mode:

Image

Scheme-pinned presets set the figure background inline but left
--chart-bg, --chart-legend-bg, and --chart-modebar-bg inheritable, so a
host page defining those tokens per color mode (the docs styling surface
and app-level chart style both do) repainted a pinned chart's plot rect
and legend/modebar chips to the page's mode — dark plot areas inside
pinned-light cards and unreadable legend text.

Every preset variant now pins all three surfaces with the engine's own
scheme defaults (plot rect transparent so the figure background shows
through), which changes nothing outside token-setting hosts. The SVG
exporter treats fully transparent background paints as omitted so
exports don't gain dead transparent rects.
@greptile-apps

greptile-apps Bot commented Jul 21, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds high-level theme presets to the declarative API. The main changes are:

  • Built-in presets, palettes, color schemes, accents, and contrast controls.
  • System light and dark token handling in browser charts.
  • Theme palettes shared across browser, SVG, and PNG rendering.
  • Public discovery helpers and type exports.
  • Theme documentation, gallery examples, and export tests.

Confidence Score: 5/5

This looks safe to merge.

No blocking issue was identified, and accepted accent values retain alpha in solid emphasis tokens.

None.

T-Rex T-Rex Logs

What T-Rex did

  • Validated visual changes across light and dark themes, confirming the background color transitions, CSS variable updates, and the presence of an 824×478 canvas, with no page or console errors; Chromium emitted only benign WebGL readback warnings.
  • Compared the before and after poster images to verify the dark system-theme variant applied the expected background and text color changes.
  • Inspected the test run logs to confirm there were no runtime errors during the proof-of-work validation.
  • Cataloged the captured videos and images to enable post-run inspection of render results against the proof.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
python/xy/_themes.py Defines the preset catalog and resolves high-level theme options into tokens and mark palettes.
python/xy/components.py Extends the theme API and applies resolved theme state while building charts.
python/xy/_figure.py Stores theme palette and scheme state and serializes system dark tokens.
js/src/50_chartview.ts Applies system color-scheme tokens during initial rendering and browser scheme changes.
python/xy/marks.py Uses the selected theme palette for default mark colors.

Reviews (6): Last reviewed commit: "docs: render a live example under each t..." | Re-trigger Greptile

Comment thread python/xy/components.py
Comment thread python/xy/_themes.py Outdated
…high-level-themes-to-the-declarative-xy-api

# Conflicts:
#	examples/demo-advance.ipynb
#	python/xy/_svg.py
_normalize_accent advertised #rrggbbaa in its error message and accepted
it, then truncated to the opaque base — so accent="#8b5cf680" rendered
selection, zoom, focus, and modebar-active at full opacity. The solid
tokens now keep the caller's alpha; the faint fills still derive from
the opaque base so appending the fixed fill alpha stays a valid 8-digit
hex.
@FarhanAliRaza

FarhanAliRaza commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

Fixed
image

image

…high-level-themes-to-the-declarative-xy-api

# Conflicts:
#	examples/demo-advance.ipynb
#	python/xy/marks.py
#	python/xy/static/index.js
#	python/xy/static/standalone.js
The #159 vite-minified build mangles internal declarations, so grepping the
installed bundle for 'function renderStandalone(' has failed ever since when
reflex is installed. Assert on the public export names instead — they are the
stable identity of the real render client.
…high-level-themes-to-the-declarative-xy-api

# Conflicts:
#	examples/demo-advance.ipynb
#	python/xy/_payload.py
#	python/xy/marks.py
#	python/xy/static/index.js
#	python/xy/static/standalone.js
Comment thread python/xy/components.py
if theme_node.color_scheme is not None:
fig.color_scheme = theme_node.color_scheme
if theme_node.dark_style:
fig.dark_style.update(theme_node.dark_style)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Earlier Dark Tokens Still Survive

When a later system theme overrides a token, theme() removes that key from its dark_style, but this dict.update() cannot remove the value already stored by an earlier theme. For example, a later grid_color="#ff0000" applies in light mode but switches back to the earlier preset's grid color in dark mode, breaking component order and documented precedence.

Artifacts

Repro: Python declarative composition and serialized payload harness

  • Evidence file captured while the check ran.

Repro: Playwright system color-scheme transition harness

  • Evidence file captured while the check ran.

Repro: executed payload and browser transition output

  • The full command output behind this check.

Repro Earlier Dark Tokens Dark

  • What the screen looked like at this point in the check.

View artifacts

T-Rex Ran code and verified through T-Rex

…high-level-themes-to-the-declarative-xy-api

# Conflicts:
#	python/xy/static/index.js
#	python/xy/static/standalone.js
The preset gallery page authored only the all-presets comparison grid;
the per-preset sections were headings with a one-line description and
nothing to look at. Reuse the fence-shared preset_example helper via a
small preset_pair() and render each preset's light/dark pair under its
own heading.
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.

2 participants