Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

XYG-shaped probability field shown as a binned scatter chart.

CI CodSpeed Python 3.11+ Documentation source Launch the examples on Binder

XYG is an extremely fast, interactive, customizable Python charting library for the web, notebooks, and static exports.

Charts are composed declaratively or through matplotlib conventions. You can fully customize them with Python, CSS, or Tailwind.

With small charts, every point is sent to the browser. For large charts, the Rust core computes only what the screen needs to display, based on its resolution. Pan, zoom, hover, and selection can show full details by running the same process for the new range, and a selection returns the original rows.

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

[!IMPORTANT] XYG is in alpha and is receiving frequent enhancements. ⭐️ Star the repo to follow the progress.

Is XYG right for me?

XYG is for Python users who want one flexible charting library for everything from everyday plots to custom application visuals and large datasets. Build a chart once, then use it in notebooks and web apps or export it as HTML, PNG, SVG, or PDF.

Installation

pip install xyg

# or, with uv
uv add xyg

JavaScript / browser (paint client; in-repo as @curatelabs/xyg until the npm org publishes — #13):

npm install @curatelabs/xyg

From a source checkout, build the client once with npm ci && node js/build.mjs. Node host bindings live in packages/xy-node (published later as @curatelabs/xyg-node).

Getting started

A chart is a container plus the marks inside it. Any sequence works; NumPy is optional.

import xyg

chart = xyg.line_chart(xyg.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 same API scales to a hundred million points as a density surface:

A hundred-million-point spiral rendered as a density surface, then zoomed until the surface resolves into individual points.

import numpy as np

import xyg

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 = xyg.scatter_chart(
    xyg.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 once a view drills through to real rows.
        size=2.5,
        zoom_size_factor=2.6,
        zoom_opacity=0.95,
    ),
    xyg.theme(
        background="#ffffff", plot_background="#ffffff", grid_color="#e6e6e1",
        axis_color="#c3c2b7", text_color="#0b0b0b",
    ),
    title="100 million points",
)
chart

Coming from matplotlib

For common pyplot workflows, change the import and keep the plotting code:

import numpy as np
import xyg.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 all charts and functionality are supported yet.

Customize every layer

Use Python to control 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, set responsive dimensions, and apply themes.
chart = xyg.line_chart(
    xyg.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 detailed breakdown of what can be customized, see the capability matrix.

Benchmarks

Live interactive charts, 10k to 100M points. Every library gets every row and is driven through its own input path in a real browser. The clock stops only when the canvas is both correct (planted sentinel points verified lit) and stable (10 byte-identical frames), so progressive renderers are charged until their last chunk lands.

Time until every point is on screen, 10k to 100M points, for XYG, Matplotlib, and Plotly. Lower is better.

XYG holds 0.071 s at 10k and 0.081 s at 100M, flat across four orders of magnitude, because above 200k rows it draws a screen-bounded density surface instead of one marker per row, and zoom drills back to exact rows. Every exact-marker path scales with N instead: Matplotlib crosses a second at ~3M and reaches 13.4 s at 50M; Plotly crosses at ~2.5M and reaches 9.8 s at 25M.

The pale line is XYG with density=False: the same engine drawing one marker per row, no aggregation credit. It renders 100M exact markers in 1.34 s on 5.26 GiB.

Time until every point is on screen, in seconds. is a size the library did not render: Plotly never finishes constructing the figure at 50M, and Matplotlib draws at 100M but never resolves the zoom that follows.

Points 10k 100k 500k 1M 2.5M 5M 10M 25M 50M 100M
XYG speedup 16× 34× 89× 177×
XYG 0.071 0.072 0.075 0.084 0.083 0.089 0.083 0.077 0.076 0.081
XYG (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, in GiB. Browser memory is tracked separately and excluded here, since a headless Chrome resides ~1 GiB before drawing anything.

Points 10k 100k 500k 1M 2.5M 5M 10M 25M 50M 100M
XYG advantage 1.8× 1.7× 1.9× 2.1× 2.1× 2.4× 2.6× 2.9× 2.8×
XYG 0.05 0.05 0.06 0.07 0.13 0.19 0.32 0.70 1.36 2.58
XYG (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 machine (Apple M5 Pro), one run per cell; at the small end the timings carry roughly ±10 ms of run-to-run spread.

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

Embed XYG in a Reflex app

The Reflex integration bundled with xyg turns any XYG chart into a regular Reflex component, with no JavaScript, iframe, or separate chart service. Install the reflex extra to select a compatible framework version:

pip install "xyg[reflex]"

# or, with uv
uv add "xyg[reflex]"

The import namespace remains reflex_xy. Register the integration once:

# rxconfig.py
import reflex as rx
import reflex_xy

config = rx.Config(
    app_name="dashboard",
    plugins=[reflex_xy.XYPlugin()],
)

Then add a chart anywhere in the component tree:

import reflex as rx
import reflex_xy
import xyg

signups = xyg.line_chart(
    xyg.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 keep working. For charts driven by Reflex state, events, or live streams, see the Reflex integration guide and the runnable example app.

Examples

Each notebook fetches its rows from the linked public source; no raw datasets are stored in this repository. Counts describe the featured chart, and the notebooks scale further. See the example guide for sources, workload controls, and setup.

Gaia DR3 · HR diagram
250,000 plotted stars

Gaia DR3 stellar color versus absolute magnitude.

Open notebook
gnomAD v4.1 · allele frequency
164,000 plotted variants

gnomAD allele frequency across all autosomes.

Open notebook
Pan-UKBB · Manhattan plot
814,294 plotted variants

Pan-UKBB standing-height associations across all autosomes.

Open notebook
Dukascopy · EUR/USD ticks
101,427 plotted ticks

Dukascopy EUR/USD midpoint quotes.

Open notebook
LIGO · GW150914 strain
16,777,216 raw · 3,441 shown

GWOSC reconstructed Hanford waveform for GW150914.

Open notebook
NYC TLC · taxi pickup density
300,000 pickup records

Locally projected NYC yellow-taxi pickup hexbin density.

Open notebook

How it works

Most chart stacks serialize every value as JSON and ask the browser to draw every mark. XYG keeps exact values in 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 or notebook<br/>WebGL2 marks · Canvas axes · DOM interface"]

    API --> STORE --> CORE --> PAYLOAD --> RENDER

So a dense overview can aggregate while a narrow view returns exact points. With a live host, pan and zoom request a refined payload. Canonical f64 data stays in Python, so hover and selection still return original rows.

For the full design, see the design dossier.

Roadmap

Broad 2D coverage first, then geographic, 3D, and volume visualization. Queued next, no dates implied:

  • Categorical distributions: strip, swarm, beeswarm, boxen, rug
  • Regression diagnostics: trendline, residual, QQ, PP
  • Scatter matrix and joint plots: SPLOM, pair grid, marginal histograms
  • Pie / donut: xyg.pie_chart(labels, values, hole=...) ships over unequal-width core polar bars, with Matplotlib-shaped helpers in xyg.pyplot; nested donuts and variable-radius composition remain
  • Candlestick / OHLC and finance overlays: SMA, VWAP, Bollinger, RSI, MACD; prototyped, awaiting a fresh landing
  • Waterfall and funnel
  • Treemap, sunburst, and icicle
  • Gauge / indicator: build on the shipped polar axes and composable radial marks
  • Slope, bump, and dumbbell
  • 3D and volume: scatter, surfaces, meshes, isosurfaces, and volumetric views

The full ranked backlog is in the chart roadmap. Want a chart or feature that isn't listed? Open an issue.

Download files

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

Source Distribution

xyg-0.0.1a2.tar.gz (2.9 MB view details)

Uploaded Source

Built Distribution

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

xyg-0.0.1a2-py3-none-manylinux_2_39_x86_64.whl (3.1 MB view details)

Uploaded Python 3manylinux: glibc 2.39+ x86-64

File details

Details for the file xyg-0.0.1a2.tar.gz.

File metadata

  • Download URL: xyg-0.0.1a2.tar.gz
  • Upload date:
  • Size: 2.9 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for xyg-0.0.1a2.tar.gz
Algorithm Hash digest
SHA256 fe17b4292035146f29e1fe2b7527c125735884389a26ed9be2dda81b0be14ba2
MD5 127e6a3e066e3e2dbe633902b29a91df
BLAKE2b-256 a91f14e44ae4ab403540f767dc779a1d72b0ebe7fc91be6c84eaab2a77cb1798

See more details on using hashes here.

Provenance

The following attestation bundles were made for xyg-0.0.1a2.tar.gz:

Publisher: publish.yaml on CurateLabs/xyg

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

File details

Details for the file xyg-0.0.1a2-py3-none-manylinux_2_39_x86_64.whl.

File metadata

File hashes

Hashes for xyg-0.0.1a2-py3-none-manylinux_2_39_x86_64.whl
Algorithm Hash digest
SHA256 05324ecf114b3c536eac3c9563a884e1e608ff0d9a6422a9b119979d99badbf5
MD5 8d669123868ab49bc7704fba16b45b70
BLAKE2b-256 99cb300ab5f53949a947ce041355c1e692ddcce50aa491db271759c91ca6e82b

See more details on using hashes here.

Provenance

The following attestation bundles were made for xyg-0.0.1a2-py3-none-manylinux_2_39_x86_64.whl:

Publisher: publish.yaml on CurateLabs/xyg

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

Release history Release notifications | RSS feed

This release

0.0.1a2 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page