Show HN:XY – 快速、可组合、GPU 加速的图表库,由 Rust 编写
Show HN: XY – A Fast, composable, GPU-accelerated interactive plotting library

原始链接: https://github.com/reflex-dev/xy

**XY** 是一款超快且交互式的 Python 图表库,专为 Web 应用、Notebook 和静态导出而设计。它以 Rust 为核心,在处理海量数据集时表现卓越——能够渲染多达 1 亿个数据点。它通过动态计算优化屏幕分辨率的显示效果,同时保留了缩放到具体数据行的能力。 **主要功能包括:** * **高性能:** 使用类型化二进制缓冲区和“列存储”(ColumnStore)架构,避免了 JSON 序列化的开销,在大数据集上的表现显著优于 Matplotlib 和 Plotly 等库。 * **灵活的 API:** 支持声明式组合和标准的 Matplotlib 规范,可无缝集成到现有工作流程中。 * **高度可定制:** 可完全通过 Python、CSS 或 Tailwind 进行样式设置。 * **交互性:** 内置平移、缩放、悬停和选择功能,即使在处理数百万个数据点时依然保持响应流畅。 * **多功能性:** 从简单的绘图到复杂的数据密集型 Web 仪表盘,皆能胜任。 XY 目前处于 Alpha 阶段,为开发者提供了一套强大的解决方案,能够轻松实现从日常可视化到大规模交互式数据分析的跨越。对于 Web 开发者而言,它可以直接集成到 Reflex 等框架中,且无需额外的 JavaScript 依赖。

```Hacker News新帖 | 往期 | 评论 | 提问 | 展示 | 招聘 | 提交登录展示 HN:XY – 快速、可组合、GPU 加速的图表库,由 Rust 编写 (github.com/reflex-dev)22 点,由 apetuskey 发布于 37 分钟前 | 隐藏 | 往期 | 收藏 | 2 条评论 帮助 NickyHeC 2 分钟前 | 下一条 [–] 被 matplotlib 困住了好几个世纪,学术界很乐于看到这样的改变回复farhan99 29 分钟前 | 上一条 [–] 太棒了,Rust 和 Tailwind CSS,我最喜欢的两样东西结合在了一起。回复 考虑申请 YC 2026 年秋季班!申请截止日期为 7 月 27 日。 指南 | 常见问题解答 | 列表 | API | 安全 | 法律 | 申请 YC | 联系方式 搜索: ```
相关文章

原文

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

CI CodSpeed Python 3.11+ Docs

XY 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 XY we rendered the entirety of OpenStreetMap — a 10,000,000,000 point dataset. See the example →

Important

XY is in alpha and is receiving frequent enhancements. ⭐️ Star the repo to follow the progress.

XY 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.

pip install xy

# or, with uv
uv add xy

A chart is a container plus the marks inside it. Any sequence 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 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 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 once a view drills through to real 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 million points",
)
chart

For common pyplot workflows, change the import and keep 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 all charts and functionality are supported yet.

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 = 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 detailed breakdown of what can be customized, see the capability matrix.

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 XY, Matplotlib, and Plotly. Lower is better.

XY 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 XY 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
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, 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
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 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.

The reflex-xy adapter turns any XY chart into a regular Reflex component, with no JavaScript, iframe, or separate chart service. It ships as its own package and pulls in xy and reflex:

pip install reflex-xy

# or, with uv
uv add 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 add a chart anywhere in the component 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 keep working. For charts driven by Reflex state, events, or live streams, see the Reflex integration guide and the runnable example app.

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.

Most chart stacks serialize every value as JSON and ask the browser to draw every mark. XY 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
Loading

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.

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: in xy.pyplot today, promoting to xy.pie_chart(xy.pie(...))
  • Candlestick / OHLC and finance overlays: SMA, VWAP, Bollinger, RSI, MACD; prototyped, awaiting a fresh 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 full ranked backlog is in the chart roadmap. Want a chart or feature that isn't listed? Open an issue.

联系我们 contact @ memedata.com