logo

G6

  • 文档
  • API
  • 图表示例
  • 社区
  • 所有产品antv logo arrow
  • 5.0.46
  • 简介
  • 开始使用
    • 快速开始
    • 安装
    • 前端框架集成
      • 在 React 中使用
      • 在 Vue 中使用
      • 在 Angular 中使用
    • 详细教程
  • 图 Graph
    • Graph - 图
    • Options 配置项
    • 内置扩展
    • Extension - 扩展
  • 数据 Data
  • 元素 Element
    • 元素总览
    • 元素状态
    • 节点 Node
      • 节点总览
      • 内置节点
        • 节点通用配置项
        • Diamond 菱形
        • Donut 甜甜圈
        • Ellipse 椭圆形
        • Hexagon 六边形
        • Html HTML
        • Image 图片
        • Rect 矩形
        • Star 五角形
        • Triangle 三角形
        • Circle 圆形
      • 自定义节点
      • 使用 React 定义节点
    • 边 Edge
      • 边总览
      • 内置边
        • 边通用配置项
        • Cubic 三次贝塞尔曲线
        • CubicHorizontal 水平三次贝塞尔曲线
        • CubicVertical 垂直三次贝塞尔曲线
        • Line 直线
        • Polyline 折线
        • Quadratic 二次贝塞尔曲线
      • 自定义边
    • 组合 Combo
      • 组合总览
      • 内置组合
        • 组合配置项
        • Circle 圆形
        • Rect 矩形
      • 自定义 Combo
    • 图形 Shape
      • 图形 Shape 与 KeyShape
      • 原子 Shape 以及其属性
      • 复合 Shape 的设计与实现
  • 布局 Layout
    • 布局总览
    • 内置布局
      • 布局通用配置项
      • AntvDagre 布局
      • Circular 环形布局
      • ComboCombined 复合布局
      • CompactBox 紧凑树
      • Concentric 同心圆布局
      • D3Force 力导向布局
      • D3Force3D 3D 力导向布局
      • Dagre 布局
      • Dendrogram 生态树
      • Fishbone 鱼骨布局
      • Force 力导向布局
      • ForceAtlas2 力导向布局
      • Fruchterman 力导向布局
      • Grid 网格布局
      • Indented 缩进树
      • MDS 高维数据降维布局
      • Mindmap 脑图树
      • Radial 径向布局
      • Random 随机布局
      • Snake 蛇形布局
    • 自定义布局
  • 交互 Behavior
    • 交互总览
    • 内置交互
      • AutoAdaptLabel 标签自适应显示
      • BrushSelect 框选
      • ClickSelect 点击选中
      • CollapseExpand 展开/收起元素
      • CreateEdge 创建边
      • DragCanvas 拖拽画布
      • DragElement 拖拽元素
      • DragElementForce 力导向拖拽元素
      • FixElementSize 缩放画布时固定元素大小
      • FocusElement 聚焦元素
      • HoverActivate 悬停激活
      • LassoSelect 套索选择
      • OptimizeViewportTransform 优化视口变换
      • ScrollCanvas 滚动画布
      • ZoomCanvas 缩放画布
    • 自定义交互
  • 插件 Plugin
    • 插件总览
    • 内置插件
      • Background 背景
      • BubbleSets 气泡集
      • Contextmenu 上下文菜单
      • EdgeBundling 边绑定
      • EdgeFilterLens 边过滤镜
      • Fisheye 鱼眼放大镜
      • Fullscreen 全屏展示
      • GridLine 网格线
      • History 历史记录
      • Hull 轮廓包围
      • Legend 图例
      • Minimap 小地图
      • Snapline 对齐线
      • Timebar 时间条
      • Toolbar 工具栏
      • Tooltip 提示框
      • Watermark 水印
    • 自定义插件
  • 数据处理 Transform
    • 数据处理总览
    • 内置数据处理
      • MapNodeSize 动态调整节点大小
      • PlaceRadialLabels 径向标签
      • ProcessParallelEdges 平行边
    • 自定义数据处理
  • 主题 Theme
    • 主题总览
    • 自定义主题
    • 色板
    • 自定义色板
  • 动画 Animation
    • 动画总览
    • 自定义动画
  • 扩展阅读
    • 事件
    • 渲染器
    • 坐标系
    • 下载图片
    • 使用 iconfont
    • 使用 3D
    • 项目打包
  • 版本特性
    • 新版本特性
    • 升级到 5.0
  • 常见问题
  • 参与贡献

复合 Shape 的设计与实现

上一篇
原子 Shape 以及其属性
下一篇
布局总览

Resources

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...

G6 提供了灵活的 Shape 机制,支持开发者自定义各种图形,并在节点、边、Combo 等元素中高效复用。本文将以 Label(标签)为例,讲解如何自定义 Shape、如何在元素中应用。

一、Shape 的自定义与封装

1. Shape 的基类设计

所有 Shape 都继承自 BaseShape,它统一管理了 Shape 的生命周期(创建、更新、销毁)、属性解析、动画、事件绑定等。你只需关注如何实现 render 方法即可。

核心抽象:

import { CustomElement } from '@antv/g';
abstract class BaseShape extends CustomElement {
// 生命周期管理、属性解析、动画等...
public abstract render(attributes, container): void;
}

2. 复合 Shape 层级结构示意

一个节点通常包含多个子 Shape,例如:

节点(Node)
├── keyShape(主图形)
├── label(标签,辅助信息)
│ ├── text(文本)
│ └── rect(背景)
├── icon(图标)
│ ├── text(文本)
│ └── image(图片)
├── badge(徽标)
│ ├── text(文本)
│ └── rect(背景)
└── port(锚点)
│ ├── circle(圆形)

3. Label Shape 的实现

Label 是一个典型的复合 Shape,由文本(Text)和可选的背景(Rect)组成。其实现思路如下:

  • 属性分离:Label 的样式属性分为文本样式和背景样式,分别传递给 Text 和 Rect。
  • 智能布局:背景自动根据文本内容、内边距、圆角等动态调整尺寸和位置。
  • 复用 upsert:通过 upsert 方法自动管理子 Shape 的创建、更新、销毁。

Label 主要代码片段:

import { Text, Rect } from '@antv/g'; // 引入原子图形
export class Label extends BaseShape {
public render(attributes = this.parsedAttributes, container= this): void {
this.upsert('text', Text, this.getTextStyle(attributes), container);
this.upsert('background', Rect, this.getBackgroundStyle(attributes), container);
}
// ... 省略样式提取方法
}
  • getTextStyle、getBackgroundStyle 分别提取文本和背景的样式属性,避免相互干扰。
  • upsert 方法保证了 Shape 的自动增删改查,极大提升了复用性和健壮性。

4. 完整自定义 Shape 示例

下面以自定义一个带特殊装饰的标签为例,演示 Shape 的完整定义、注册与使用:

import { BaseShape, ExtensionCategory, Circle } from 'g6';
import { Text, Rect, Circle } from '@antv/g';
class FancyLabel extends BaseShape {
render(attributes = this.parsedAttributes, container = this) {
// 主文本
this.upsert('text', Text, this.getTextStyle(attributes), container);
// 背景
this.upsert('background', Rect, this.getBackgroundStyle(attributes), container);
// 额外装饰:左侧小圆点
this.upsert('dot', Circle, {
x: -8, y: 0, r: 3, fill: '#faad14',
}, container);
}
// ...实现 getTextStyle/getBackgroundStyle
}
// 注册自定义 Shape
register(ExtensionCategory.SHAPE, 'fancy-label-shape', FancyLabel);
// 定义自定义节点
class CustomCircle extends Circle {
public drawFancyLabelShape(attributes, container) {
this.upsert('fancy-label', 'fancy-label-shape', this.getFancyLabelStyle(attributes), container);
}
render(attributes = this.parsedAttributes, container) {
super.render(attributes, container);
this.drawFancyLabelShape(attributes, container);
}
}
// 注册自定义节点
register(ExtensionCategory.Node, 'fancy-label-node', CustomCircle);

二、样式属性的前缀分离

G6 中节点、边、Combo 等元素往往包含多个子 Shape(如主图形、标签、徽标、锚点等)。为了让每个子 Shape 的样式互不干扰,G6 采用了样式属性前缀分离的设计。

1. 前缀分离的意义

  • 解耦:每个子 Shape 只关心属于自己的样式属性,避免样式污染。
  • 易扩展:新增子 Shape 只需定义新的前缀,无需修改原有逻辑。
  • 配置直观:用户在配置节点/边/Combo 时,可以一目了然地设置各部分样式。

2. 代码实现

以 Label 为例:

import { RectStyleProps, TextStyleProps } from '@antv/g';
type PrefixKey<P extends string = string, K extends string = string> = `${P}${Capitalize<K>}`;
type Prefix<P extends string, T extends object> = {
[K in keyof T as K extends string ? PrefixKey<P, K> : never]?: T[K];
};
interface LabelStyleProps extends TextStyleProps, Prefix<'background', RectStyleProps> {
background?: boolean;
}
  • Prefix<'background', RectStyleProps> 表示所有以 background 开头的属性都属于标签背景样式。
  • 在实际渲染时,通过 subStyleProps、subObject 等工具函数,自动提取带前缀的样式,传递给对应的 Shape。

Label 背景样式提取示例

protected getBackgroundStyle(attributes: Required<LabelStyleProps>) {
if (attributes.background === false) return false;
const style = this.getGraphicStyle(attributes);
const backgroundStyle = subStyleProps<RectStyleProps>(style, 'background');
// ...省略布局计算
return backgroundStyle;
}

样式配置示例

{
"text": "label",
"fontSize": 12,
"fontFamily": "system-ui, sans-serif",
"wordWrap": true,
"maxLines": 1,
"wordWrapWidth": 128,
"textOverflow": "...",
"textBaseline": "middle",
"background": true,
"backgroundOpacity": 0.75,
"backgroundZIndex": -1,
"backgroundLineWidth": 0
}

三、Label 与 keyShape 的关系

  • keyShape 是节点/边/Combo 的主图形,决定交互拾取、包围盒、主样式等。
  • Label、icon、badge、port 等通常作为辅助 Shape 存在,不会作为 keyShape。
  • 你可以在自定义节点时通过 drawKeyShape 或类似方法指定 keyShape,Label 只负责展示文本信息,不影响节点的交互主控。

四、如何在元素中应用自定义 Shape

以节点为例,节点基类 BaseNode 已经内置了对多种子 Shape 的支持(keyShape、label、icon、badge、port、halo 等)。你只需专注于 keyShape 的绘制,其他子 Shape 可以通过配置和样式前缀自动管理。

1. 节点渲染流程

protected drawLabelShape(attributes: Required<S>, container: Group): void {
const style = this.getLabelStyle(attributes);
this.upsert('label', Label, style, container);
}
public render(attributes = this.parsedAttributes, container: Group = this) {
// 1. 绘制 keyShape(主图形)
this._drawKeyShape(attributes, container);
if (!this.getShape('key')) return;
// 2. 绘制 halo
this.drawHaloShape(attributes, container);
// 3. 绘制 icon
this.drawIconShape(attributes, container);
// 4. 绘制 badges
this.drawBadgeShapes(attributes, container);
// 5. 绘制 label
this.drawLabelShape(attributes, container);
// 6. 绘制 ports
this.drawPortShapes(attributes, container);
}
  • 每个子 Shape 的样式都通过前缀分离自动提取,传递给对应的 Shape 实例。
  • 你可以通过配置项灵活控制每个子 Shape 的显示与样式。

2. 应用 Label 的示例

假设你要为节点添加带背景的标签,只需在节点数据中配置 label 相关属性:

{
label: true,
labelText: '我是标签',
labelFill: '#333',
labelFontSize: 14,
labelBackground: true,
labelBackgroundFill: '#fffbe6',
labelBackgroundRadius: 6,
labelPadding: [4, 8],
}
  • labelText、labelFill、labelFontSize 等会被自动提取并传递给 Label 的文本部分。
  • labelBackground、labelBackgroundFill、labelBackgroundRadius、labelPadding 等会被自动提取并传递给 Label 的背景部分。

你无需手动管理 Label 的创建、更新、销毁,G6 会自动完成。

五、常见问题与调试建议

1. 为什么 label 样式没有生效?

  • 检查样式属性前缀是否正确(如 labelFill、labelBackgroundFill)。
  • 确认节点/边/Combo 的 label 配置为 true,且 labelText 已设置。
  • 检查是否被其他样式覆盖。

2. 如何调试自定义 Shape 的渲染?

  • 使用浏览器控制台查看 shapeMap,确认各子 Shape 是否被正确创建。

3. 如何让 Label 响应节点状态(如 hover、selected)?

  • 直接在图配置中设置节点状态样式(推荐)
const graph = new Graph({
node: {
style: {
label: false,
},
state: {
hover: {
label: true,
labelText: 'show when hovered',
},
},
},
});
  • 或者在 Label 的实现中监听状态变化,动态调整样式。可以通过 data 获取到当前的状态值

如需更深入了解,建议阅读源码 base-shape.ts、base-node.ts。