- 数据可视化
- 前端
- 图表库
【免费下载链接】G6
♾ A Graph Visualization Framework in JavaScript.
导读
G6 是一个以数据驱动的 JavaScript 图可视化框架,图数据的组织方式直接决定了图的渲染结果与交互行为。本文基于 packages/site/docs/manual/data.en.md 展开,系统讲解 G6 的GraphData数据结构(节点、边、Combo 三要素)、各字段的语义与类型约束、数据组织的推荐实践,并结合仓库源码剖析getData、addData、updateData、removeData等数据 API 的底层实现与调用链。读完本文,你将掌握如何用标准 JSON 描述一张图、如何正确存放业务数据以避免与 G6 内部字段冲突,以及如何通过数据 API 对图执行增删改查并理解其内部合并与变更机制。
数据驱动的图:为什么数据是 G6 的核心
G6 是典型的数据驱动(data-driven)图可视化库,数据是图的“唯一事实来源”。在 G6 中,展示和交互都基于数据:节点的位置、大小、颜色,边的样式,Combo 的折叠状态等,全部由数据经样式映射推导而来;用户拖拽、框选等行为产生的变更,最终也会写回数据模型。因此,理解 G6 的数据结构是使用 G6 的第一步,也是后续自定义扩展、性能优化的基础。
常见的图数据交换格式包括 CSV、DOT、GDF、GML、GraphML、GEXF 等,而 G6 选用JSON来描述图结构。JSON 相比上述格式的优势在于:
- 结构直观:节点与边的信息以键值对形式平铺表达,可读性强;
- 扩展灵活:可以随时为节点或边追加任意自定义属性;
- 兼容性好:JSON 是计算机世界广泛支持的数据交换格式,无需担心格式兼容问题。
一个最简的 G6 图数据如下:
{ "nodes": [{ "id": "node1" }, { "id": "node2" }], "edges": [{ "source": "node1", "target": "node2" }] }GraphData 数据结构:节点、边与 Combo
在 G6 中,一张图的数据由三部分组成:nodes(节点数据)、edges(边数据)与combos(Combo 数据),三者分别对应图中的不同元素,其类型与取值共同决定图的呈现方式。
源码中的类型定义(见 packages/g6/src/spec/data.ts)如下:
interface GraphData { nodes?: NodeData[]; // 节点数据 edges?: EdgeData[]; // 边数据(可选) combos?: ComboData[]; // Combo 数据(可选) }注意:实际源码中nodes、edges、combos均为可选字段(以?标记),只有nodes在语义上是图的主体,边与 Combo 可以按需提供。
NodeData:节点数据
节点是图的基本构成单元,代表图中的实体(如人、系统、事件)。每个节点拥有唯一的id用于标识,同时可以携带数据(data)、样式(style)与状态(states)。
| 属性 | 类型 | 说明 |
|---|---|---|
| id(必填) | string | 节点唯一标识,用于区分不同节点 |
| type | string | 节点类型,可以是内置节点类型或自定义节点类型 |
| data | Object | 节点的自定义业务数据(如 name、description 等),可在样式映射中通过回调函数访问 |
| style | Object | 节点样式,包括位置、尺寸、颜色等视觉属性 |
| states | string[] | 节点的初始状态,如 selected、active、hover 等 |
| combo | string|null | 节点所属 Combo 的 ID,用于组织层级关系;无归属时为 null |
| children | string[] | 子节点 ID 集合,仅用于树图结构 |
示例:
{ "id": "node-1", "type": "circle", "data": { "name": "alice", "role": "Admin" }, "style": { "x": 100, "y": 200, "size": 32, "fill": "violet" }, "states": ["selected"], "combo": null }对照源码 packages/g6/src/spec/data.ts,NodeData还包含depth?: number(节点深度,仅用于树图)以及[key: string]: unknown索引签名。索引签名的存在意味着 G6 允许你在节点上直接挂载任意自定义字段——但从官方最佳实践看,业务数据仍应放进data字段,以保证与样式映射、数据比较等内部机制的兼容。
EdgeData:边数据
边连接两个节点,代表节点之间的关系。每条边关联两个节点(source与target),边本身同样可以携带数据、样式与状态。边的数据常用来表达逻辑关系,例如社交网络中的用户关联、流程图中的步骤流转。
| 属性 | 类型 | 说明 |
|---|---|---|
| source(必填) | string | 源节点 ID |
| target(必填) | string | 目标节点 ID |
| id | string | 边的唯一标识;若不指定,将自动生成${source}-${target}格式的 ID |
| type | string | 边类型,可以是内置边类型或自定义边类型 |
| data | Object | 边的自定义业务数据,可在样式映射中通过回调函数访问 |
| style | Object | 边样式,包括描边颜色、线宽、箭头等 |
| states | string[] | 边的初始状态 |
示例:
{ "source": "alice", "target": "bob", "type": "line", "data": { "relationship": "friend", "strength": 5 }, "style": { "stroke": "green", "lineWidth": 2 }, "states": ["hover"] }从源码看,EdgeData中id、source、target之外的字段均为可选(见 packages/g6/src/spec/data.ts),但source与target为必填。边 ID 的自动生成规则(${source}-${target})在文档中明确给出,这意味着同一对节点间只允许存在一条不带显式id的边;若需要表达多条并行边,应显式指定不同的id(可参考测试数据集 packages/g6/tests/dataset/parallel-edges.json 与 element-edges.json 中的真实用法)。
ComboData:Combo 数据
Combo 用于将多个节点组织为一个逻辑单元,实现分层、分组或其他结构化目的。一个 Combo 可以包含子节点,也可以包含其他 Combo,从而形成嵌套结构。
| 属性 | 类型 | 说明 |
|---|---|---|
| id(必填) | string | Combo 的唯一标识 |
| type | string | Combo 类型,可以是内置 Combo 类型或自定义 Combo 类型 |
| data | Object | Combo 的自定义业务数据,可在样式映射中通过回调函数访问 |
| style | Object | Combo 样式 |
| states | string[] | Combo 的初始状态 |
| combo | string|null | 父 Combo 的 ID;无父 Combo 时为 null |
示例:
{ "id": "combo1", "type": "circle", "data": { "groupName": "Group A" }, "style": { "fill": "lightblue", "stroke": "blue", "collapsed": true }, "states": [], "combo": null }Combo 在源码中同样以图节点(Node)的形式存储于图模型,通过内部的comboIds集合与 Combo 树结构(COMBO_KEY)加以区分(见 packages/g6/src/runtime/data.ts 中DataController的实现)。从类型看(packages/g6/src/types/data.ts),ComboData与NodeData被统一抽象为NodeLikeData,这正是两者可共享父/子层级 API 的原因。此外 G6 中“树形层级”与“Combo 层级”是两套相互独立的结构:树形层级由节点的children字段驱动,Combo 层级由combo字段驱动,相关 API(如getParentData、getAncestorsData)需要通过hierarchy: 'tree' | 'combo'显式指定层级类型(见 packages/g6/src/types/data.ts)。
数据组织与最佳实践
为保证图渲染与交互的正确性,建议严格按 G6 的标准数据结构组织数据。文档明确给出两条核心建议:
- 避免使用与 G6 内部字段冲突的标识符:不要将业务字段命名为
id、type、style等 G6 保留字段,防止命名冲突; - 将业务数据统一存放在
data字段:如用户信息、社交关系等,存放在data中可保证数据的灵活性与可扩展性,同时不干扰 G6 的元素属性解析。
推荐示例:
{ "nodes": [ { "id": "node1", "data": { "name": "Alice", "role": "Admin" } }, { "id": "node2", "data": { "name": "Bob", "role": "User" } } ], "edges": [ { "source": "node1", "target": "node2", "data": { "relationship": "friend" } } ] }这一建议与源码实现高度吻合:mergeElementsData(见 packages/g6/src/utils/data.ts)在合并元素数据时,会将data与style作为独立的二级对象进行浅合并({ ...originalData, ...modifiedData }),而顶层属性直接覆盖。也就是说,data字段天然被设计为可增量合并的业务数据容器,将业务数据放在此处可以享受更新合并带来的便利,而放在顶层则可能被整体覆盖。
通过数据 API 操作图数据
G6 提供了一系列数据 API 覆盖图数据从查询、修改到更新的完整生命周期(文档完整列表见 packages/site/docs/manual/data.en.md 的 API 小节,详细签名与示例见 packages/site/docs/api/data.en.md):
- 查询:
getData、getNodeData、getEdgeData、getComboData - 新增:
addData、addNodeData、addEdgeData、addComboData - 更新:
updateData、updateNodeData、updateEdgeData、updateComboData - 删除:
removeData、removeNodeData、removeEdgeData、removeComboData
这些 API 以graph.xxx()的形式暴露,内部委托给运行时数据控制器(DataController,见 packages/g6/src/runtime/data.ts)执行,并触发后续的变更事件与重绘。
查询数据
graph.getData()返回包含全部节点、边、Combo 的完整图数据(对应实现见 packages/g6/src/runtime/graph.ts,其内部调用DataController.getData,见 packages/g6/src/runtime/data.ts):
const graphData = graph.getData(); console.log('Node data:', graphData.nodes); console.log('Edge data:', graphData.edges); console.log('Combo data:', graphData.combos);getNodeData/getEdgeData/getComboData均支持三种调用方式:无参取全部、传入单个 ID、传入 ID 数组。例如:
// 获取所有节点 const nodes = graph.getNodeData(); // 获取单个节点 const node = graph.getNodeData('node1'); console.log('Node position:', node.style.x, node.style.y); // 获取多个节点 const [node1, node2] = graph.getNodeData(['node1', 'node2']);新增数据
addData用于一次性添加多种元素,addNodeData/addEdgeData/addComboData则分别针对单一元素类型。所有方法都支持传入数组或返回数组的函数式写法:
graph.addData({ nodes: [{ id: 'node-1' }, { id: 'node-2' }], edges: [{ source: 'node-1', target: 'node-2' }], }); // 添加带样式与业务数据的节点 graph.addNodeData([ { id: 'node1', style: { x: 100, y: 100 }, data: { label: 'Node 1' } }, ]); // 函数式新增 graph.addNodeData((prev) => [...prev, { id: 'node4', style: { x: 400, y: 400 } }]);从源码实现看(packages/g6/src/runtime/data.ts),addData内部会先添加 Combo、再添加节点、最后添加边:这是因为 Combo 需要先建立层级结构,而边依赖节点存在。此外新增元素后会自动计算zIndex(computeZIndex),保证组合内层级、边与节点之间的绘制顺序正确。
仓库演示用例 animation-element-style-position.ts 展示了新增、删除、更新节点的组合用法:
graph.addNodeData([ { id: 'node-4', style: { x: 50, y: 200, fill: 'orange' } }, { id: 'node-5', style: { x: 75, y: 150, fill: 'purple' } }, { id: 'node-6', style: { x: 200, y: 100, fill: 'cyan' } }, ]); graph.removeNodeData(['node-1']); graph.updateNodeData([{ id: 'node-2', style: { x: 200, y: 200, stroke: 'green' } }]); graph.draw();更新数据
更新类 API 遵循“只需传入需要更新的数据,无需传入完整数据”的原则(文档中以 ⚠️ 特别标注):
graph.updateData({ nodes: [{ id: 'node-1', style: { x: 100, y: 100 } }], edges: [{ id: 'edge-1', style: { lineWidth: 2 } }], }); // 更新单个节点 graph.updateNodeData([{ id: 'node-1', style: { x: 100, y: 100 } }]); // 更新边的样式 graph.updateEdgeData([{ id: 'edge-1', style: { lineWidth: 2 } }]);这一“局部更新”语义的实现关键在于mergeElementsData:更新时,原始数据与传入的修改数据会做浅合并——顶层属性(如type、states)直接覆盖,data与style内部的二级字段按 key 增量合并(见 packages/g6/src/utils/data.ts)。同时,updateNodeData在合并前会通过isElementDataEqual做相等性判断(见 packages/g6/src/utils/data.ts),数据未变化时直接跳过,避免无意义的变更事件与重绘。更新边的source/target时,数据层会调用model.updateEdgeSource/model.updateEdgeTarget同步图模型中的拓扑关系(见 packages/g6/src/runtime/data.ts)。
从类型层面看,PartialNodeLikeData<T>要求更新节点/Combo 时必须携带id,PartialEdgeData<T>则要求携带source+target或id二选一(见 packages/g6/src/types/data.ts)。
删除数据
删除 API 传入需要删除元素的 ID(或返回 ID 的函数):
graph.removeData({ nodes: ['node-1', 'node-2'], edges: ['edge-1'] }); graph.removeNodeData(['node-1', 'node-2']); graph.removeEdgeData(['edge-1']); graph.removeComboData(['combo-1']);值得注意的是,removeData内部采用“先删边、再删节点、最后删 Combo”的顺序(见 packages/g6/src/runtime/data.ts),且删除节点时会顺带移除与之关联的边(removeEdgeData(this.getRelatedEdgesData(id).map(idOf)),见 packages/g6/src/runtime/data.ts)。删除 Combo 后,其子节点会被提升到父 Combo 或根层级(removeNodeLikeHierarchy,见 packages/g6/src/runtime/data.ts),避免出现“孤儿节点”。
更多查询类 API
除上述增删改查外,G6 还提供面向关系与层级查询的数据 API(签名与示例详见 packages/site/docs/api/data.en.md):
getElementData(id):不关心元素类型,直接按 ID 获取元素数据;getElementDataByState(elementType, state):获取处于指定状态(如'selected'、'active'、'highlight'、'inactive'、'disabled')的元素数据;getNeighborNodesData(id):获取节点/Combo 的邻居节点数据;getRelatedEdgesData(id, direction?):获取与节点/Combo 关联的边数据,direction支持'in' | 'out' | 'both';getParentData(id, hierarchy)/getChildrenData(id)/getAncestorsData(id, hierarchy)/getDescendantsData(id):按'tree' | 'combo'层级获取父子与祖先/后代数据。
这些方法的底层实现均可在 packages/g6/src/runtime/data.ts 中找到,例如getChildrenData会根据元素类型自动选择TREE_KEY或COMBO_KEY对应的树结构(packages/g6/src/runtime/data.ts)。
使用远程数据
G6 本身不提供数据获取与解析能力,数据需要由业务侧准备好再交给图实例。
对于本地 JSON 数据,可以直接导入使用:
import data from './path/to/data.json' assert { type: 'json' };对于远程数据,使用fetch或其他网络库获取后传入:
fetch('https://path/to/data.json') .then((res) => res.json()) .then((data) => { // 使用 data });获取数据后,既可以在实例化Graph时通过data配置项传入(仓库演示 common-graph.ts 即为标准写法),也可以在渲染前或运行中调用graph.setData(data)进行设置。setData同样支持函数式写法,便于基于当前数据做增量调整:
graph.setData({ nodes: [ { id: 'node1', style: { x: 100, y: 100 } }, { id: 'node2', style: { x: 200, y: 200 } }, ], edges: [{ id: 'edge1', source: 'node1', target: 'node2' }], }); // 函数式更新:基于当前数据追加节点 graph.setData((prev) => ({ ...prev, nodes: [...prev.nodes, { id: 'node3', style: { x: 300, y: 300 } }], }));从源码看,setData的实现(见 packages/g6/src/runtime/data.ts)会对新旧数据按 ID 做差量计算(arrayDiff),分别得到需要新增(enter)、更新(update)与删除(exit)的元素,再在同一个批处理(batch)内依次执行,从而避免多余的重绘开销。
状态(states)在数据中的角色
节点的states字段声明元素的初始状态,G6 内置了'selected'、'active'、'highlight'、'inactive'、'disabled'等状态(见 packages/site/docs/api/data.en.md 的 Built-in States 一节),状态对应的视觉表现由主题(theme)与元素样式定义决定。仓库测试数据集 element-nodes.json 中可以看到真实的状态声明用法:
{ "id": "active", "states": ["active"] }, { "id": "selected", "states": ["selected"] }, { "id": "highlight", "states": ["highlight"] }, { "id": "inactive", "states": ["inactive"] }, { "id": "disabled", "states": ["disabled"] }运行时可通过updateNodeData/updateEdgeData等 API 修改元素的states来驱动状态切换,例如仓库中的树图用例会通过graph.updateNodeData([{ id: targetId, states: ['selected'] }])高亮选中节点(见 case-indented-tree.ts)。
小结
G6 的数据体系可以概括为三条主线:
- 结构:
GraphData由nodes、edges、combos组成,三者分别对应节点、边与 Combo;节点与边分别以id和source/target为必填标识,业务数据统一存放在data字段中; - API:查询(
get*)、新增(add*)、更新(update*)、删除(remove*)四类数据 API 覆盖了图数据的完整生命周期,支持数组与函数式两种传参方式,更新操作采用局部合并语义; - 实现:数据层由
DataController统一管理,内部基于图模型(GraphLib)存储节点/边与树形结构,数据变更会生成变更记录并驱动重绘;setData采用差量 diff 以最小化重绘开销。
在此基础上,你可以进一步阅读 数据 API 完整参考 获取每个方法的签名、参数与返回值细节,或结合仓库中的 测试数据集 与 演示用例 查看真实场景下的数据组织方式。
- 数据可视化
- 前端
- 图表库
【免费下载链接】G6
♾ A Graph Visualization Framework in JavaScript.
相关推荐
开发者指南:基于Inkling-Small-mlx-4bit构建自定义多模态应用的完整流程
开发者指南:基于Inkling Small mlx 4bit构建自定义多模态应用的完整流程 Inkling Small mlx 4bit是一款专为Apple S
数据结构选择:harmony-utils中数据结构的最佳实践
数据结构选择:harmony utils中数据结构的最佳实践 痛点:鸿蒙开发中的数据管理困境 你还在为鸿蒙应用开发中的数据管理而烦恼吗?面对复杂的业务场景,如何
OpenHarmonyCrowbar高级应用:掌握GoldSource和Source引擎模型处理的5个专业工作流
Crowbar高级应用:掌握GoldSource和Source引擎模型处理的5个专业工作流 Crowbar是一款专为GoldSource和Source引擎设计的
游戏开发开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考