ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

G6 自定义 Combo 完全指南:从继承 BaseCombo 到状态响应与生命周期

G6 自定义 Combo 完全指南:从继承 BaseCombo 到状态响应与生命周期 数据可视化前端图表库【免费下载链接】G6♾ A Graph Visualization Framework in JavaScript.项目地址https://gitcode.com/gh_mirrors/g6/G6点击查看免费下载自定义 Combo 是 G6Graph Visualization Framework in JavaScript中把业务分组语义如部门、集群、子图落地为可视化形态的关键手段。本文以 custom-combo.en.md 为骨架完整讲解 Combo 的基本组成、两种自定义路线、三步创建第一个自定义 Combo 的完整代码并深入 G6 源码剖析upsert、render、getShape、生命周期钩子与状态响应等底层原理帮助读者从能跑通示例进阶到能按业务自由定制。Combo 的基本组成与独有特性在动手之前先理解 G6 中一个完整 Combo 通常由哪些图形部件构成。文档给出的三部分如下keyCombo 的主体图形决定其轮廓形状例如圆形、矩形或本文示例中的六边形label文本标签通常用于展示 Combo 的名称或描述halo围绕主体图形绘制的光晕图形用于高亮、选中反馈等场景。除这三部分外BaseCombo还支持collapsedMarker收起标记与icon、badge、port等通用图形插槽。从 base-combo.ts 的BaseComboStyleProps可以看到Combo 样式还包含size展开默认大小、collapsedSize收起默认大小默认 32、padding内边距仅展开时生效、childrenNode与childrenData等专属属性。Combo 与普通节点不同具有以下四类特性包含性ContainmentCombo 可以包含节点和其他 Combo形成层级结构双状态Two States具有展开Expanded与收起Collapsed两种状态自适应尺寸Adaptive Size会根据内部元素自动计算合适的大小拖拽行为Drag Behavior支持整体拖拽也支持把元素拖入/拖出 Combo。这些特性在 base-combo.ts 中有直接体现getKeySize会依据childrenNode.length是否为零以及collapsed状态分别走getEmptyKeySize、getCollapsedKeySize或getExpandedKeySize其中展开尺寸由getContentBBox子元素包围盒 padding计算而来。两种自定义路线继承优先从零起步兜底文档明确指出创建自定义 Combo 有两条路线且需要根据场景选对路。路线一继承已有 Combo 类型推荐这是最常见的做法可选择继承以下类型之一BaseCombo源码base-combo.ts最基础的 Combo 类提供 Combo 的核心能力例如自适应尺寸、收起/展开、位置同步Circle源码circle.ts圆形 Combo其getExpandedKeySize使用Math.sqrt(width² height²) / 2让圆始终外接子元素包围盒Rect源码rect.ts矩形 Combo主图形为GRect展开尺寸直接等于子元素包围盒加 padding。选择继承路线的好处代码量少复用已有属性与方法只聚焦新特性、开发快适合绝大多数项目需求、易维护继承关系清晰、代码结构明确。提示如果你选择继承路线绝大多数用户都会这么做可以直接跳到下文三步创建第一个自定义 Combo章节开始动手实践。路线二基于 G 图形系统从零开发高级用法当内置类型与继承都无法满足需求时可以基于 G6 底层的 [G Graphics System] 从零绘制 Combo。这条路线带来最大自由度完全掌控每个细节、满足特殊需求内置类型无法覆盖的高定制场景、便于定向性能优化。警告从零开发需要自行处理图形绘制、事件响应、状态切换、展开/收起逻辑等全部细节挑战较大。可直接参考 base-combo.ts 的源码实现作为起点。三步创建第一个自定义 Combo六边形 Combo 实战下面以继承BaseCombo实现一个自定义六边形 Combo 为例走完完整的三个步骤。文档同时提供了可直接运行的完整示例包含Graph实例化与数据装配以及分步拆解的 TypeScript 实现这里先给出分步核心代码。Step 1编写自定义 Combo 类首先定义收起/展开按钮的 SVG 路径生成函数然后实现HexagonCombo类import { BaseCombo } from antv/g6; import type { BaseComboStyleProps } from antv/g6; // 收起状态按钮路径 const collapse (x, y, r) { return [ [M, x - r, y], [a, r, r, 0, 1, 0, r * 2, 0], [a, r, r, 0, 1, 0, -r * 2, 0], [M, x - r 4, y], [L, x r - 4, y], ]; }; // 展开状态按钮路径比收起状态多一条竖向短线 const expand (x, y, r) { return [ [M, x - r, y], [a, r, r, 0, 1, 0, r * 2, 0], [a, r, r, 0, 1, 0, -r * 2, 0], [M, x - r 4, y], [L, x - r 2 * r - 4, y], [M, x - r r, y - r 4], [L, x, y r - 4], ]; }; class HexagonCombo extends BaseCombo { // 计算六边形顶点并构造 SVG path protected getKeyPath(attributes: RequiredBaseComboStyleProps) { const [width, height] this.getKeySize(attributes); const padding 10; const size Math.min(width, height) padding; const points []; for (let i 0; i 6; i) { const angle (Math.PI / 3) * i; const x (size / 2) * Math.cos(angle); const y (size / 2) * Math.sin(angle); points.push([x, y]); } const path [[M, points[0][0], points[0][1]]]; for (let i 1; i 6; i) { path.push([L, points[i][0], points[i][1]]); } path.push([Z]); return path; } // 主体图形样式根据 collapsed 状态切换填充色与透明度 protected getKeyStyle(attributes: RequiredBaseComboStyleProps) { const style super.getKeyStyle(attributes); return { ...style, d: this.getKeyPath(attributes), fill: attributes.collapsed ? #FF9900 : #F04864, fillOpacity: attributes.collapsed ? 0.5 : 0.2, stroke: #54BECC, lineWidth: 2, }; } // 用 path 类型绘制主体图形 protected drawKeyShape(attributes: RequiredBaseComboStyleProps, container: Group) { return this.upsert(key, path, this.getKeyStyle(attributes), container); } // 绘制展开/收起按钮hit-area 扩大点击区域button 绘制图标 protected drawCollapseButton(attributes: RequiredBaseComboStyleProps) { const { collapsed } attributes; const [width] this.getKeySize(attributes); const btnR 8; const x width / 2 btnR; const d collapsed ? expand(x, 0, btnR) : collapse(x, 0, btnR); const hitArea this.upsert(hit-area, circle, { cx: x, r: 8, fill: #fff, cursor: pointer }, this); this.upsert(button, path, { stroke: #54BECC, d, cursor: pointer, lineWidth: 1.4 }, hitArea); } // 覆盖 render在基础渲染之上追加按钮 render(attributes, container) { super.render(attributes, container); this.drawCollapseButton(attributes, container); } // 生命周期钩子绑定点击事件切换展开/收起 onCreate() { this.shapeMap[hit-area].addEventListener(click, () { const id this.id; const collapsed !this.attributes.collapsed; const { graph } this.context; if (collapsed) graph.collapseElement(id); else graph.expandElement(id); }); } }实现要点getKeyPath使用this.getKeySize(attributes)拿到考虑收起状态与子元素的尺寸再按正六边形角度每Math.PI / 3一个顶点生成坐标并拼成 SVG pathgetKeyStyle调用super.getKeyStyle继承基础样式再叠加d路径数据、填充、描边等定制样式并利用attributes.collapsed实现状态相关的视觉差异drawKeyShape通过upsert(key, path, ...)以 path 图形渲染主体onCreate生命周期钩子中通过this.shapeMap[hit-area]取到按钮图形绑定 click 事件后调用graph.collapseElement(id)/graph.expandElement(id)完成展开/收起切换。Step 2注册自定义 Combo扩展注册只需一次即可在项目任意位置使用内置扩展在项目导入时自动注册自定义扩展需手动注册import { ExtensionCategory } from antv/g6; register(ExtensionCategory.COMBO, hexagon-combo, HexagonCombo);从 register.ts 的源码可以看到register(category, type, Ctor)会把扩展类写入EXTENSION_REGISTRY若同名扩展已被注册会打印警告并覆盖。ExtensionCategory.COMBO指定注册分类为 Combohexagon-combo是后续挂载到图上时使用的标识符。Step 3在 Graph 中应用自定义 Comboconst graph new Graph({ // ...其他配置 data: { nodes: [ { id: node1, combo: combo1, style: { x: 100, y: 100 } }, { id: node2, combo: combo1, style: { x: 150, y: 150 } }, { id: node3, combo: combo2, style: { x: 300, y: 100 } }, { id: node4, combo: combo2, style: { x: 350, y: 150 } }, ], combos: [ { id: combo1, data: { label: Hexagon 1 } }, { id: combo2, data: { label: Hexagon 2 }, style: { collapsed: true } }, ], }, combo: { type: hexagon-combo, // 使用注册时的名称 style: { padding: 20, showCollapseButton: true, labelText: (d) d.data?.label, labelPlacement: top, }, }, // 展开/收起逻辑已由我们自己实现这里只需拖拽行为 behaviors: [drag-element], }); graph.render();应用时的关键点combo.type必须与register时传入的类型名一致节点通过combo: combo1字段声明归属Combo 数据用data.label存放标签文本style.collapsed控制初始收起状态因为展开/收起按钮的交互已在onCreate中自行实现所以这里仅配置drag-element行为即可无需再挂collapse-expand行为。至此第一个自定义 Combo 就完成了。深入原理Combo 的绘制机制Combo 与 Node 的差异尽管 Combo 继承自BaseNode源码见 base-combo.ts二者仍有关键差异自适应尺寸Combo 根据内部元素自动计算尺寸展开/收起状态Combo 有双显示状态需要处理状态切换对应getKeySize中collapsed分支层级结构Combo 可嵌套形成层级关系测试用例 element-combo.ts 中combo-1就嵌在combo-2内内部元素管理Combo 需要管理其包含的节点与子 Combo通过childrenNode/childrenData属性。此外BaseCombo还做了两个细节处理update时调用updateComboPosition把计算出的坐标回写模型model.syncNodeLikeDatum收起状态下坐标取后代节点平均中心、展开状态下取内容包围盒中心见 base-combo.ts。原子图形Atomic GraphicsG6 的 Combo 使用 G 图形系统提供的原子图形单元绘制。所有图形都可以通过upsert()动态创建或更新并自动管理图形状态与生命周期。原子图形的详细说明可参考 Element - Shape 概览若该文件存在对应的文档章节。元素基类两个核心属性自定义 Combo 前需要了解元素基类中的两个重要属性属性类型说明shapeMapRecordstring, DisplayObject当前元素下所有图形的映射表animateMapRecordstring, IAnimation当前元素下所有动画的映射表shapeMap正是上例中this.shapeMap[hit-area]能取到按钮图形的原因——凡是通过upsert创建的图形都会被记录其中。upsert(name, Ctor, style, container)图形创建/更新upsert是 update or insert 的缩写负责在元素中新增或更新图形是自定义 Combo 时使用最频繁的方法upsert(key: string, Ctor: { new (...args: any[]): DisplayObject }, style: Recordstring, any, container: DisplayObject);参数类型说明keystring图形键名对应shapeMap中的键。内置键包括key、label、halo、icon、port、badge。键名不要使用特殊符号会转换为驼峰形式去调用getXxxStyle与drawXxxShape方法Ctor{ new (...args: any[]): DisplayObject }图形类如circle、rect、pathstyleRecordstring, any图形样式containerDisplayObject图形挂载的容器例如插入一个固定位置的紫色圆形this.upsert( element-key, // 元素唯一标识 circle, // 图形类型 { x: 100, y: 100, fill: #a975f3 }, // 样式配置对象 container, // 父容器 );为什么用upsert而不是直接container.appendChild()创建图形性能更好状态变化或数据更新时智能复用已有图形而不是删除重建显著提升渲染性能代码更简洁无需手动判断元素是否存在管理方便所有通过upsert创建的图形都会记录在shapeMap中可用this.getShape(key)轻松访问。render(attributes, container)Combo 渲染主入口每个自定义 Combo 类都必须实现render(attributes, container)方法它定义了 Combo 如何被绘制可在其中用各种原子图形组合出想要的形态render(style: Recordstring, any, container: Group): void;参数类型说明styleRecordstring, any元素样式containerGroup容器BaseCombo.render先调用super.render(attributes, container)绘制 key、label、halo 等基础图形再绘制 collapsed marker见 base-combo.ts自定义类在其后追加额外图形即可。getShape(name)获取已创建的图形创建之后可能需要修改子图形属性或让子图形之间产生交互此时可用getShape访问之前通过upsert创建的任意图形this.getShape(key);注意图形创建顺序很重要。如果图形 B 依赖图形 A 的位置务必确保 A 先创建。元素约定Element Conventions使用约定属性Combo 中的约定属性包括——this.getKeySize(attributes)获取 Combo 尺寸已考虑收起状态与子元素this.getContentBBox(attributes)获取内容区域包围盒this.getComboPosition(attributes)获取 Combo 当前位置基于状态与子元素计算。使用getXxxStyle与drawXxxShape成对绘制getXxxStyle负责获取图形样式drawXxxShape负责绘制图形这样创建的图形支持自动执行动画。其中Xxx是传给upsert的 key 的驼峰形式。例如六边形示例中的getKeyStyle/drawKeyShape对以及BaseCombo内部的getCollapsedMarkerStyle/drawCollapsedMarkerShape对。通过this.context访问图上下文context提供graph、model、element、animation等运行时能力例如按钮点击回调里通过const { graph } this.context拿到图实例后调用collapseElement/expandElement。BaseElement中context的定义见 base-element.ts。生命周期钩子在关键时刻插入自定义逻辑ElementHooks接口见 types/element.ts定义了以下生命周期钩子可在自定义 Combo 中覆写钩子函数触发时机典型用途onCreateCombo 创建完成且入场动画结束后绑定交互事件、初始化 Combo 状态、添加外部监听器onUpdateCombo 更新且过渡动画结束后更新依赖数据、调整相关元素、触发联动效果onDestroyCombo 销毁且退场动画结束后清理资源、移除外部监听器、执行销毁通知这些钩子在 runtime/element.ts 中被调用createElement在入场动画的after回调中触发AFTER_ELEMENT_CREATE事件并调用element.onCreate?.()onUpdate、onDestroy的触发时机与之对应更新/销毁动画完成后。状态响应把状态与绘制逻辑解耦G6 元素设计最强大的能力之一是把状态响应与绘制逻辑分离。你可以在 Combo 配置中为每个状态定义样式combo: { type: custom-combo, style: { fill: #f0f2f5, stroke: #d9d9d9 }, state: { selected: { stroke: #1890ff, lineWidth: 2, shadowColor: rgba(24,144,255,0.2), shadowBlur: 15, }, hover: { fill: #e6f7ff, }, }, }切换状态的方法graph.setElementState(comboId, [selected]);该状态会传入render()方法的attributes并经内部系统合并后自动应用到图形上。也可以在自定义类中根据状态编写渲染逻辑protected getKeyStyle(attributes: RequiredBaseComboStyleProps) { const style super.getKeyStyle(attributes); // 根据状态调整样式 if (attributes.states?.includes(selected)) { return { ...style, stroke: #1890ff, lineWidth: 2, shadowColor: rgba(24,144,255,0.2), shadowBlur: 15, }; } return style; }两种方式可结合使用配置化的state适合声明式、无需写代码的常见反馈在getKeyStyle中读取attributes.states适合需要联动多个子图形或做复杂分支逻辑的场景。进一步探索阅读 Combo 元素基础文档若该文件存在了解内置圆形/矩形 Combo 的完整配置在源码 packages/g6/src/elements/combos 下对比circle.ts与rect.ts的实现理解两种内置 Combo 如何在drawKeyShape中复用upsert运行测试用例 element-combo.ts 观察嵌套 Combo、标签与虚线样式的实际效果查看 registry/register.ts 与 registry/store.ts 了解扩展注册表的实现以及ExtensionCategory支持的全部扩展分类。赞分享数据可视化前端图表库【免费下载链接】G6♾ A Graph Visualization Framework in JavaScript.项目地址https://gitcode.com/gh_mirrors/g6/G6点击查看免费下载相关推荐G6 自定义 Combo 完整实战指南从继承 BaseCombo 到掌握绘制原理G6 自定义 Combo 完整实战指南从继承 BaseCombo 到掌握绘制原理 本篇指南聚焦于 G6 GitHub 加速计划 / g6 / G6 http数据可视化前端图表库Alpine.js 生命周期完全指南从元素初始化到状态响应与 Alpine 启动钩子Alpine.js 生命周期完全指南从元素初始化到状态响应与 Alpine 启动钩子 生命周期钩子是 Alpine.js 中在正确的时机做正确的事的关键机前端G6 Combo组合元素完全指南从数据建模到交互与自定义G6 Combo组合元素完全指南从数据建模到交互与自定义 G6 中的 Combo组合是一种特殊的图元素用于将节点与子 Combo 组织成层级容器常数据可视化前端图表库上一篇Joplin 笔记导入导出完全实战指南ENEX、Markdown、OneNote 到 JEX 备份下一篇ZLMediaKit音视频录制系统FLV/HLS/MP4格式的存储策略与实现创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进