Skip to main content

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.

How it works

  • Control plane (Reflex-native). The only chart state is a token string, minted by a @reflex_xy.figure computed var. Semantic events — on_point_hover(event), on_point_click(event), on_select_end(event), and on_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).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

reflex_xy-0.0.2.tar.gz (156.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

reflex_xy-0.0.2-py3-none-any.whl (44.0 kB view details)

Uploaded Python 3

File details

Details for the file reflex_xy-0.0.2.tar.gz.

File metadata

  • Download URL: reflex_xy-0.0.2.tar.gz
  • Upload date:
  • Size: 156.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for reflex_xy-0.0.2.tar.gz
Algorithm Hash digest
SHA256 fd82fb8140128052b70a1aeabb59d7b2b5a11b3de66551e99224892a42d818e5
MD5 5507a0b84cd848041084d1f1b39e0a21
BLAKE2b-256 4a020ff0758650f4e69fc8fe58c0b123bfe08e7167a0df9845e61be456d9d5d0

See more details on using hashes here.

Provenance

The following attestation bundles were made for reflex_xy-0.0.2.tar.gz:

Publisher: release-reflex-xy.yml on reflex-dev/xy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file reflex_xy-0.0.2-py3-none-any.whl.

File metadata

  • Download URL: reflex_xy-0.0.2-py3-none-any.whl
  • Upload date:
  • Size: 44.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for reflex_xy-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 55af2692066b2b8137cbe8699e815521fe414dbe4388dd98eddba29d4adfc4e5
MD5 4edbd228e45a170af2ffee1587643641
BLAKE2b-256 c43e95ae05b62b1fd299b139ead9f466bcfc9ccb8065f166d70ed670cca3f33f

See more details on using hashes here.

Provenance

The following attestation bundles were made for reflex_xy-0.0.2-py3-none-any.whl:

Publisher: release-reflex-xy.yml on reflex-dev/xy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page