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.
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()
Tailwind classes on live charts
Add Reflex's Tailwind plugin when chart chrome uses utility classes. A direct
fixed xy.Chart is scanned automatically. A token or Var is resolved only at
runtime, so expose its possible complete utility names through the adapter:
# rxconfig.py
config = rx.Config(
app_name="dash",
plugins=[
# Keep dark: utilities synchronized with Reflex's .dark color mode.
rx.plugins.TailwindV4Plugin(config={"darkMode": "selector"}),
reflex_xy.XYPlugin(),
],
)
# dash.py
LIVE_CHART_CLASSES = [
"rounded-2xl border border-slate-200 dark:border-slate-800",
"bg-white text-slate-900 dark:bg-slate-950 dark:text-slate-100",
]
reflex_xy.chart(
Dash.chart,
tailwind_classes=LIVE_CHART_CLASSES,
)
The inventory is present only in generated source for Tailwind's build-time scan. It never reaches the DOM; the runtime chart receives the same class names from its XY payload. Use a string or ordered iterable (not a mapping or set), and include every complete class that any state-driven version of the figure can emit. Quotes, escaped arbitrary-selector underscores, and Unicode content are preserved verbatim:
LIVE_CHART_CLASSES = [
"before:content-['✓']",
r"[&_[data-xy-slot=legend\_label]]:font-semibold",
]
When a recomputed live figure changes root or slot classes, the browser rebuilds its DOM chrome so the new classes take effect without changing the figure token. Every named-axis range and durable box/range/lasso geometry survives that rebuild without replaying semantic callbacks.
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),
)
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).
Metadata
Release files for reflex-xy 0.0.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| reflex_xy-0.0.2.tar.gz | 156.1 kB | With provenance | Details for reflex_xy-0.0.2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| reflex_xy-0.0.2-py3-none-any.whl | Python 3 | none | any | Details for reflex_xy-0.0.2-py3-none-any.whl |
Total release size: 200.1 kB
Release files / reflex_xy-0.0.2.tar.gz
| Download URL | reflex_xy-0.0.2.tar.gz |
|---|---|
| Size | 156.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fd82fb8140128052b70a1aeabb59d7b2b5a11b3de66551e99224892a42d818e5
|
|
BLAKE2b-256 checksum How to use checksums |
4a020ff0758650f4e69fc8fe58c0b123bfe08e7167a0df9845e61be456d9d5d0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 28, 2026.
Transparency logRelease files / reflex_xy-0.0.2-py3-none-any.whl
| Download URL | reflex_xy-0.0.2-py3-none-any.whl |
|---|---|
| Size | 44.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
55af2692066b2b8137cbe8699e815521fe414dbe4388dd98eddba29d4adfc4e5
|
|
BLAKE2b-256 checksum How to use checksums |
c43e95ae05b62b1fd299b139ead9f466bcfc9ccb8065f166d70ed670cca3f33f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 28, 2026.
Transparency logSecurity for reflex-xy 0.0.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for reflex_xy-0.0.2.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 28, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.