news 2026/9/23 17:37:44

G6 图数据模型完全指南:GraphData 结构、数据 API 与最佳实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
G6 图数据模型完全指南:GraphData 结构、数据 API 与最佳实践
  • 数据可视化
  • 前端
  • 图表库

【免费下载链接】G6

♾ A Graph Visualization Framework in JavaScript.

项目地址:https://gitcode.com/gh_mirrors/g6/G6
点击查看免费下载

导读

G6 是一个以数据驱动的 JavaScript 图可视化框架,图数据的组织方式直接决定了图的渲染结果与交互行为。本文基于 packages/site/docs/manual/data.en.md 展开,系统讲解 G6 的GraphData数据结构(节点、边、Combo 三要素)、各字段的语义与类型约束、数据组织的推荐实践,并结合仓库源码剖析getDataaddDataupdateDataremoveData等数据 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 数据(可选) }

注意:实际源码中nodesedgescombos均为可选字段(以?标记),只有nodes在语义上是图的主体,边与 Combo 可以按需提供。

NodeData:节点数据

节点是图的基本构成单元,代表图中的实体(如人、系统、事件)。每个节点拥有唯一的id用于标识,同时可以携带数据(data)、样式(style)与状态(states)。

属性类型说明
id(必填)string节点唯一标识,用于区分不同节点
typestring节点类型,可以是内置节点类型或自定义节点类型
dataObject节点的自定义业务数据(如 name、description 等),可在样式映射中通过回调函数访问
styleObject节点样式,包括位置、尺寸、颜色等视觉属性
statesstring[]节点的初始状态,如 selected、active、hover 等
combostring|null节点所属 Combo 的 ID,用于组织层级关系;无归属时为 null
childrenstring[]子节点 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:边数据

边连接两个节点,代表节点之间的关系。每条边关联两个节点(sourcetarget),边本身同样可以携带数据、样式与状态。边的数据常用来表达逻辑关系,例如社交网络中的用户关联、流程图中的步骤流转。

属性类型说明
source(必填)string源节点 ID
target(必填)string目标节点 ID
idstring边的唯一标识;若不指定,将自动生成${source}-${target}格式的 ID
typestring边类型,可以是内置边类型或自定义边类型
dataObject边的自定义业务数据,可在样式映射中通过回调函数访问
styleObject边样式,包括描边颜色、线宽、箭头等
statesstring[]边的初始状态

示例:

{ "source": "alice", "target": "bob", "type": "line", "data": { "relationship": "friend", "strength": 5 }, "style": { "stroke": "green", "lineWidth": 2 }, "states": ["hover"] }

从源码看,EdgeDataidsourcetarget之外的字段均为可选(见 packages/g6/src/spec/data.ts),但sourcetarget为必填。边 ID 的自动生成规则(${source}-${target})在文档中明确给出,这意味着同一对节点间只允许存在一条不带显式id的边;若需要表达多条并行边,应显式指定不同的id(可参考测试数据集 packages/g6/tests/dataset/parallel-edges.json 与 element-edges.json 中的真实用法)。

ComboData:Combo 数据

Combo 用于将多个节点组织为一个逻辑单元,实现分层、分组或其他结构化目的。一个 Combo 可以包含子节点,也可以包含其他 Combo,从而形成嵌套结构。

属性类型说明
id(必填)stringCombo 的唯一标识
typestringCombo 类型,可以是内置 Combo 类型或自定义 Combo 类型
dataObjectCombo 的自定义业务数据,可在样式映射中通过回调函数访问
styleObjectCombo 样式
statesstring[]Combo 的初始状态
combostring|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),ComboDataNodeData被统一抽象为NodeLikeData,这正是两者可共享父/子层级 API 的原因。此外 G6 中“树形层级”与“Combo 层级”是两套相互独立的结构:树形层级由节点的children字段驱动,Combo 层级由combo字段驱动,相关 API(如getParentDatagetAncestorsData)需要通过hierarchy: 'tree' | 'combo'显式指定层级类型(见 packages/g6/src/types/data.ts)。

数据组织与最佳实践

为保证图渲染与交互的正确性,建议严格按 G6 的标准数据结构组织数据。文档明确给出两条核心建议:

  1. 避免使用与 G6 内部字段冲突的标识符:不要将业务字段命名为idtypestyle等 G6 保留字段,防止命名冲突;
  2. 将业务数据统一存放在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)在合并元素数据时,会将datastyle作为独立的二级对象进行浅合并({ ...originalData, ...modifiedData }),而顶层属性直接覆盖。也就是说,data字段天然被设计为可增量合并的业务数据容器,将业务数据放在此处可以享受更新合并带来的便利,而放在顶层则可能被整体覆盖。

通过数据 API 操作图数据

G6 提供了一系列数据 API 覆盖图数据从查询、修改到更新的完整生命周期(文档完整列表见 packages/site/docs/manual/data.en.md 的 API 小节,详细签名与示例见 packages/site/docs/api/data.en.md):

  • 查询getDatagetNodeDatagetEdgeDatagetComboData
  • 新增addDataaddNodeDataaddEdgeDataaddComboData
  • 更新updateDataupdateNodeDataupdateEdgeDataupdateComboData
  • 删除removeDataremoveNodeDataremoveEdgeDataremoveComboData

这些 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 需要先建立层级结构,而边依赖节点存在。此外新增元素后会自动计算zIndexcomputeZIndex),保证组合内层级、边与节点之间的绘制顺序正确。

仓库演示用例 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:更新时,原始数据与传入的修改数据会做浅合并——顶层属性(如typestates)直接覆盖,datastyle内部的二级字段按 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 时必须携带idPartialEdgeData<T>则要求携带source+targetid二选一(见 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_KEYCOMBO_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 的数据体系可以概括为三条主线:

  1. 结构GraphDatanodesedgescombos组成,三者分别对应节点、边与 Combo;节点与边分别以idsource/target为必填标识,业务数据统一存放在data字段中;
  2. API:查询(get*)、新增(add*)、更新(update*)、删除(remove*)四类数据 API 覆盖了图数据的完整生命周期,支持数组与函数式两种传参方式,更新操作采用局部合并语义;
  3. 实现:数据层由DataController统一管理,内部基于图模型(GraphLib)存储节点/边与树形结构,数据变更会生成变更记录并驱动重绘;setData采用差量 diff 以最小化重绘开销。

在此基础上,你可以进一步阅读 数据 API 完整参考 获取每个方法的签名、参数与返回值细节,或结合仓库中的 测试数据集 与 演示用例 查看真实场景下的数据组织方式。

  • 数据可视化
  • 前端
  • 图表库

【免费下载链接】G6

♾ A Graph Visualization Framework in JavaScript.

项目地址:https://gitcode.com/gh_mirrors/g6/G6
点击查看免费下载
上一篇:告别混乱代码:Vue.js项目的CursorRules配置实战指南
下一篇:Catch2测试优化:性能提升策略

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 17:37:40

手写实现布里渊区算法避坑:解决代码报错与性能瓶颈

手写实现布里渊区算法避坑:解决代码报错与性能瓶颈 复制来的布里渊区计算代码,一跑就报错,或者结果和教科书上的图对不上,调试半天找不到原因?这种“复制粘贴即翻车”的经历,在固体物理计算中太常见了。很多开发者直接套用GitHub上开源的示例,却忽略了输入数据的格式、单位制转换以及数值稳定性的处理,导致手…

作者头像 李华
网站建设 2026/9/23 17:37:30

资料分析真题手写实现:3个技巧让速度翻倍

资料分析真题手写实现:3个技巧让速度翻倍 面试被问原理答不上来,是多数开发者的噩梦。尤其是面对资料分析这类高频考点,光背公式不够,还得懂底层逻辑。今天聊聊资料分析真题手写实现中的性能优化,分享几个经过实战验证的最佳实践。 性能瓶颈:数据预处理拖垮整个流程…

作者头像 李华
网站建设 2026/9/23 17:37:28

BI学习资源大全:从官方文档到实战模板,一文搞定

经常有人问我&#xff1a;做BI这行&#xff0c;到底该去哪些网站找资料&#xff1f;说实话&#xff0c;这个问题我每次听到都觉得既简单又难答。简单是因为BI相关资源确实不少&#xff0c;Power BI、帆软BI、各类开源工具&#xff0c;随便一搜就是一大堆&#xff1b;难是因为大…

作者头像 李华
网站建设 2026/9/23 17:36:54

读懂世界上最神奇的3本书性能优化避坑指南

读懂世界上最神奇的3本书性能优化避坑指南 官方文档太长抓不住重点?别慌,这篇避坑指南帮你把《世界上最神奇的3本书》里的性能优化精髓,浓缩成能直接抄的代码。 性能瓶颈:你以为的慢,其实是假象…

作者头像 李华
网站建设 2026/9/23 17:36:50

b612下载避坑指南:3个技巧搞定实战项目

b612下载避坑指南:3个技巧搞定实战项目 官方文档翻了三遍还是没抓住重点?别慌。很多老手在接 实战项目 时,都卡在b612下载这一步,明明代码看着对,一运行就报错。其实问题往往出在版本兼容和环境配置上,而不是你不够聪明。…

作者头像 李华