Skip to content

Data-bound chart components 5/7: the chart factories - #465

Open
FarhanAliRaza wants to merge 1 commit into
stack/4-composite-servingfrom
stack/5-chart-factories
Open

Data-bound chart components 5/7: the chart factories#465
FarhanAliRaza wants to merge 1 commit into
stack/4-composite-servingfrom
stack/5-chart-factories

Conversation

@FarhanAliRaza

@FarhanAliRaza FarhanAliRaza commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Stacked on #464. Base is stack/4-composite-serving. This is the headline PR — the user-facing surface.

class Dash(rx.State):
    @reflex_xy.data
    def cloud(self) -> CloudData: ...

def index():
    return reflex_xy.scatter_chart(data=Dash.cloud, x="x", y="y",
                                   color="mag", colormap="viridis", height="460px")

What the compile now catches

A factory call at page evaluation builds a plan, so structure is validated at reflex run rather than at hydrate. In the order a user hits it:

  • a hallucinated factory name fails at import (the xy node re-exports are an explicit curated map, not getattr passthrough);
  • an unknown kwarg fails with a did-you-mean;
  • a bad colormap, enum, or axis ref fails in the zero-row probe;
  • an unknown column name fails against the data var's TypedDict, without executing the data method;
  • the wrong var or a raw string in data= fails on the typed prop.

The kwarg partition

Derived from inspect.signature at import, not hand-listed. A hand-listed partition silently drifts from xy's signatures, and Reflex absorbs unknown non-event kwargs into style — so a typo would vanish rather than raise (the hazard PR1 pinned). Collisions get generated aliases (mark_<name>, with widthstroke_width where the mark hasn't claimed it), pinned by test.

Two mounts, one surface

A Var data source becomes the plan/data props the wrapper composes into a composite subscription. A concrete mapping binds immediately and routes to the static payload-asset path — same validation, works under reflex export.

Excluded kinds (recorded decision)

Aggregating marks (box, violin, hexbin, contour, heatmap, stairs, ecdf) and the data-taking composites (pie, radar, wind_rose, sankey) are refused by name, pointing at the two routes that work. Their validators need real values, and a synthetic-row probe would validate against made-up data — a silent decimation of the compile guarantee.

Page-plan registration

_ensure_page_plans lands here rather than in PR4, with the factories that populate the map: backend-only workers import the app without evaluating pages, so the startup lifespan evaluates them once, making "the plan map is populated in every worker" true by construction instead of an assumption about Reflex.

Spec

reflex-integration.md §3.6 (factories, static symmetry, kind coverage, page-plan registration), file map.

Test plan

  • uv run pytest tests/reflex_adapter tests/test_validation_timing.py — 235 passed
  • pre-commit run --all-files, ruff check, ruff format --check, ty check — clean

Review in cubic

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7e613212-77c3-4b1d-9739-18772cff454b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Aug 5, 2026

Copy link
Copy Markdown

Greptile Summary

The PR introduces the user-facing data-bound chart factory API and registers chart plans in backend-only workers.

  • Adds flat per-kind factories and composed multi-mark chart construction.
  • Supports live DataHandle sources and static mapping-backed payload assets.
  • Extends the Reflex component and JSX wrapper with plan/data subscriptions.
  • Evaluates unevaluated pages during worker startup to populate process-local plans.
  • Adds factory, validation, dispatch, static-tier, and page-registration tests.

Confidence Score: 5/5

The PR appears safe to merge based on the eligible follow-up review findings.

No blocking failure remains in the available follow-up review scope.

Important Files Changed

Filename Overview
python/reflex_xy/factories.py Adds signature-derived factory dispatch, compile-time schema and plan validation, and live/static mounting.
python/reflex_xy/app.py Adds worker-startup evaluation of page component functions so process-local chart plans are registered.
python/reflex_xy/assets/XYChart.jsx Adds client-side plan/data token composition and bounds repeated resynchronization attempts.
python/reflex_xy/component.py Extends the private Reflex component with typed plan-tier data props.
python/reflex_xy/init.py Exposes the chart factories and a curated set of xy node constructors.
tests/reflex_adapter/test_factories.py Covers factory partitioning, validation, mounting, public exports, and supported chart kinds.
tests/reflex_adapter/test_page_plan_registration.py Covers backend-worker plan registration and isolation of failing page evaluations.
spec/design/reflex-integration.md Documents the factory surface, static symmetry, supported kinds, and worker registration lifecycle.

Reviews (2): Last reviewed commit: "feat(reflex): data-bound chart factories" | Re-trigger Greptile

@codspeed-hq

codspeed-hq Bot commented Aug 5, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 109 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing stack/5-chart-factories (6301617) with stack/4-composite-serving (25a9fdc)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@cubic-dev-ai cubic-dev-ai Bot 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.

3 issues found across 8 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="python/reflex_xy/__init__.py">

<violation number="1" location="python/reflex_xy/__init__.py:50">
P3: The new quickstart multi-mark example references a column (`"t"`) that is not defined in the preceding `CloudData`/`Dash.cloud` example, so users copying this snippet will hit a validation error immediately. Using an existing field (for example `"y"` or `"mag"`) keeps the example runnable and aligned with the documented data contract.</violation>
</file>

<file name="python/reflex_xy/assets/XYChart.jsx">

<violation number="1" location="python/reflex_xy/assets/XYChart.jsx:916">
P2: Live charts can get stuck unsubscribed after repeated transient `err{resync:true}` responses, because retries are permanently disabled after 5 attempts unless a payload is successfully applied. Consider resetting or decaying the retry budget (or using backoff) so temporary backend recovery can resume without requiring remount/disconnect.</violation>
</file>

<file name="python/reflex_xy/factories.py">

<violation number="1" location="python/reflex_xy/factories.py:82">
P3: `stem_chart` is implemented and registered but omitted from `__all__`, so exported API surfaces can miss this factory even though it is supported internally. Adding it to `__all__` keeps the public factory set consistent.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

if (destroyed || !data || data.fig !== liveToken) return;
console.warn(`xy: ${data.error} (fig ${data.fig})`);
if (data.resync === true && socket.connected) subscribe();
if (data.resync === true && socket.connected && errResyncs < 5) {

@cubic-dev-ai cubic-dev-ai Bot Aug 5, 2026

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.

P2: Live charts can get stuck unsubscribed after repeated transient err{resync:true} responses, because retries are permanently disabled after 5 attempts unless a payload is successfully applied. Consider resetting or decaying the retry budget (or using backoff) so temporary backend recovery can resume without requiring remount/disconnect.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At python/reflex_xy/assets/XYChart.jsx, line 916:

<comment>Live charts can get stuck unsubscribed after repeated transient `err{resync:true}` responses, because retries are permanently disabled after 5 attempts unless a payload is successfully applied. Consider resetting or decaying the retry budget (or using backoff) so temporary backend recovery can resume without requiring remount/disconnect.</comment>

<file context>
@@ -899,7 +913,10 @@ export function XYChart(props) {
       if (destroyed || !data || data.fig !== liveToken) return;
       console.warn(`xy: ${data.error} (fig ${data.fig})`);
-      if (data.resync === true && socket.connected) subscribe();
+      if (data.resync === true && socket.connected && errResyncs < 5) {
+        errResyncs += 1;
+        subscribe();
</file context>
Fix with cubic

app = rx.App()

Multi-mark charts compose xy nodes around the same data var
(``reflex_xy.chart(reflex_xy.scatter("x", "y"), reflex_xy.line("x", "t"),

@cubic-dev-ai cubic-dev-ai Bot Aug 5, 2026

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.

P3: The new quickstart multi-mark example references a column ("t") that is not defined in the preceding CloudData/Dash.cloud example, so users copying this snippet will hit a validation error immediately. Using an existing field (for example "y" or "mag") keeps the example runnable and aligned with the documented data contract.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At python/reflex_xy/__init__.py, line 50:

<comment>The new quickstart multi-mark example references a column (`"t"`) that is not defined in the preceding `CloudData`/`Dash.cloud` example, so users copying this snippet will hit a validation error immediately. Using an existing field (for example `"y"` or `"mag"`) keeps the example runnable and aligned with the documented data contract.</comment>

<file context>
@@ -3,38 +3,55 @@
     app = rx.App()
+
+Multi-mark charts compose xy nodes around the same data var
+(``reflex_xy.chart(reflex_xy.scatter("x", "y"), reflex_xy.line("x", "t"),
+data=Dash.cloud)``), and charts whose *structure* depends on state keep the
+escape hatch: an ``@reflex_xy.figure`` method returning an ``xy.Chart``,
</file context>
Suggested change
(``reflex_xy.chart(reflex_xy.scatter("x", "y"), reflex_xy.line("x", "t"),
(``reflex_xy.chart(reflex_xy.scatter("x", "y"), reflex_xy.line("x", "y"),
Fix with cubic

"line_chart",
"scatter_chart",
"segments_chart",
"step_chart",

@cubic-dev-ai cubic-dev-ai Bot Aug 5, 2026

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.

P3: stem_chart is implemented and registered but omitted from __all__, so exported API surfaces can miss this factory even though it is supported internally. Adding it to __all__ keeps the public factory set consistent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At python/reflex_xy/factories.py, line 82:

<comment>`stem_chart` is implemented and registered but omitted from `__all__`, so exported API surfaces can miss this factory even though it is supported internally. Adding it to `__all__` keeps the public factory set consistent.</comment>

<file context>
@@ -0,0 +1,489 @@
+    "line_chart",
+    "scatter_chart",
+    "segments_chart",
+    "step_chart",
+]
+
</file context>
Fix with cubic

The user-facing half of the tier: reflex_xy.scatter_chart(data=Dash.cloud,
x="x", y="y", color="mag") and the composed reflex_xy.chart(*nodes,
data=...) for multi-mark charts.

A factory call at page evaluation builds a plan, so the chart's structure
is validated at `reflex run` rather than at hydrate. What that buys, in the
order a user hits it: a hallucinated factory name fails at import (the xy
node re-exports are an explicit curated map, not getattr passthrough); an
unknown kwarg fails with a did-you-mean; a bad colormap, enum, or axis ref
fails in the zero-row probe; an unknown column name fails against the data
var's TypedDict without executing the data method; and the wrong var or a
raw string in data= fails on the typed prop.

The kwarg partition — mark options vs chrome vs component props vs event
handlers — is derived from inspect.signature at import rather than
hand-listed, because a hand-listed partition silently drifts from xy's
signatures and Reflex absorbs unknown non-event kwargs into `style` where
a typo would vanish rather than raise (the hazard Phase 0 pinned).
Collisions get generated aliases, pinned by test.

Two mounts from one surface: a Var data source becomes the plan/data props
the wrapper composes into a composite subscription, while a concrete
mapping binds immediately and routes to the static payload-asset path —
same validation, works under `reflex export`.

Aggregating kinds (box, violin, hexbin, contour, heatmap, stairs, ecdf) and
the data-taking composites (pie, radar, wind_rose, sankey) are excluded
from the plan tier and refused by name with the two working routes: their
validators need real values, and a synthetic-row probe would validate
against made-up data — a silent decimation of the compile guarantee.

Page-plan registration lands here too, with the factories that populate the
map: backend-only workers import the app without evaluating pages, so the
startup lifespan evaluates them once, making "the plan map is populated in
every worker" true by construction instead of an assumption about Reflex.

Spec: reflex-integration.md §3.6 (factories, static symmetry, kind
coverage, page-plan registration), file map.
@FarhanAliRaza
FarhanAliRaza force-pushed the stack/5-chart-factories branch from 1a9158c to 6301617 Compare August 5, 2026 14:51
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.

1 participant