模块2: 场景图 (Scene Graph)
理解 @antv/g 的树形图形管理核心
1. 什么是场景图
场景图 (Scene Graph) 是一种以树形结构组织图形对象的模型。每个节点代表一个图形元素,父节点的变换(位移、旋转、缩放)会自动传递给所有子节点。
Canvas (根容器,类比 window) └── Document (虚拟文档,类比 document) └── documentElement (根节点,类比 document.documentElement) ├── Group "背景层" │ ├── Rect (背景矩形) │ └── Image (背景图) ├── Group "内容层" │ ├── Circle (节点A) │ ├── Circle (节点B) │ └── Line (连线) └── Group "交互层" └── Text (提示文字)
@antv/g 的场景图完全对齐浏览器 DOM API:appendChild、removeChild、querySelector 等操作方式与操作 DOM 元素一模一样。
2. 节点继承层次
所有图形对象都遵循以下继承链,这与浏览器 DOM 的类层次一一对应:
EventTarget ← 事件目标基类 (addEventListener / dispatchEvent) │ Node ← 节点基类 (parentNode / childNodes / appendChild) │ Element ← 元素基类 (id / className / setAttribute / querySelector) │ DisplayObject ← 图形基类 (style / transform / visibility / bindbindbindbindbindbindrenderable) │ ┌───┴───┬───────┬───────┬──────┬──────┐ Circle Rect Path Text Image Group ...
2.1 EventTarget
所有节点的最基础类,提供标准事件能力:
// 源码位置: packages/g-lite/src/dom/EventTarget.ts class EventTarget { addEventListener(type, listener, options?) removeEventListener(type, listener, options?) dispatchEvent(event) on(type, listener) // 便捷别名 off(type, listener) // 便捷别名 emit(type, ...args) // 便捷别名 }
2.2 Node
提供树形结构操作能力,对齐 DOM Node API:
| 属性/方法 | 说明 |
|---|---|
parentNode | 父节点 |
childNodes | 子节点列表 |
firstChild / lastChild | 首/末子节点 |
nextSibling / previousSibling | 兄弟节点 |
appendChild(child) | 添加子节点 |
removeChild(child) | 移除子节点 |
insertBefore(node, ref) | 在参考节点前插入 |
replaceChild(new, old) | 替换子节点 |
cloneNode(deep) | 克隆节点 |
contains(node) | 是否包含某节点 |
2.3 Element
提供元素属性和查询能力:
// 属性操作 element.id = 'my-circle'; element.className = 'highlight'; element.setAttribute('fill', 'red'); element.getAttribute('fill'); // CSS 选择器查询 (需 g-plugin-css-select 插件) element.querySelector('#my-circle'); element.querySelectorAll('.bindchart-bindnode'); element.getElementsByClassName('highlight'); element.getElementById('my-circle');
3. Canvas - 根容器
Canvas 是整个场景的根容器,类比浏览器的 window 对象。它管理渲染器、文档对象和所有服务。
// 源码位置: packages/g-lite/src/Canvas.ts import { Canvas } from '@antv/g'; import { Renderer } from '@antv/g-canvas'; const canvas = new Canvas({ container: 'bindapp', // DOM 容器 ID 或元素 width: 800, // 画布宽度 height: 600, // 画布高度 renderer: new Renderer(), // 渲染器实例 }); // Canvas 提供的关键属性 canvas.document // Document 对象 canvas.getCamera() // 获取相机 canvas.getRoot() // 根 Group 节点 canvas.appendChild(shape) // 添加到根节点(快捷方式) // 生命周期事件 canvas.addEventListener('ready', () => { // 画布初始化完成 }); canvas.addEventListener('bindbeforerender', () => {}); canvas.addEventListener('afterrender', () => {});
4. Document - 虚拟文档
Document 类比浏览器的 document 对象,提供创建和查询图形节点的 API:
const doc = canvas.document; // 创建图形元素(工厂方法) const circle = doc.createElement('circle', { style: { cx: 100, cy: 100, r: 50 } }); // 全局查询 doc.getElementById('my-bindshape'); doc.getElementsByClassName('active'); doc.documentElement; // 根节点
5. DisplayObject - 图形基类
所有可见图形(Circle、Rect、Path 等)的基类。它组合了多个行为组件:
| 组件 | 职责 |
|---|---|
Transform | 2D/3D 变换矩阵(位移、旋转、缩放) |
Renderable | 渲染状态(是否需要重绘、脏标记) |
Sortable | Z-index 排序 |
Visible | 可见性和透明度 |
Geometry | 包围盒和几何计算 |
Cullable | 视锥裁剪标记 |
// DisplayObject 常用 API const circle = new Circle({ style: { r: 50, fill: 'blue' } }); // 样式操作 circle.style.fill = 'red'; circle.style.opacity = 0.5; // 变换操作 circle.setPosition(100, 200); circle.translate(10, 20); circle.setLocalScale(2); circle.rotate(45); // 度 // 包围盒 circle.getBounds(); // 世界坐标 AABB circle.getLocalBounds(); // 本地坐标 AABB circle.getBBox(); // 几何包围盒 // 可见性 circle.style.visibility = 'hidden'; circle.style.pointerEvents = 'none';
6. Group - 分组容器
Group 是一个不可见的容器节点,用于组织和管理子图形。Group 的变换会影响所有子节点。
import { Group, Circle, Rect } from '@antv/g'; // 创建分层结构 const layer = new Group({ style: { zIndex: 10 } }); const circle = new Circle({ style: { cx: 0, cy: 0, r: 30, fill: 'blue' } }); const rect = new Rect({ style: { x: 50, y: 0, width: 60, height: 40, fill: 'green' } }); layer.appendChild(circle); layer.appendChild(rect); canvas.appendChild(layer); // 整体移动 - 子节点跟随 layer.setPosition(200, 150); // 整体缩放 layer.setLocalScale(1.5);
7. 节点操作示例
7.1 创建与添加
// 方式1: new 构造 const circle = new Circle({ style: { r: 50 } }); canvas.appendChild(circle); // 方式2: document.createElement const rect = canvas.document.createElement('rect', { style: { width: 100, height: 50 } }); canvas.appendChild(rect);
7.2 遍历与查询
// 遍历子节点 group.children.forEach(child => { // child 是 DisplayObject }); // 查找特定节点 const found = canvas.document.getElementById('target'); // 获取父子关系 circle.parentNode; // 父节点 group.firstChild; // 第一个子节点 circle.nextSibling; // 下一个兄弟节点 // 是否包含 group.contains(circle); // true
7.3 移除与销毁
// 从父节点移除 group.removeChild(circle); // 移除所有子节点 group.removeChildren(); // 销毁(移除 + 清理资源) circle.destroy();
8. 场景图事件
节点的增删改会触发对应事件:
| 事件 | 触发时机 |
|---|---|
DOMNodeInserted | 节点被添加到场景图 |
DOMNodeRemoved | 节点从场景图移除 |
DOMAttrModified | 节点属性被修改 |
destroy | 节点被销毁 |
circle.addEventListener('DOMNodeInserted', (e) => { // 当 circle 被添加到场景图时触发 }); circle.addEventListener('DOMAttrModified', (e) => { // 当 circle 的属性发生变化时触发 // e.attrName, e.prevValue, e.newValue });