Reflex integration for xy: WebGL charts over the app's own websocket
Project description
reflex-xy
xy figures as first-class Reflex components: WebGL rendering, million-point interactivity, and streaming updates — with chart data riding the app's existing websocket, not a sidecar API.
Status: prototype implementing spec/design/reflex-integration.md.
How it works
- Control plane (Reflex-native). The only chart state is a token string,
minted by a
@reflex_xy.figurecomputed var. Semantic events —on_point_hover(event),on_point_click(event),on_select_end(event), andon_view_change(event)— arrive as ordinary Reflex event handlers with small JSON payloads. - Data plane (xy-native). A second socket.io namespace (
/_xy) multiplexed onto the app's own engine.io websocket ships the spec as JSON and every data column as a binary frame (no JSON numbers, no base64, no extra endpoints to reverse-proxy). Pan/zoom/hover round-trips go straight to the figure kernel and never touch Reflex state. - No figure server. Figures live in a per-process registry as
rebuildable caches: the token encodes
(client, state, var), so any backend worker can re-run the builder against Reflex state (already distributed via redis in prod) when a reconnect lands on it.
Usage
# rxconfig.py
import reflex as rx
import reflex_xy
config = rx.Config(app_name="dash", plugins=[reflex_xy.XYPlugin()])
# dash/dash.py
import numpy as np
import reflex as rx
import xy
import reflex_xy
class Dash(rx.State):
points: int = 200_000
hovered: dict = {}
@reflex_xy.figure
def chart(self) -> xy.Chart:
rng = np.random.default_rng(7)
xs = rng.normal(size=self.points)
ys = xs * 0.6 + rng.normal(scale=0.6, size=self.points)
return xy.scatter_chart(xy.scatter(xs, ys), width="100%", height=460)
@rx.event
def on_hover(self, row: dict):
self.hovered = row
def index() -> rx.Component:
return rx.vstack(
reflex_xy.chart(Dash.chart, on_point_hover=Dash.on_hover, height="460px"),
rx.text(Dash.hovered.to_string()),
width="100%",
)
app = rx.App()
Change points in an event handler and the chart re-publishes itself to
every subscriber — the token never changes, so nothing re-renders except
pixels.
Builders can be async def (they become reflex AsyncComputedVars, same
rule as rx.var) — await a database, HTTP endpoint, or dataframe store:
@reflex_xy.figure
async def remote(self) -> xy.Chart:
rows = await fetch_rows(self.query)
return xy.line_chart(xy.line(rows.t, rows.value))
Streaming: reflex_xy.append(token, x=[...], y=[...]) from any handler or
background task pushes an incremental update over the same socket.
Events and cross-filtering
Live token-backed charts emit versioned, bounded dictionaries with a stable
token and canonical row IDs. Selection rows can update ordinary State, which
causes dependent @reflex_xy.figure builders to republish without changing
their tokens or remounting the chart:
class Dash(rx.State):
selected_groups: list[str] = []
@rx.event
def filter_groups(self, event: dict):
selection = event["selection"]
self.selected_groups = [] if selection["cleared"] else sorted({
row["color_category"] for row in selection["rows"]
})
def index():
return rx.grid(
reflex_xy.chart(Dash.groups, on_select_end=Dash.filter_groups),
reflex_xy.chart(Dash.filtered_detail),
)
Selection JSON is capped and reports total_count plus truncated; call
reflex_xy.resolve_selection(event) in the handler when all canonical rows
are required. Hover and view changes are latest-wins throttled (view changes
stream live during a gesture and always end on a final-phase event), and
view/selection state survives dependent republishes without feedback events.
The complete envelopes, limits, clear/shared-handler/viewport examples, and
static-versus-live availability are documented in
docs/engineering/design/reflex-integration.md.
Fixed-data charts
For a chart that doesn't depend on state, skip the state var entirely — pass the Chart straight in:
def index() -> rx.Component:
return reflex_xy.chart(
xy.line_chart(xy.line(t, np.sin(t)), width="100%", height=220),
height="220px",
)
That compiles the figure to a content-addressed binary asset at page build
and renders it with zero backend involvement — client-side hover,
pan/zoom, and density re-bin, same as Figure.to_html() exports; works
under reflex export. When a fixed chart still needs kernel round-trips
(deep drilldown into millions of points), register it once at module scope
instead:
cloud = reflex_xy.inline(xy.scatter_chart(xy.scatter(x, y))) # module scope
def index() -> rx.Component:
return reflex_xy.chart(cloud, height="460px")
inline() tokens are content-addressed, so every backend worker derives
the same one — no state, no coordination. The escalation path is:
direct Chart (static) → inline() (fixed data, live kernel) →
@reflex_xy.figure (per-session, state-driven).
Demo
The repository's examples/reflex/ is a runnable
showcase of every linking method: a drillable figure var with hover / click /
box-select events, a histogram driven by a slider and cross-filtered by the
selection, a streaming line, a detail chart recomputed from on_view_change,
and the two fixed-data tiers (a direct xy.Chart and an inline() token).
Each section shows its own source, read live with inspect.getsource.
For serving the same charts without Reflex, examples/fastapi/
renders standalone HTML and a live 100M-point drilldown from a plain FastAPI app.
Versioning & releases
reflex-xy versions independently of xy: the distribution version is
derived from reflex-xy-vX.Y.Z git tags (the core uses bare vX.Y.Z), and
releases publish from the repository's release-reflex-xy.yml workflow after
a dated entry lands in this package's CHANGELOG. Builds
between tags carry a .devN+<commit> version that PyPI rejects by design.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file reflex_xy-0.0.1a1.tar.gz.
File metadata
- Download URL: reflex_xy-0.0.1a1.tar.gz
- Upload date:
- Size: 152.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3f66afacedcdb156d18adba90db2adcaa60ec725af5af38201f16e32c5f8e615
|
|
| MD5 |
fc951e71153fb3f31144496f3f0d1be4
|
|
| BLAKE2b-256 |
68557effa36f962c059373362f772cd014462cea0c832bf083400048431e7015
|
Provenance
The following attestation bundles were made for reflex_xy-0.0.1a1.tar.gz:
Publisher:
release-reflex-xy.yml on reflex-dev/xy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
reflex_xy-0.0.1a1.tar.gz -
Subject digest:
3f66afacedcdb156d18adba90db2adcaa60ec725af5af38201f16e32c5f8e615 - Sigstore transparency entry: 2241523697
- Sigstore integration time:
-
Permalink:
reflex-dev/xy@9c4eaece351d7772e25baa76d0f8df67497384a6 -
Branch / Tag:
refs/tags/reflex-xy-v0.0.1a1 - Owner: https://github.com/reflex-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-reflex-xy.yml@9c4eaece351d7772e25baa76d0f8df67497384a6 -
Trigger Event:
push
-
Statement type:
File details
Details for the file reflex_xy-0.0.1a1-py3-none-any.whl.
File metadata
- Download URL: reflex_xy-0.0.1a1-py3-none-any.whl
- Upload date:
- Size: 41.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
054e75ec954a2ea1064baec2f7657c2d1c6189aec7cc50111595b7363fafbb0f
|
|
| MD5 |
021c90c209af0b81615a6e48e33b8d95
|
|
| BLAKE2b-256 |
eeef13fec87d4859b3c4cb68c1a7d4a8a55ad7d25ce8f617fc3a24abd1f75e32
|
Provenance
The following attestation bundles were made for reflex_xy-0.0.1a1-py3-none-any.whl:
Publisher:
release-reflex-xy.yml on reflex-dev/xy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
reflex_xy-0.0.1a1-py3-none-any.whl -
Subject digest:
054e75ec954a2ea1064baec2f7657c2d1c6189aec7cc50111595b7363fafbb0f - Sigstore transparency entry: 2241523889
- Sigstore integration time:
-
Permalink:
reflex-dev/xy@9c4eaece351d7772e25baa76d0f8df67497384a6 -
Branch / Tag:
refs/tags/reflex-xy-v0.0.1a1 - Owner: https://github.com/reflex-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-reflex-xy.yml@9c4eaece351d7772e25baa76d0f8df67497384a6 -
Trigger Event:
push
-
Statement type: