Show HN: XY – Fast, composable, GPU-accelerated charts, written in Rust

Jul 28, 2026 10:54 PM - 2 hours ago 62

XY-shaped probability section shown arsenic a binned scatter chart.

CI CodSpeed Python 3.11+ Docs

XY is an highly fast, interactive, customizable Python charting room for the web, notebooks, and fixed exports.

Charts are composed declaratively aliases done matplotlib conventions. You can fully customize them pinch Python, CSS, aliases Tailwind.

With mini charts, each constituent is sent to the browser. For ample charts, the Rust halfway computes only what the surface needs to display, based connected its resolution. Pan, zoom, hover, and action tin show afloat specifications by moving the same process for the caller range, and a action returns the original rows.

With XY we rendered the entirety of OpenStreetMap — a 10,000,000,000 point dataset. See the illustration →

Important

XY is successful alpha and is receiving predominant enhancements. ⭐️ Star the repo to travel the progress.

XY is for Python users who want 1 elastic charting room for everything from mundane land to civilization exertion visuals and ample datasets. Build a chart once, past usage it successful notebooks and web apps aliases export it arsenic HTML, PNG, SVG, aliases PDF.

pip instal xy # or, pinch uv uv adhd xy

A floor plan is simply a instrumentality positive the marks wrong it. Any series works; NumPy is optional.

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

The aforesaid API scales to a 100 cardinal points arsenic a density surface:

A hundred-million-point spiral rendered arsenic a density surface, past zoomed until the aboveground resolves into individual points.

import numpy as np import xy rng = np.random.default_rng(7) n = 100_000_000 r = 6.0 * rng.beta(1.2, 3.0, n) theta = 2.9 * np.log1p(r) + rng.integers(0, 4, n) * (np.pi / 2) + rng.normal(0, 0.045 + 0.016 * r, n) chart = xy.scatter_chart( xy.scatter( r * np.cos(theta), r * np.sin(theta), color=np.exp(-r / 2.2), colormap="magma_r", density=True, opacity=0.85, # Grow and solidify markers erstwhile a position drills done to existent rows. size=2.5, zoom_size_factor=2.6, zoom_opacity=0.95, ), xy.theme( background="#ffffff", plot_background="#ffffff", grid_color="#e6e6e1", axis_color="#c3c2b7", text_color="#0b0b0b", ), title="100 cardinal points", ) chart

For communal pyplot workflows, alteration the import and support the plotting code:

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()

See the compatibility guide; not each charts and functionality are supported yet.

Use Python to power the chart, from marks and axes to interactions and layout.

  • Marks: Control color, size, opacity, symbols, gradients, strokes, curves, and colormaps.
  • Guides: Customize axes, ticks, grids, annotations, legends, colorbars, and tooltips.
  • Interaction: Add pan, zoom, hover, selections, crosshairs, callbacks, and linked charts.
  • Layout: Create layers and facets, group responsive dimensions, and apply themes.
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"}, )

See the styling guide for examples. For a elaborate breakdown of what tin beryllium customized, spot the capability matrix.

Live interactive charts, 10k to 100M points. Every room gets each statement and is driven done its ain input way successful a existent browser. The timepiece stops only when the canvas is some correct (planted sentinel points verified lit) and stable (10 byte-identical frames), truthful progressive renderers are charged until their past chunk lands.

Time until each constituent is connected screen, 10k to 100M points, for XY, Matplotlib, and Plotly. Lower is better.

XY holds 0.071 s astatine 10k and 0.081 s astatine 100M, level crossed 4 orders of magnitude, because supra 200k rows it draws a screen-bounded density surface instead of 1 marker per row, and zoom drills backmost to nonstop rows. Every exact-marker way scales pinch N instead: Matplotlib crosses a 2nd astatine ~3M and reaches 13.4 s astatine 50M; Plotly crosses astatine ~2.5M and reaches 9.8 s astatine 25M.

The ray statement is XY pinch density=False: the aforesaid motor drafting 1 marker per row, nary aggregation credit. It renders 100M nonstop markers successful 1.34 s on 5.26 GiB.

Time until each constituent is connected screen, successful seconds. ✕ is simply a size the library did not render: Plotly ne'er finishes constructing the fig astatine 50M, and Matplotlib draws astatine 100M but ne'er resolves the zoom that follows.

Points 10k 100k 500k 1M 2.5M 5M 10M 25M 50M 100M
XY speedup 16× 34× 89× 177×
XY 0.071 0.072 0.075 0.084 0.083 0.089 0.083 0.077 0.076 0.081
XY (density=False) 0.085 0.074 0.087 0.098 0.111 0.144 0.206 0.424 0.645 1.343
Matplotlib (WebAgg) 0.086 0.115 0.224 0.357 0.758 1.424 2.804 6.838 13.385
Plotly (scattergl) 0.341 0.373 0.477 0.614 1.033 1.785 3.367 9.794

Peak Python-side resident memory, successful GiB. Browser representation is tracked separately and excluded here, since a headless Chrome resides ~1 GiB earlier drawing anything.

Points 10k 100k 500k 1M 2.5M 5M 10M 25M 50M 100M
XY advantage 1.8× 1.7× 1.9× 2.1× 2.1× 2.4× 2.6× 2.9× 2.8×
XY 0.05 0.05 0.06 0.07 0.13 0.19 0.32 0.70 1.36 2.58
XY (density=False) 0.05 0.05 0.07 0.10 0.18 0.31 0.57 1.35 2.66 5.26
Matplotlib (WebAgg) 0.09 0.09 0.12 0.15 0.28 0.46 0.84 2.06 3.85
Plotly (scattergl) 0.21 0.18 0.28 0.36 0.60 1.05 1.86 4.70

One instrumentality (Apple M5 Pro), 1 tally per cell; astatine the mini extremity the timings carry astir ±10 sclerosis of run-to-run spread.

For the environment, methodology, per-size videos, and earthy results, spot the benchmark runbook and competitive benchmark specification.

The reflex-xy adapter turns immoderate XY floor plan into a regular Reflex component, with no JavaScript, iframe, aliases abstracted floor plan service. It ships arsenic its ain package and pulls successful xy and reflex:

pip instal reflex-xy # or, pinch uv uv adhd reflex-xy

Register the adapter once:

# rxconfig.py import reflex as rx import reflex_xy config = rx.Config( app_name="dashboard", plugins=[reflex_xy.XYPlugin()], )

Then adhd a floor plan anyplace successful the constituent tree:

import reflex as rx import reflex_xy import xy signups = xy.line_chart( xy.line([1, 2, 3, 4, 5], [120, 180, 165, 240, 310]), title="Weekly signups", ) def index() -> rx.Component: return rx.card( rx.heading("Growth"), reflex_xy.chart(signups, height="320px"), width="100%", ) app = rx.App() app.add_page(index)

Hover, pan, and zoom support working. For charts driven by Reflex state, events, or live streams, spot the Reflex integration guide and the runnable illustration app.

Each notebook fetches its rows from the linked nationalist source; nary earthy datasets are stored successful this repository. Counts picture the featured chart, and the notebooks standard further. See the example guide for sources, workload controls, and setup.

Most floor plan stacks serialize each worth arsenic JSON and inquire the browser to draw every mark. XY keeps nonstop values successful a ColumnStore, computes a level of detail in Rust, and transfers typed binary buffers. Decimated and density views are bounded by the visible result.

flowchart TB API["Python API<br/>Build the chart"] STORE["ColumnStore<br/>Keep canonical f64 columns"] CORE["Native Rust compute<br/>Direct · decimated · density"] PAYLOAD["Compact payload<br/>Data-less JSON spec + typed binary buffers"] RENDER["Browser aliases notebook<br/>WebGL2 marks · Canvas axes · DOM interface"] API --> STORE --> CORE --> PAYLOAD --> RENDER
Loading

So a dense overview tin aggregate while a constrictive position returns nonstop points. With a unrecorded host, cookware and zoom petition a refined payload. Canonical f64 information stays in Python, truthful hover and action still return original rows.

For the afloat design, spot the design dossier.

Broad 2D sum first, past geographic, 3D, and measurement visualization. Queued next, nary dates implied:

  • Categorical distributions: strip, swarm, beeswarm, boxen, rug
  • Regression diagnostics: trendline, residual, QQ, PP
  • Scatter matrix and associated plots: SPLOM, brace grid, marginal histograms
  • Pie / donut: successful xy.pyplot today, promoting to xy.pie_chart(xy.pie(...))
  • Candlestick / OHLC and finance overlays: SMA, VWAP, Bollinger, RSI, MACD; prototyped, awaiting a caller landing
  • Waterfall and funnel
  • Treemap, sunburst, and icicle
  • Radar / polar and gauge: needs polar axes first
  • Slope, bump, and dumbbell
  • 3D and volume: scatter, surfaces, meshes, isosurfaces, and volumetric views

The afloat classed backlog is successful the chart roadmap. Want a floor plan aliases characteristic that isn't listed? Open an issue.

More