Library / SDK
reflex-dev/xy avatar
reflex-dev/xy

reflex-dev/xy: a Python charting engine with a Rust core and a GPU client

Project brief: Ultra-fast and customizable Python charts. Customize every layer Use Python to control the chart, from marks and axes to interactions and layout.

1,861 stars78 forksPythonApache-2.0

At a glance

What is it?
XY is an alpha Python charting library that draws small charts point by point and switches to a screen-bounded density surface above 200k rows. Here is how it installs, what the architecture actually does, and where the alpha status bites.
Who is it for?
Adopt XY if you plot in Python and your data regularly runs past a few hundred thousand points, or if you need the same chart to appear in a notebook and in a Reflex app without rewriting it. Do not adopt it if you need a stable API surface, a complete matplotlib replacement, or a plotting library you can pin for years: the README states XY is in alpha, the version is 0.0.7, and the compatibility guide explicitly says not all charts and functionality are supported yet.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 7 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem XY targets: one chart library from notebook to app to export

Python plotting splits into two camps that do not talk to each other. Matplotlib gives you control and a huge surface area, but its interactive backends are slow once a series gets large. Browser-oriented libraries give you interactivity, but you write the chart in their model and then fight it when you want a custom layout or a static file. XY positions itself between them: a chart is a container plus the marks inside it, and the README states that the same object can render in notebooks, in web apps, or be exported as HTML, PNG, SVG or PDF.

The intended audience is Python users who want one library for everyday plots, custom application visuals and large datasets. The README's own framing is that you build a chart once and reuse it across those contexts. That is a narrower claim than "better than matplotlib at everything", and it is the claim worth testing.

The declared dependencies are small: numpy and anywidget. The Reflex integration is an optional extra. That matters because it means importing plain xy does not drag a web framework into a notebook kernel.

How the Rust core and the density surface actually work

The architecture is visible in three places in the repository: the Rust crate, the Python package, and the JavaScript client. Cargo.toml names the crate xy-core and describes it as a native compute core with zone maps, offset-encoded f32 and M4 decimation. It builds as a cdylib and an rlib, and the comment in that file states the core is exposed through a plain C ABI so that one cdylib per platform covers every CPython version. The Python side binds to it with ctypes, which is why the release profile keeps .dynsym and keeps panic unwinding: lib.rs catches panics at the C ABI boundary.

The data path is described in the README. For small charts, every point is sent to the browser. Above 200k rows the core computes only what the screen needs, based on its resolution, and draws a density surface instead of one marker per row. Pan, zoom, hover and selection re-run the same process for the new range, and a selection returns the original rows. So the aggregation is not a lossy dead end: drilling into a view gives you back the underlying data.

The client is a separate build. package.json is named xy-dev-tools, marked private, and describes itself as development-only dependencies for the vite/typescript client build toolchain and browser test runner, with Playwright, TypeScript and Vite as devDependencies. The pyproject description calls the whole thing an experimental Python charting engine with a native Rust core, binary columnar transport and a GPU render client. Binary columnar transport is the piece that keeps the Python-to-browser hop from being the bottleneck when you do send exact rows.

The trade-off is real. A screen-bounded density surface is fast because it throws away detail the screen cannot show. If your question is "what is the exact y value of point 4,201,887", the density view does not answer it until you zoom in far enough that the renderer drills back to real rows, which the README describes with the zoom_size_factor and zoom_opacity parameters on scatter.

Installing XY and drawing a first chart

The README gives two install paths. Either works; the uv form is the one the project itself uses in its Makefile, which creates a .venv and builds the native core.

bash
pip install xy

# or, with uv
uv add xy

A chart is a container plus the marks inside it, and the README notes any sequence works and NumPy is optional. The minimal example below wraps a line mark in a line_chart container. In a notebook the last expression renders the chart; the commented lines are the static export calls.

python
import xy

chart = xy.line_chart(xy.line([1, 2, 3, 4, 5], [120, 180, 165, 240, 310]))
# chart.to_html("chart.html")
# chart.to_png("chart.png")
# chart.to_svg("chart.svg")
chart  # notebooks render it

Styling goes through class_name and class_names, which take CSS or Tailwind classes. The README's styling example attaches a class to the chart container and a separate one to the tooltip, so the two can be themed independently.

python
chart = xy.line_chart(
    xy.line(x, y, color="#7c3aed", width=3),
    class_name="rounded-xl bg-white",
    class_names={"tooltip": "rounded-lg bg-zinc-900 text-white"},
)

If you already have matplotlib code, the migration path is an import swap. The README's compatibility example keeps the plotting calls unchanged and only replaces the pyplot import. It also links a compatibility guide and states plainly that not all charts and functionality are supported yet, so treat this as a starting point rather than a guarantee.

python
import numpy as np
import xy.pyplot as plt

x = np.linspace(0, 10, 200)
fig, ax = plt.subplots()
ax.plot(x, np.sin(x), "r--", label="signal")
ax.legend()
plt.show()

The repository ships an examples directory with a demo notebook, a matplotlib shim notebook, a dashboard, a FastAPI drilldown example, a Reflex example and an OSM example, which is the fastest way to see the API used on something larger than a toy.

The alpha label is the limitation that matters most

The README carries an explicit notice that XY is in alpha and is receiving frequent enhancements. The current release is v0.0.7, published on 2026-08-27, and the two releases before it were v0.0.7a1 and v0.0.7a2 in the same week. That cadence is normal for a young library and it is also the practical problem: a charting library that changes its API weekly is expensive to build a product on.

The compatibility shim is the second constraint. Importing xy.pyplot gets you the matplotlib call style, but the README directs you to a compatibility guide and says not all charts and functionality are supported. There is no list in the README of what is missing, so the only way to find out is to run your own plotting code through it.

The third is platform surface. Because the compute core is a compiled cdylib bound through ctypes, the wheel has to ship a binary for your platform and Python version. The Cargo comment explains the design goal is one cdylib per platform covering every CPython version, which reduces the matrix, but it does not eliminate it. If the native core is unavailable, the fast path is unavailable.

Finally, the benchmark numbers in the README are the project's own, produced by its own harness, and the README describes that harness in detail: every library gets every row, is driven through its own input path in a real browser, and the clock stops only when the canvas is correct and stable across ten byte-identical frames. That is a more honest methodology than most, and it is still the project grading its own homework. The numbers are a reason to try XY on your data, not a reason to believe a specific millisecond figure.

XY versus Plotly and matplotlib: different first move

The README's benchmark tables compare XY against Matplotlib through WebAgg and Plotly through scattergl, so those are the two reference points the project itself chooses.

The difference is the first move each library makes. Plotly builds a figure object and hands a serialized structure to the browser, which draws every trace. Matplotlib through WebAgg rasterizes server-side and ships images. XY decides at the data layer: below roughly 200k rows it sends points, above that it aggregates to a screen-bounded density surface and keeps the exact rows available for a drilldown. That is why the README can report XY holding near-flat time from 10k to 100M points while the exact-marker path, XY with density=False, grows with N, reaching 1.343 s at 100M.

The practical consequence is about what you lose. Plotly and matplotlib give you a marker per row at every size, so hover tooltips and per-point styling behave predictably. XY's density mode gives you a surface, and per-point identity only returns after zooming in. If your chart is 5,000 points and you need every one of them individually annotated, the density machinery is not doing anything for you and the plain path is what you get.

If you are already inside the Reflex ecosystem, the reflex extra pins reflex>=0.9.6 and the adapter source ships in every wheel, so the framework integration and render client cannot drift apart. That is a genuine reason to prefer XY there over wiring a separate JS charting library into a Reflex app by hand.

Licence, maintenance and what an upgrade costs you

XY is Apache-2.0, and both pyproject.toml and Cargo.toml declare that licence for the Python package and the Rust core respectively. Apache-2.0 is permissive and includes an explicit patent grant, which is usually what a company wants when it embeds a charting library in a product. The repository also carries a SECURITY.md. This is a description of the licence text, not legal advice; if you are redistributing a modified wheel, read the licence and the NOTICE handling yourself.

The last push to the repository was on 2026-08-27, the same day as the v0.0.7 release, so the project is not archived and the codebase is current as of that date. The README states XY is in alpha and receiving frequent enhancements, and the release history is consistent with that.

Upgrade cost is the part to budget for. There is a CHANGELOG.md, and the Makefile exposes a news and news-check target driven by a pinned release tool, which suggests the project tracks changes deliberately. But a 0.0.x version line means no compatibility promise. If you pin XY in a production dependency file, plan on reading the changelog before each bump and re-running your own charts, particularly if you depend on the pyplot shim, since the compatibility guide is the document most likely to lag the code.

Editorial conclusion

Adopt XY if you plot in Python and your data regularly runs past a few hundred thousand points, or if you need the same chart to appear in a notebook and in a Reflex app without rewriting it. Do not adopt it if you need a stable API surface, a complete matplotlib replacement, or a plotting library you can pin for years: the README states XY is in alpha, the version is 0.0.7, and the compatibility guide explicitly says not all charts and functionality are supported yet. Before committing, verify three things in your own environment: that the chart types you actually use exist in the capability matrix, that your matplotlib calls survive the pyplot shim, and that the wheel for your platform ships the compiled core, since a missing native build is the failure mode that no amount of Python-level testing will surface.

Frequently asked questions

What does "xy" stand for in Python and what is it used for?

In this project xy is the name of the charting library, not an abbreviation, and the README describes it as an extremely fast, interactive, customizable Python charting library for the web, notebooks and static exports. It is used to build a chart once and then render it in a notebook, in a web app, or as an HTML, PNG, SVG or PDF file. The package imports as xy and the matplotlib-style entry point is xy.pyplot.

Which Python library is the best for plotting?

The README does not rank plotting libraries generally, and this article will not either. What the XY README does provide is a comparison on one axis: it benchmarks XY against Matplotlib through WebAgg and Plotly through scattergl, and states that XY holds near-flat render time from 10k to 100M points because above 200k rows it draws a screen-bounded density surface instead of one marker per row. Those are the project's own measurements from its own harness.

How do I create an XY plot in Python?

Install the package with pip install xy or uv add xy, then compose a container with the marks inside it. The README's example is xy.line_chart(xy.line([1, 2, 3, 4, 5], [120, 180, 165, 240, 310])), which renders directly in a notebook and can be written out with chart.to_html, chart.to_png or chart.to_svg. If you already have matplotlib code, importing xy.pyplot as plt lets you keep the plotting calls unchanged, though the README notes not all functionality is supported yet.

What is a reflex app and how does XY relate to it?

The README does not define what a Reflex app is beyond the framework relationship. What the repository does show is that XY ships a reflex optional extra pinning reflex>=0.9.6, and the pyproject comment states the adapter source ships in every xy wheel so the framework integration and render client can never drift. Importing plain xy remains framework-free, so the Reflex dependency is only pulled in when you select that extra.

Official sources

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/reflex-dev-xy.svg)](https://hysenlabs.com/projects/reflex-dev-xy)