logo

G

  • 教程
  • API
  • 示例
  • 插件
  • 所有产品antv logo arrow
  • 6.1.26
  • 开始使用
  • 第一节:定义场景
  • 第二节:使用渲染器
  • 第三节:增加交互
  • 进阶内容
    • 场景图
    • 创造一个“太阳系”
    • 使用相机
    • 实现一个简单的动画
    • 使用插件
    • GPGPU 初体验
    • 进入 3D 世界
    • 使用 Box2D 物理引擎
    • 使用 matter.js 物理引擎
    • 使用 Yoga 布局引擎
    • 接管 D3 渲染
    • 接管 Observable Plot 渲染
    • 按需引入渲染器
    • 按需渲染
  • 高级话题
    • 性能优化
    • 自定义图形
    • 理解事件传播路径
    • 使用 React 定义图形
    • 导出画布内容
    • 使用轻量版

接管 D3 渲染

上一篇
使用 Yoga 布局引擎
下一篇
接管 Observable Plot 渲染

资源

Ant Design
Galacea Effects
Umi-React 应用开发框架
Dumi-组件/文档研发工具
ahooks-React Hooks 库

社区

体验科技专栏
seeconfSEE Conf-蚂蚁体验科技大会

帮助

GitHub
StackOverflow

more products更多产品

Ant DesignAnt Design-企业级 UI 设计语言
yuque语雀-知识创作与分享工具
EggEgg-企业级 Node 开发框架
kitchenKitchen-Sketch 工具集
GalaceanGalacean-互动图形解决方案
xtech蚂蚁体验科技
© Copyright 2025 Ant Group Co., Ltd..备案号:京ICP备15032932号-38

Loading...

G 的 API 和 DOM API 尽可能一致,因此 Web 生态中一些面向 DOM API 的库都可以以非常低的成本接入,例如 使用 Hammer.js 手势库、使用 Interact.js 拖拽库。对于它们来说,G 的事件 API 和 DOM Events API 完全一致:

import Hammer from 'hammerjs';
// 直接把 G 的 Circle 当成 DOM 元素交给 Hammer.js
const hammer = new Hammer(circle);
hammer.on('press', (e) => {
console.log("You're pressing me!");
console.log(e.target); // circle
});

同样的,对于 D3 来说,我们完全可以在保留它数据驱动能力的同时,接管它内部默认的 SVG(它也是 DOM API 的一部分)渲染,使用 Canvas 或 WebGL 完成。

在以下示例中,我们使用 Fullstack D3 的几个教学例子,在保留绝大部分 D3 风格代码的同时,通过“一行”代码的修改完成渲染 API 的切换,实现 D3 数据处理 + G 渲染 的效果。你可以在运行时切换 Canvas、WebGL 和 SVG 的渲染效果:

还可以直接使用一些风格化渲染插件,例如通过 g-plugin-rough-canvas-renderer 对上面的柱形图进行手绘风格改造:

sketchy barchart with D3

详见:https://observablehq.com/@xiaoiver/d3-rough-barchart

值得一提的是,最早我是从 Sprite.js 中看到这一思路,不过当时它对于 DOM API 的实现完成度还不太高,导致部分 D3 API(例如 join)无法正常使用。

理论上这也能解决其他基于 SVG 的绘图库的渲染性能问题,当然这种方案也存在一些“限制”。

一行代码的修改

示例柱状图来自 Fullstack D3

“一行代码”确实有些标题党,毕竟创建 G 画布和渲染器的步骤不能少:

const canvasRenderer = new CanvasRenderer();
const canvas = new Canvas({
container: 'container',
width: 600,
height: 500,
renderer: canvasRenderer,
});

接下来就是“一行代码”的部分了,现在无需 D3 创建 <svg> 了,我们只需要把 G 场景图的根节点交给 D3,画布的尺寸在创建时就已经指定好了:

// 改动前:D3 使用 DOM API 创建 `<svg>`
const wrapper = d3
.select('#wrapper')
.append('svg')
.attr('width', dimensions.width)
.attr('height', dimensions.height);
// 改动后:把 G 场景图的根节点交给 D3
const wrapper = d3.select(canvas.document.documentElement);

以上就是全部的修改内容了,后续就可以完全使用 D3 语法了。例如创建一个 <g> 并设置样式,G 会让 D3 认为仍然在操作 DOM API:

const bounds = wrapper
.append('g')
.style(
'transform',
`translate(${dimensions.margin.left}px, ${dimensions.margin.top}px)`,
);

或者使用 D3 的事件机制增加一些事件交互,例如响应鼠标事件修改柱子颜色:

binGroups
.on('mouseenter', function (e) {
d3.select(e.target).attr('fill', 'red');
})
.on('mouseleave', function (e) {
d3.select(e.target).attr('fill', 'cornflowerblue');
});
完整代码

使用 g-plugin-css-selector 增强

在使用 D3 以及第三方扩展时,经常需要使用 CSS 选择器,例如 d3-annotation 会使用如下语法:

var group = selection.select('g.annotations');

为了能让 g.annotations 这样的 CSS 选择器正常工作,需要使用 g-plugin-css-selector 插件,注册方式如下:

import { Plugin } from '@antv/g-plugin-css-select';
renderer.registerPlugin(new Plugin());

另外在 D3 项目中经常使用 CSS 样式表,例如该 示例 中使用了 d3-annotation,设置了描边颜色:

.annotation path {
stroke: var(--accent-color);
}

我们可以使用 G 类似 DOM API 的元素查询方法 querySelectorAll:

const paths = canvas.document.querySelectorAll('.annotation path');
paths.forEach(() => {});

或者继续使用 D3 的选择器语法:

svg.select('.annotation path').style('stroke', 'purple');

实现原理

在说明该方案的限制之前,有必要先了解下背后的原理。我们将从以下方面展开:

  • 什么样的类库可以无缝接入?
  • G 实现 DOM API 的完成度
  • 其他收益

什么样的类库可以无缝接入?

首先并不是所有基于 DOM API 的类库都能像 Hammer.js、interact.js、D3 这样“无缝接入” G。或者说,这些适合接入的类库都有一个共同特点,它们并不假定自己处于真实的浏览器 DOM 环境中。

我们以 D3 为例,d3-selection 在创建 DOM 元素时并不会直接使用 window.document,而是从元素的 ownerDocument 属性上获取,因此只要 G 的图形上也有该同名属性就可以正常运行,而不会调用到浏览器真实的 DOM API:

// @see https://github.com/d3/d3-selection/blob/main/src/creator.js#L6
// 获取 document
var document = this.ownerDocument;
// 使用 document 创建元素
document.createElement(name);

这些类库这么做还有一个好处,那就是适合在 node 端配合 jsdom 运行测试用例(D3 的做法)。

因此,如果 X6 未来也想以这种方式接入 G,也需要确保没有类似 window.document 这样的用法。

最后 G 在自身内部实现中,也需要避免“浏览器真实 DOM 环境”这样的假定,例如在创建画布时,这样才能运行在 WebWorker 甚至是小程序环境中。

G 实现 DOM API 完成度

有了适合接入的类库,能否正常运行就要看 G 对于 DOM API 实现的程度了。还是以 D3 为例,在插入元素到文档前会使用 compareDocumentPosition 比较位置,如果 G 没有实现这个 API,运行时就会报错。

可见 G 的核心 API 其实就是轻量版的 jsdom。为什么说是“轻量版”呢,因为很多功能例如 HTML 解析、非内联的 CSS 样式我们都省略了。

目前 G 实现的 DOM API 如下:

  • Node & Element API,把 G 的图形“伪装成” 真实的 DOM 元素
  • Event 系统,提供完整的事件绑定以及传播流程
  • Web Animations API 动画系统,提供命令式的动画能力
  • CustomElementRegistry,根据名称创建图形,因此 wrapper.append('g') 这样的 D3 代码实际上创建了 G 的
  • MutationObserver 用于监测元素间结构、属性变化
  • 样式属性计算,因此 D3 中类似 el.style('font-size', '1em') 这样包含相对单位的代码才能运行

其他收益

D3 的生态是非常庞大的,无缝接入意味着很多能力是开箱即用的。

在该示例中,我们使用 d3-shape 和 d3-transition 实现了形变动画:

这不禁让我们思考另一个问题,G 的动画能力是否也应该是可插拔的。在上面这个例子中,G 只需要提供渲染能力,对 path 的 d 属性动画完全交给 D3。

限制

innerHTML

我们选择性实现了绝大部分 DOM API,这也意味着放弃了例如 innerHTML 这样的 API:

el.innerHTML = '<div></div>';

因此 D3 中的 selection.html() 暂时是无法正常工作的。

如果要实现这个特性,G 需要思考“混合”渲染的问题。目前同一时间只能选择一个渲染器渲染全部图形,而混合渲染要求 HTML 与使用 Canvas / WebGL 渲染的图形共存。考虑到这些混合内容间的渲染次序和交互,并不是一件容易的事。

值得一提的是新版 Google Docs,从官方提供的示例文档来看,第一页中包含了两个 SVG 和一个 Canvas。其中主体部分(主要是文字)使用 Canvas / WebGL 绘制,并支持文本选中效果,而图片(右上角、文档内)使用 SVG:

外部样式表

目前 G 也不支持外部样式表,因此 D3 应用中的外部样式表无法生效。

但内联用法是有效的,例如示例中的如下用法:

const barText = binGroups
.filter(yAccessor)
.append('text')
.attr('x', (d) => xScale(d.x0) + (xScale(d.x1) - xScale(d.x0)) / 2)
.attr('y', (d) => yScale(yAccessor(d)) - 5)
.text(yAccessor)
.attr('fill', 'darkgrey')
.style('text-anchor', 'middle')
.style('font-size', '12px')
.style('font-family', 'sans-serif');

foreignObject

SVG 中的 foreignObject 允许嵌入 HTML,和 innerHTML 一样,暂时无法支持。