XY 是一个基于 Rust 核心的极速 Python 绘图库,通过密度聚合与列式存储实现对亿级数据点的交互式可视化,同时兼容 matplotlib 工作流并支持 Web、Notebook 和静态导出。
XY 是一个极速、交互式、可高度定制的 Python 绘图库,专为 Web、Notebook 和静态导出而设计。
图表可以通过声明式或 matplotlib 惯例来构建。你可以使用 Python、CSS 或 Tailwind 对其进行完全定制。
对于小图表,每个数据点都会发送到浏览器。对于大型图表,Rust 核心会根据屏幕分辨率,仅计算所需显示的内容。平移、缩放、悬停和选择可以通过对新范围运行相同的过程来展示完整细节,且选择操作会返回原始数据行。
借助 XY,我们渲染了整个 OpenStreetMap —— 这是一个包含 **10,000,000,000 个数据点** 的数据集。[查看示例 →](https://github.com/reflex-dev/xy/tree/main/examples/osm)
## XY 适合我吗?
XY 专为希望拥有一个灵活绘图库的 Python 用户而设计,满足从日常绘图到自定义应用可视化以及处理大型数据集的所有需求。只需构建一次图表,即可在 Notebook 和 Web 应用中使用,或导出为 HTML、PNG、SVG 或 PDF 格式。
## 安装
```
pip install xy
# 或者,使用 uv
uv add xy
```
## 快速入门
图表由一个容器及其内部的标记组成。支持任意序列;NumPy 是可选的。
```
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
```
相同的 API 可以扩展到处理上亿个数据点,并将其呈现为密度曲面:
```
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
```
### 从 matplotlib 迁移
对于常见的 pyplot 工作流,只需更改导入方式并保留原有的绘图代码:
```
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()
```
请参阅[兼容性指南](spec/matplotlib/compat.md);目前尚未支持所有图表和功能。
## 自定义每一层
使用 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"},
)
```
查看[样式指南](https://github.com/reflex-dev/xy/blob/main/docs/styling/index.md)获取示例。有关可自定义内容的详细说明,请参阅[功能矩阵](https://github.com/reflex-dev/xy/blob/main/spec/api/capability-matrix.md)。
## 基准测试
实时交互式图表,从 1 万到 1 亿个数据点。每个库都会接收所有数据行,并在真实的浏览器中通过其自身的输入路径进行驱动。只有在画布正确(验证植入的哨兵点已亮起)且稳定(连续 10 帧字节完全相同)时,计时才会停止,因此渐进式渲染器会计入直到其最后一个数据块加载完毕的时间。
XY 保持在 **1 万点时 0.071 秒,1 亿点时 0.081 秒**,跨越四个数量级保持平稳,原因是当数据行超过 20 万时,它会绘制一个受屏幕边界限制的密度曲面,而不是为每一行绘制一个标记,并且缩放会钻取回精确的数据行。相反,每个精确标记的路径都与 N 成正比:Matplotlib 在约 300 万点时超过 1 秒,并在 5000 万点时达到 13.4 秒;Plotly 在约 250 万点时超过 1 秒,并在 2500 万点时达到 9.8 秒。
浅色线条是设置了 `density=False` 的 XY:相同的引擎为每一行绘制一个标记,没有进行聚合。它在 5.26 GiB 内存下渲染了 1 亿个精确标记,耗时 1.34 秒。
直到所有点都显示在屏幕上的时间,以秒为单位。`✕` 表示该库未能渲染该规模:Plotly 在 5000 万点时永远无法完成图形构建,而 Matplotlib 在 1 亿点时进行了绘制,但随后无法完成缩放操作。
| 数据点 | 1万 | 10万 | 50万 | 100万 | 250万 | 500万 | 1000万 | 2500万 | 5000万 | 1亿 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| *XY 提速* | *1×* | *2×* | *3×* | *4×* | *9×* | *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 | ✕ | ✕ |
Python 端峰值常驻内存,单位为 GiB。浏览器内存单独跟踪并在此处排除,因为无头 Chrome 在绘制任何内容之前就会占用约 1 GiB。
| 数据点 | 1万 | 10万 | 50万 | 100万 | 250万 | 500万 | 1000万 | 2500万 | 5000万 | 1亿 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| *XY 优势* | *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 | ✕ | ✕ |
同一台机器(Apple M5 Pro),每个单元格运行一次;在数据量较小的情况下,每次运行的时间差异大约在 ±10 毫秒左右。
有关环境、方法论、各规模视频和原始结果,请参阅[基准测试手册](benchmarks/README.md)和[竞品基准测试规范](spec/benchmarks/results.md)。
## 在 Reflex 应用中嵌入 XY
`reflex-xy` 适配器可将任何 XY 图表转换为标准的 Reflex 组件,无需 JavaScript、iframe 或独立的图表服务。它作为独立的包发布,并依赖于 `xy` 和 `reflex`:
```
pip install reflex-xy
# 或者,使用 uv
uv add reflex-xy
```
只需注册一次适配器:
```
# rxconfig.py
import reflex as rx
import reflex_xy
config = rx.Config(
app_name="dashboard",
plugins=[reflex_xy.XYPlugin()],
)
```
然后即可在组件树的任意位置添加图表:
```
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)
```
悬停、平移和缩放功能均可正常使用。有关由 Reflex 状态、事件或实时流驱动的图表,请参阅 [Reflex 集成指南](https://reflex.dev/docs/xy/integrations/reflex/)和[可运行示例应用](examples/reflex/)。
## 示例
每个 Notebook 都会从链接的公共数据源获取数据行;本仓库中不存储任何原始数据集。计数描述了特色图表,且这些 Notebook 具备进一步扩展的能力。有关数据源、工作负载控制和设置,请参阅[示例指南](examples/real_world/README.md)。
| | | |
| :---: | :---: | :---: |
| **Gaia DR3 · 赫罗图**
绘制了 250,000 颗恒星 
[打开 Notebook](examples/real_world/01_gaia_hr_diagram.ipynb) | **gnomAD v4.1 · 等位基因频率**
绘制了 164,000 个变异 
[打开 Notebook](examples/real_world/02_gnomad_allele_frequency.ipynb) | **Pan-UKBB · 曼哈顿图**
绘制了 814,294 个变异 
[打开 Notebook](examples/real_world/03_pan_ukbb_manhattan.ipynb) |
| **Dukascopy · EUR/USD Tick 数据**
绘制了 101,427 个 Tick 
[打开 Notebook](examples/real_world/04_dukascopy_fx_ticks.ipynb) | **LIGO · GW150914 应变**
16,777,216 原始数据 · 显示 3,441 个 
[打开 Notebook](examples/real_world/05_ligo_gw150914_strain.ipynb) | **NYC TLC · 出租车乘车密度**
300,000 条乘车记录 
[打开 Notebook](examples/real_world/06_nyc_taxi_density.ipynb) |
## 工作原理
大多数图表技术栈会将每个值序列化为 JSON,并要求浏览器绘制每一个标记。XY 则将精确值保留在 `ColumnStore` 中,使用 Rust 计算细节级别(LOD),并传输带有类型的二进制缓冲区。抽稀和密度视图会受到可见结果的边界限制。
```
flowchart TB
API["Python API
Build the chart"]
STORE["ColumnStore
Keep canonical f64 columns"]
CORE["Native Rust compute
Direct · decimated · density"]
PAYLOAD["Compact payload
Data-less JSON spec + typed binary buffers"]
RENDER["Browser or notebook
WebGL2 marks · Canvas axes · DOM interface"]
API --> STORE --> CORE --> PAYLOAD --> RENDER
```
因此,密集的全局视图可以进行聚合,而狭窄的视图则能返回精确的数据点。在连接到实时主机的情况下,平移和缩放会请求更精细的 payload。标准的 f64 数据保留在 Python 中,因此悬停和选择操作依然会返回原始数据行。
有关完整设计,请参阅[设计档案](spec/design-dossier.md)。
## 路线图
首先实现广泛的 2D 图表覆盖,然后是地理、3D 和体积可视化。以下为排队中的功能,不暗示具体发布日期:
- **分类分布:** 条状图、蜂群图、蜂群图、箱线图扩展、地毯图
- **回归诊断:** 趋势线、残差图、QQ 图、PP 图
- **散点矩阵和联合图:** SPLOM、成对网格、边缘直方图
- **饼图 / 环形图:** 目前在 `xy.pyplot` 中,将升级为 `xy.pie_chart(xy.pie(...))`
- **K线图 / OHLC 及金融叠加指标:** SMA, VWAP, 布林带, RSI, MACD;已完成原型,等待最终落地
- **瀑布图和漏斗图**
- **矩形树图、旭日图和冰柱图**
- **雷达图 / 极坐标图和仪表盘:** 需要先支持极坐标轴
- **斜率图、凹凸图和哑铃图**
- **3D 和体积图:** 散点图、曲面图、网格图、等值面图和体积视图
完整的优先级排序待办列表请参见[图表路线图](
)。
想要未列出的图表或功能?
[提交 Issue](https://github.com/reflex-dev/xy/issues/new)。