Choosing a Runtime and Deployment Mode
If you are building an application, the runtime is Reflex. XY does not ship a second application runtime and does not embed into another web framework. The choice you actually make is inside Reflex: which data tier a chart uses.
Everything else here — notebooks, standalone HTML, PNG, SVG — is not an application runtime. Those are exploration and output.
Applications run on Reflex
XY charts are first-class Reflex components in an all-Python app. Reflex owns UI, state, events, and deployment; XY owns transport, rendering, and interaction math.
- Compose XY charts with normal Reflex components in Python.
- Drive charts from per-session state with
@reflex_xy.figure. - Connect hover, click, selection, and view changes to Reflex event handlers,
then stream updates with
reflex_xy.append. - Reuse the app's existing websocket and XY's binary data plane instead of deploying a separate chart service.
The integration is experimental and ships inside the published xy
distribution. Install xy[reflex] so the environment also selects a supported
Reflex version, then import it as reflex_xy. See
Deployment Recipes.
Choose a data tier
The chart definition is identical in every tier. What changes is where its data comes from, and therefore what the chart can still do after you deploy.
Browser-local means hover, pan, zoom, and selection still work in the page; they just cannot reach Python.
Start at the top. A fixed xy.Chart becomes a content-addressed asset that
needs no backend connection and survives reflex export. Move down only when
the chart must answer with data it did not ship with — a drill-down, a fresh
query, the exact rows behind a lasso.
Despite its name, inline() is the live, kernel-backed fixed-data tier; passing
a Chart directly is the static tier. Call inline() at module scope so every
backend worker registers the same content-addressed token.
There is also reflex_xy.register(), an imperative dev-tier escape hatch. Its
figure lives only in the registering process and cannot be rebuilt after a
worker restart or on another node, so do not deploy it.
Moving down a tier is a wrapper change
The chart definition does not change; only its source does.
# Fixed asset tier
def page():
return rx.vstack(
reflex_xy.chart(
xy.scatter_chart(xy.scatter(x="orders", y="revenue", data=daily))
)
)
# Session-backed tier — same chart, now driven by state
class Dashboard(rx.State):
channel: str = "web"
@reflex_xy.figure
def revenue(self) -> xy.Chart:
rows = daily[daily.channel == self.channel]
return xy.scatter_chart(xy.scatter(x="orders", y="revenue", data=rows))
def page():
return rx.vstack(reflex_xy.chart(Dashboard.revenue))ExpandCollapse
See the Reflex integration guide for component props, event payloads, and the API boundary.
Outside an application
Not every chart belongs in an app. The same definition runs in a notebook and exports to files.
chart = xy.scatter_chart(xy.scatter(x="orders", y="revenue", data=daily))
chart.show() # notebook widget — live Python, dies with the kernel
chart.to_html("out.html") # one portable interactive file, no server
chart.to_png("out.png") # static raster
chart.to_svg("out.svg") # static vectorFor formats beyond PNG and SVG — JPEG, WebP, PDF, and batch write_images —
and for the native-versus-Chromium engine choice, see
Display and Export.
Know what each choice costs you
Full list: Limitations and Alpha Status.
Where to go next
- Applications → Reflex integration
- Exploration → Notebooks
- Files and static output → Display and Export
- Hosting, CSP, offline → Serving, CSP, and offline use