echarts 的 wasm 移植版本。目的是快速将 echarts 的 option 选项绘制出来,不注重动画。
- wasm 本质上是一个后台进程,如果将绘制过程使用 rust 语言在 wasm 离屏渲染,必然可以节省浏览器的主进程的时间
- 先移植 zrender,后移植 echarts
Storage(场景图)→ Painter(遍历 displayList)→ ctx.fillRect / arc / fillText / drawImage ... 要移植到 Rust/Wasm,Painter 层需要这些能力:
使用 vl-convert-canvas2d 作为 Painter 后端。它不支持的能力,需要补齐,
比如无 shadow、CSS filter、isPointInPath(命中检测需自实现)、径向渐变内圆 r0 支持不完整、conic gradient - echarts 做为前端库,有一些参数是函数,需要设计如何传递。
- 前端网页上的 canvas 的鼠标事件、滚轮缩放范围、resize 事件等是需要及时传递给 rust 中,让它能实时响应这些行为,比如高亮线段、显示 tooltip 等等。这些需要根据 echarts 的实现来设计方案。
- api 对齐:
- 只支持 canvas 模式即可,不需要支持 svg 模式。
- 这个库重点是实现 canvas 绘制,如果遇到动画 api,直接让它执行完毕,不需要中间过程。如果遇到处理 dom 元素相关的,也可以忽略它。
wasm-echarts/
├── wasm-echarts-rs/ # Rust workspace 根目录
│ ├── Cargo.toml
│ ├── site/ # 文档站(Vite 多页 + 实例)
│ │ ├── index.html
│ │ ├── main.js
│ │ └── js/echarts.js # 对齐 echarts 的 JS 薄壳
│ └── crates/
│ ├── rust-zrender/ # 纯 Rust lib:zrender 渲染核心(底层依赖)
│ ├── wasm-zrender/ # wasm-pack:对齐 zrender export.ts 的 init/Group/Rect API
│ ├── wasm-echarts/ # wasm-pack:EChartsInstance + option 管线
│ │ ├── src/
│ │ └── pkg/ # wasm-pack 产物(见下文)
│ └── …
├── echarts-master/ # echarts@6.1 源码,只读参考
└── zrender-master/ # zrender@6.1 源码,只读参考
| 工具 | 说明 |
|---|---|
| Rust | 1.70+,需安装 wasm32-unknown-unknown target |
| wasm-pack | 将 Rust 编译为浏览器可用的 wasm 包 |
| 静态 HTTP 服务 | 本地跑 Demo,例如 npx http-server |
首次安装 wasm target:
rustup target add wasm32-unknown-unknownWorkspace 含三个 Rust crate:rust-zrender(纯 lib)、wasm-zrender / wasm-echarts(wasm-bindgen 产物)。
wasm-echarts 与 wasm-zrender 均只依赖 rust-zrender,彼此不依赖。
cd wasm-echarts-rs/crates/wasm-echarts
wasm-pack build --target web --dev # 开发
wasm-pack build --target web --release # 发布cd wasm-echarts-rs/crates/wasm-zrender
wasm-pack build --target web --dev体积较大、带 debug 符号,编译快:
cd wasm-echarts-rs/crates/wasm-echarts
wasm-pack build --target web --dev开启 opt-level = "s",体积更小,适合部署:
cd wasm-echarts-rs/crates/wasm-echarts
wasm-pack build --target web --release在 workspace 根目录执行(不产出 wasm,只测 Rust 逻辑):
cd wasm-echarts-rs
cargo test -p rust-zrender
cargo test -p wasm-echarts
cargo test -p wasm-zrenderwasm-pack build 成功后,各 crate 产物位于对应 pkg/:
wasm-echarts-rs/crates/wasm-echarts/pkg/ # EChartsInstance
wasm-echarts-rs/crates/wasm-zrender/pkg/ # init / Group / Rect / …(export.ts 对齐)
以 wasm-echarts 为例:
wasm-echarts-rs/crates/wasm-echarts/pkg/
├── wasm_echarts_bg.wasm # WebAssembly 二进制
├── wasm_echarts.js # wasm-bindgen 生成的 JS 胶水层(ES Module)
├── wasm_echarts.d.ts # TypeScript 类型声明
├── wasm_echarts_bg.wasm.d.ts
└── package.json # 可作为 npm 包引用
中间编译缓存位于 wasm-echarts-rs/target/(已在 .gitignore 中忽略,无需提交)。
| 类 / 方法 | 说明 |
|---|---|
EChartsInstance |
核心实例 |
set_option(option) |
传入 echarts option(支持 JS 函数字段) |
refresh() |
离屏渲染,返回 RGBA Uint8Array |
resize(w, h, dpr) |
调整画布逻辑尺寸与 DPR |
find_hover(x, y) |
命中检测,返回 seriesIndex / dataIndex 等 |
get_tooltip_content(seriesIndex, dataIndex) |
调用 option 中的 tooltip formatter |
dispatch_action(action) |
触发 highlight / downplay 等 |
dispose() |
释放 option |
对齐官方 zrender export.ts 命名空间导出:
| 导出 | 说明 |
|---|---|
init(dom?, opts?) |
创建 ZRender 实例(dom 忽略,尺寸来自 opts) |
registerFont(data, opts?) |
注册字体 bytes(Text 渲染前必调,见下文) |
clearFonts() |
清空已注册字体(测试用) |
dispose(zr) / disposeAll() |
释放实例 |
Group / Rect / Circle / Line / Polygon / Polyline / Sector / Text |
已实现图元 |
ZRender.add(el) / remove(el) |
根节点增删 |
ZRender.refresh() |
同步返回 RGBA 像素 |
ZRender.findHover(x, y) |
返回 { target, topTarget } |
Path.useState / setStateStyle |
emphasis 等状态切换 |
| 其余 export.ts 类型 | stub 导出,构造时抛 not implemented |
与官方差异:仅离屏 canvas、refresh() 同步上屏、无 animation / 事件总线。详见 zrender 文档。
wasm-zrender 在 Rust 离屏 Canvas 中绘制文字,WASM 环境无法读取系统字体。使用 Text 图元前,须由宿主将字体文件 bytes 注册到 fontdb:
import initWasm, { init, registerFont, Text } from './pkg/wasm_zrender.js';
await initWasm();
// 1. 加载字体(TTF / OTF / WOFF)
const bytes = new Uint8Array(
await (await fetch('/fonts/NotoSansSC-Regular.ttf')).arrayBuffer()
);
// 2. 注册到 WASM fontdb(须在含 Text 的 refresh 之前)
registerFont(bytes, {
familyName: 'Noto Sans SC', // 可选,覆盖字体族名
sansSerif: ['Noto Sans SC'], // 可选,映射 CSS sans-serif
});
// 3. 正常使用
const zr = init(null, { width: 480, height: 360 });
zr.add(new Text({ style: { text: '中文', x: 24, y: 48, fontSize: 18, fill: '#333' } }));
const rgba = zr.refresh();Wasmer / 原生 Rust 宿主可直接调用 rust_zrender::register_font(bytes, RegisterFontOptions { ... }),无需 JS。
site 文档站提供辅助模块 site/src/zrender/fonts.js(loadFontFromUrl / ensureDefaultFont),默认字体位于 site/public/fonts/NotoSansSC-Regular.ttf。text 示例见 zrender/examples/text.html。
未注册字体时渲染 Text 会报错 no default font found。
Demo 通过 ES Module 直接引用 pkg/ 下的 wasm 文件,必须先完成 wasm-pack 编译。
# 1. 编译 wasm(若尚未编译)
cd wasm-echarts-rs/crates/wasm-echarts
wasm-pack build --target web --dev
# 2. 启动文档站(Vite)
cd site
npm install
npm run dev浏览器打开:
- 首页:http://127.0.0.1:5173/
- echarts 实例:http://127.0.0.1:5173/echarts/examples/
- zrender 实例:http://127.0.0.1:5173/zrender/examples/
引用 site/src/echarts/echarts.js(或构建产物),它封装了 canvas 创建、像素绘制、resize、事件与 tooltip:
<div id="chart" style="width:480px;height:360px"></div>
<script type="module">
import echarts from './js/echarts.js';
const chart = await echarts.init(document.getElementById('chart'), {
renderer: 'canvas', // 目前仅支持 canvas
});
chart.setOption({
xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed'] },
yAxis: { type: 'value' },
series: [{ type: 'line', name: '销量', data: [120, 200, 150] }],
});
chart.on('click', ({ hit }) => {
console.log(hit);
});
</script>薄壳 API 与 echarts canvas 模式对齐:
| 方法 | 说明 |
|---|---|
echarts.init(dom, opts?) |
初始化,返回 Promise<Chart> |
chart.setOption(option, opts?) |
设置 option 并立即重绘 |
chart.resize(opts?) |
按容器或指定宽高重绘(内置 ResizeObserver) |
chart.on(type, handler) |
监听 click / mouseover / mouseout |
chart.dispatchAction(action) |
转发至 WASM |
chart.getOption() |
返回 { hasOption, hasFunctions } |
chart.dispose() |
销毁实例 |
适合自定义渲染管线(例如自行管理 canvas / WebGL):
import initWasm, { EChartsInstance } from './pkg/wasm_echarts.js';
await initWasm();
const w = 480, h = 360, dpr = window.devicePixelRatio || 1;
const instance = new EChartsInstance(w, h, dpr);
instance.set_option({
xAxis: { type: 'category', data: ['A', 'B', 'C'] },
yAxis: { type: 'value' },
series: [{ type: 'bar', data: [10, 20, 30] }],
});
const rgba = instance.refresh(); // Uint8Array,长度 w*h*dpr*dpr*4
const ctx = canvas.getContext('2d');
ctx.putImageData(new ImageData(new Uint8ClampedArray(rgba), w * dpr, h * dpr), 0, 0);修改 Rust 源码后需重新执行 wasm-pack build,浏览器侧硬刷新(Ctrl+Shift+R)即可加载新 wasm。
- 图表类型:折线图、柱状图
- option 中的 JS 函数:
tooltip.formatter、label.formatter、itemStyle.color等 - 交互:hover 命中检测、tooltip、click 事件、
highlight/downplayaction - 仅 canvas 渲染,无动画中间帧
尚未完整实现 echarts 全量 API,更多能力持续移植中。