---
title: "气泡集 BubbleSets"
description: "气泡集 BubbleSets"
language: "zh"
canonical: "https://g6.antv.antgroup.com/zh/manual/plugin/BubbleSets/"
version: "5.1.1"
---

## 概述

BubbleSets 插件通过创建气泡形状来表示集合及其关系，帮助用户直观地理解集合间的交集、并集等逻辑关系。它是一种增强数据可视化效果的工具，特别适用于展示复杂的数据集合关系。

## 使用场景

BubbleSets 插件主要适用于以下场景：

- 展示集合间的关系（如交集、并集）
- 增强数据可视化的表达能力
- 在复杂网络图中标识特定节点或边的集合

## 基本用法

以下是一个简单的 BubbleSets 插件初始化示例：

```js
const graph = new Graph({
  plugins: [
    {
      type: 'bubble-sets',
      members: ['node1', 'node2'], // 需要包裹的节点 ID 列表
      label: true, // 是否显示标签
    },
  ],
});
```

## 在线体验



```ts
import { createGraph } from '/demo-runtime.ts';

createGraph(
  {
    autoFit: 'center',
    data: {
      nodes: [
        {
          id: 'node-0',
          data: { cluster: 'a' },
          style: { x: 555, y: 151 },
        },
        {
          id: 'node-1',
          data: { cluster: 'a' },
          style: { x: 532, y: 323 },
        },
        {
          id: 'node-2',
          data: { cluster: 'a' },
          style: { x: 473, y: 227 },
        },
        {
          id: 'node-3',
          data: { cluster: 'a' },
          style: { x: 349, y: 212 },
        },
        {
          id: 'node-4',
          data: { cluster: 'b' },
          style: { x: 234, y: 201 },
        },
        {
          id: 'node-5',
          data: { cluster: 'b' },
          style: { x: 338, y: 333 },
        },
        {
          id: 'node-6',
          data: { cluster: 'b' },
          style: { x: 365, y: 91 },
        },
      ],
      edges: [
        {
          id: 'edge-0',
          source: 'node-0',
          target: 'node-2',
        },
        {
          id: 'edge-1',
          source: 'node-1',
          target: 'node-2',
        },
        {
          id: 'edge-2',
          source: 'node-2',
          target: 'node-3',
        },
        {
          id: 'edge-3',
          source: 'node-3',
          target: 'node-4',
        },
        {
          id: 'edge-4',
          source: 'node-3',
          target: 'node-5',
        },
        {
          id: 'edge-5',
          source: 'node-3',
          target: 'node-6',
        },
      ],
    },
    node: {
      style: { labelText: (d) => d.id },
      palette: { field: 'cluster', color: ['#7e3feb', '#ffa940'] },
    },
    behaviors: ['drag-canvas', 'drag-element'],
    plugins: [
      'grid-line',
      {
        type: 'bubble-sets',
        key: 'bubble-sets',
        members: ['node-0', 'node-1', 'node-2', 'node-3'],
        labelText: 'bubblesets-a',
        fill: '#7e3feb',
        fillOpacity: 0.1,
        stroke: '#7e3feb',
        strokeOpacity: 1,
        labelFill: '#fff',
        labelPadding: 2,
        labelBackgroundFill: '#7e3feb',
        labelBackgroundRadius: 5,
      },
    ],
  },
  { width: 600, height: 450 },
  (gui, graph) => {
    const options = {
      type: 'bubble-sets',
      members: ['node-0', 'node-1', 'node-2', 'node-3'],
      avoidMembers: [],
      // style
      fill: '#7e3feb',
      fillOpacity: 0.1,
      stroke: '#7e3feb',
      strokeOpacity: 1,
      // label
      label: true,
      labelCloseToPath: true,
      labelAutoRotate: true,
      labelOffsetX: 0,
      labelOffsetY: 0,
      labelPlacement: 'bottom',
      // bubblesets
      maxRoutingIterations: 100,
      maxMarchingIterations: 20,
      pixelGroup: 4,
      edgeR0: 10,
      edgeR1: 20,
      nodeR0: 15,
      nodeR1: 50,
      morphBuffer: 10,
      threshold: 1,
      memberInfluenceFactor: 1,
      edgeInfluenceFactor: 1,
      nonMemberInfluenceFactor: -0.8,
      virtualEdges: true,
    };

    const optionFolder = gui.addFolder('Bubblesets Options');
    optionFolder.add(options, 'type').disable();
    optionFolder.addColor(options, 'fill');
    optionFolder.addColor(options, 'stroke');
    optionFolder.add(options, 'fillOpacity', 0, 1, 0.1);
    optionFolder.add(options, 'strokeOpacity', 0, 1, 0.1);
    optionFolder.add(options, 'label');
    optionFolder.add(options, 'labelCloseToPath');
    optionFolder.add(options, 'labelAutoRotate');
    optionFolder.add(options, 'labelOffsetX', 0, 20, 1);
    optionFolder.add(options, 'labelOffsetY', 0, 20, 1);
    optionFolder.add(options, 'labelPlacement', ['left', 'right', 'top', 'bottom', 'center']);
    optionFolder.add(options, 'maxRoutingIterations', 0, 200, 1);
    optionFolder.add(options, 'maxMarchingIterations', 0, 40, 1);
    optionFolder.add(options, 'pixelGroup', 0, 20, 1);
    optionFolder.add(options, 'edgeR0', 0, 50, 1);
    optionFolder.add(options, 'edgeR1', 0, 50, 1);
    optionFolder.add(options, 'nodeR0', 0, 50, 1);
    optionFolder.add(options, 'nodeR1', 0, 50, 1);
    optionFolder.add(options, 'morphBuffer', 0, 20, 1);
    optionFolder.add(options, 'threshold', -1, 1, 0.1);
    optionFolder.add(options, 'memberInfluenceFactor', -1, 1, 0.1);
    optionFolder.add(options, 'edgeInfluenceFactor', -1, 1, 0.1);
    optionFolder.add(options, 'nonMemberInfluenceFactor', -1, 1, 0.1);
    optionFolder.add(options, 'virtualEdges');

    optionFolder.onChange(({ property, value }) => {
      graph.updatePlugin({
        key: 'bubble-sets',
        [property]: value,
      });
      graph.render();
    });

    const apiConfig = {
      member: 'node-1',
      avoidMember: 'node-1',
    };
    const apiFolder = gui.addFolder('Bubblesets API');
    const instance = graph.getPluginInstance('bubble-sets');
    const nodeIds = graph.getData().nodes.map((node) => node.id);
    const edgeIds = graph.getData().edges.map((edge) => edge.id);
    apiFolder.add(apiConfig, 'member', [...nodeIds, ...edgeIds]);
    apiFolder.add({ addMember: () => instance.addMember(apiConfig.member) }, 'addMember').name('add member');
    apiFolder
      .add({ removeMember: () => instance.removeMember(apiConfig.member) }, 'removeMember')
      .name('remove member');
    apiFolder
      .add({ removeMember: () => alert('Members in Bubblesets: ' + instance.getMember()) }, 'removeMember')
      .name('get member');
    apiFolder.add(apiConfig, 'avoidMember', nodeIds);
    apiFolder
      .add({ addAvoidMember: () => instance.addAvoidMember(apiConfig.avoidMember) }, 'addAvoidMember')
      .name('add avoid member');
    apiFolder
      .add({ removeAvoidMember: () => instance.removeAvoidMember(apiConfig.avoidMember) }, 'removeAvoidMember')
      .name('remove avoid member');
    apiFolder
      .add({ removeMember: () => alert('Avoid members in Bubblesets: ' + instance.getAvoidMember()) }, 'removeMember')
      .name('get avoid member');
  },
);
```



## 配置项

| 属性                     | 描述                                                             | 类型                                                           | 默认值        | 必选 |
| ------------------------ | ---------------------------------------------------------------- | -------------------------------------------------------------- | ------------- | ---- |
| type                     | 插件类型                                                         | string                                                         | `bubble-sets` | ✓    |
| key                      | 插件唯一标识符，用于后续更新                                     | string                                                         | -             |      |
| members                  | 成员元素，包括节点和边，[示例](#members)                         | string[]                                                       | -             | ✓    |
| avoidMembers             | 需要避开的元素，在绘制轮廓时不会包含这些元素（目前支持设置节点） | string[]                                                       | -             |      |
| label                    | 是否显示标签                                                     | boolean                                                        | true          |      |
| labelPlacement           | 标签位置                                                         | `left` \| `right` \| `top` \| `bottom` \| `center` \| `bottom` | `bottom`      |      |
| labelBackground          | 是否显示背景                                                     | boolean                                                        | false         |      |
| labelPadding             | 标签内边距                                                       | number \| number[]                                             | 0             |      |
| labelCloseToPath         | 标签是否贴合轮廓，[示例](#labelclosetopath)                      | boolean                                                        | true          |      |
| labelAutoRotate          | 标签是否跟随轮廓旋转，[示例](#labelautorotate)                   | boolean                                                        | true          |      |
| labelOffsetX             | 标签 x 轴偏移量                                                  | number                                                         | 0             |      |
| labelOffsetY             | 标签 y 轴偏移量                                                  | number                                                         | 0             |      |
| labelMaxWidth            | 文本的最大宽度，超出会自动省略                                   | number                                                         | -             |      |
| maxRoutingIterations     | 计算成员之间路径的最大迭代次数                                   | number                                                         | 100           |      |
| maxMarchingIterations    | 计算轮廓的最大迭代次数                                           | number                                                         | 20            |      |
| pixelGroup               | 每个潜在区域组的像素数，用于提高速度                             | number                                                         | 4             |      |
| edgeR0                   | 边的半径参数 R0                                                  | number                                                         | -             |      |
| edgeR1                   | 边的半径参数 R1                                                  | number                                                         | -             |      |
| nodeR0                   | 节点的半径参数 R0                                                | number                                                         | -             |      |
| nodeR1                   | 节点的半径参数 R1                                                | number                                                         | -             |      |
| morphBuffer              | 形态缓冲区大小                                                   | number                                                         |               |      |
| threshold                | 阈值                                                             | number                                                         | -             |      |
| memberInfluenceFactor    | 成员影响因子                                                     | number                                                         | -             |      |
| edgeInfluenceFactor      | 边影响因子                                                       | number                                                         | -             |      |
| nonMemberInfluenceFactor | 非成员影响因子                                                   | number                                                         | -             |      |
| virtualEdges             | 是否使用虚拟边                                                   | boolean                                                        | -             |      |

### members

成员元素，包括节点和边。

```js
const graph = new Graph({
  plugins: [
    {
      type: 'bubble-sets',
      members: ['node-0', 'node-1', 'node-2'],
    },
  ],
});
```

效果如下：



```ts

import { Graph } from '@antv/g6';

const graph = new Graph({
  container: 'container',
  width: 300,
  height: 150,
  autoFit: 'view',
  data: {
    nodes: [
      {
        id: 'node-0',
        data: { cluster: 'a' },
        style: { x: 555, y: 151 },
      },
      {
        id: 'node-1',
        data: { cluster: 'a' },
        style: { x: 532, y: 323 },
      },
      {
        id: 'node-2',
        data: { cluster: 'a' },
        style: { x: 473, y: 227 },
      },
      {
        id: 'node-3',
        data: { cluster: 'a' },
        style: { x: 349, y: 212 },
      },
      {
        id: 'node-4',
        data: { cluster: 'b' },
        style: { x: 234, y: 201 },
      },
      {
        id: 'node-5',
        data: { cluster: 'b' },
        style: { x: 338, y: 333 },
      },
      {
        id: 'node-6',
        data: { cluster: 'b' },
        style: { x: 365, y: 91 },
      },
    ],
    edges: [
      {
        id: 'edge-0',
        source: 'node-0',
        target: 'node-2',
      },
      {
        id: 'edge-1',
        source: 'node-1',
        target: 'node-2',
      },
      {
        id: 'edge-2',
        source: 'node-2',
        target: 'node-3',
      },
      {
        id: 'edge-3',
        source: 'node-3',
        target: 'node-4',
      },
      {
        id: 'edge-4',
        source: 'node-3',
        target: 'node-5',
      },
      {
        id: 'edge-5',
        source: 'node-3',
        target: 'node-6',
      },
    ],
  },
  behaviors: ['drag-canvas', 'zoom-canvas'],
  plugins: [
    {
      type: 'bubble-sets',
      key: 'bubble-sets-a',
      members: ['node-0', 'node-1', 'node-2'],
    },
  ],
});

graph.render();
```


### labelCloseToPath

示例：不让 label 贴合轮廓

```js
const graph = new Graph({
  plugins: [
    {
      type: 'bubble-sets',
      members: ['node-0', 'node-1', 'node-2', 'node-3'],
      label: true, // 显示标签
      labelText: 'cluster-a',
      labelCloseToPath: false,
    },
  ],
});
```

效果如下：



```ts

import { Graph } from '@antv/g6';

const graph = new Graph({
  container: 'container',
  width: 300,
  height: 150,
  autoFit: 'view',
  data: {
    nodes: [
      {
        id: 'node-0',
        data: { cluster: 'a' },
        style: { x: 555, y: 151 },
      },
      {
        id: 'node-1',
        data: { cluster: 'a' },
        style: { x: 532, y: 323 },
      },
      {
        id: 'node-2',
        data: { cluster: 'a' },
        style: { x: 473, y: 227 },
      },
      {
        id: 'node-3',
        data: { cluster: 'a' },
        style: { x: 349, y: 212 },
      },
      {
        id: 'node-4',
        data: { cluster: 'b' },
        style: { x: 234, y: 201 },
      },
      {
        id: 'node-5',
        data: { cluster: 'b' },
        style: { x: 338, y: 333 },
      },
      {
        id: 'node-6',
        data: { cluster: 'b' },
        style: { x: 365, y: 91 },
      },
    ],
    edges: [
      {
        id: 'edge-0',
        source: 'node-0',
        target: 'node-2',
      },
      {
        id: 'edge-1',
        source: 'node-1',
        target: 'node-2',
      },
      {
        id: 'edge-2',
        source: 'node-2',
        target: 'node-3',
      },
      {
        id: 'edge-3',
        source: 'node-3',
        target: 'node-4',
      },
      {
        id: 'edge-4',
        source: 'node-3',
        target: 'node-5',
      },
      {
        id: 'edge-5',
        source: 'node-3',
        target: 'node-6',
      },
    ],
  },
  plugins: [
    {
      key: 'bubble-sets-a',
      type: 'bubble-sets',
      members: ['node-0', 'node-1', 'node-2', 'node-3'],
      label: true, // 显示标签
      labelText: 'cluster-a',
      labelCloseToPath: false,
    },
  ],
  behaviors: ['drag-canvas', 'zoom-canvas'],
});

graph.render();
```


### labelAutoRotate

示例：不让label标签跟随轮廓旋转

```js
const graph = new Graph({
  plugins: [
    {
      type: 'bubble-sets',
      members: ['node-0', 'node-1', 'node-2', 'node-3'],
      label: true, // 显示标签
      labelText: 'cluster-a',
      labelAutoRotate: false,
    },
  ],
});
```

效果如下：



```ts

import { Graph } from '@antv/g6';

const graph = new Graph({
  container: 'container',
  width: 300,
  height: 150,
  autoFit: 'view',
  data: {
    nodes: [
      {
        id: 'node-0',
        data: { cluster: 'a' },
        style: { x: 555, y: 151 },
      },
      {
        id: 'node-1',
        data: { cluster: 'a' },
        style: { x: 532, y: 323 },
      },
      {
        id: 'node-2',
        data: { cluster: 'a' },
        style: { x: 473, y: 227 },
      },
      {
        id: 'node-3',
        data: { cluster: 'a' },
        style: { x: 349, y: 212 },
      },
      {
        id: 'node-4',
        data: { cluster: 'b' },
        style: { x: 234, y: 201 },
      },
      {
        id: 'node-5',
        data: { cluster: 'b' },
        style: { x: 338, y: 333 },
      },
      {
        id: 'node-6',
        data: { cluster: 'b' },
        style: { x: 365, y: 91 },
      },
    ],
    edges: [
      {
        id: 'edge-0',
        source: 'node-0',
        target: 'node-2',
      },
      {
        id: 'edge-1',
        source: 'node-1',
        target: 'node-2',
      },
      {
        id: 'edge-2',
        source: 'node-2',
        target: 'node-3',
      },
      {
        id: 'edge-3',
        source: 'node-3',
        target: 'node-4',
      },
      {
        id: 'edge-4',
        source: 'node-3',
        target: 'node-5',
      },
      {
        id: 'edge-5',
        source: 'node-3',
        target: 'node-6',
      },
    ],
  },
  plugins: [
    {
      key: 'bubble-sets-a',
      type: 'bubble-sets',
      members: ['node-0', 'node-1', 'node-2', 'node-3'],
      label: true, // 显示标签
      labelText: 'cluster-a',
      labelAutoRotate: false,
    },
  ],
  behaviors: ['drag-canvas', 'zoom-canvas'],
});

graph.render();
```


## 使用示例

### 基础 BubbleSets

最简单的方式是直接使用预设配置：

```js
const graph = new Graph({
  plugins: [
    {
      type: 'bubble-sets',
      members: ['node-0', 'node-1', 'node-2', 'node-3'],
    },
  ],
});
```

效果如下：



```ts

import { Graph } from '@antv/g6';

const graph = new Graph({
  container: 'container',
  width: 300,
  height: 150,
  autoFit: 'center',
  data: {
    nodes: [
      {
        id: 'node-0',
        data: { cluster: 'a' },
        style: { x: 555, y: 151 },
      },
      {
        id: 'node-1',
        data: { cluster: 'a' },
        style: { x: 532, y: 323 },
      },
      {
        id: 'node-2',
        data: { cluster: 'a' },
        style: { x: 473, y: 227 },
      },
      {
        id: 'node-3',
        data: { cluster: 'a' },
        style: { x: 349, y: 212 },
      },
      {
        id: 'node-4',
        data: { cluster: 'b' },
        style: { x: 234, y: 201 },
      },
      {
        id: 'node-5',
        data: { cluster: 'b' },
        style: { x: 338, y: 333 },
      },
      {
        id: 'node-6',
        data: { cluster: 'b' },
        style: { x: 365, y: 91 },
      },
    ],
    edges: [
      {
        id: 'edge-0',
        source: 'node-0',
        target: 'node-2',
      },
      {
        id: 'edge-1',
        source: 'node-1',
        target: 'node-2',
      },
      {
        id: 'edge-2',
        source: 'node-2',
        target: 'node-3',
      },
      {
        id: 'edge-3',
        source: 'node-3',
        target: 'node-4',
      },
      {
        id: 'edge-4',
        source: 'node-3',
        target: 'node-5',
      },
      {
        id: 'edge-5',
        source: 'node-3',
        target: 'node-6',
      },
    ],
  },
  behaviors: ['drag-canvas', 'zoom-canvas'],
  plugins: [
    {
      type: 'bubble-sets',
      key: 'bubble-sets-a',
      members: ['node-0', 'node-1', 'node-2', 'node-3'],
    },
  ],
});

graph.render();
```


### 自定义 BubbleSets 样式

```js
const graph = new Graph({
  plugins: [
    {
      type: 'bubble-sets',
      members: ['node-0', 'node-1', 'node-2', 'node-3'],
      fill: '#7e3feb', // 气泡填充颜色
      fillOpacity: 0.1, // 填充透明度
      stroke: '#7e3feb', // 边框颜色
      strokeOpacity: 1, // 边框透明度
    },
  ],
});
```

效果如下：



```ts

import { Graph } from '@antv/g6';

const graph = new Graph({
  container: 'container',
  width: 300,
  height: 150,
  autoFit: 'center',
  data: {
    nodes: [
      {
        id: 'node-0',
        data: { cluster: 'a' },
        style: { x: 555, y: 151 },
      },
      {
        id: 'node-1',
        data: { cluster: 'a' },
        style: { x: 532, y: 323 },
      },
      {
        id: 'node-2',
        data: { cluster: 'a' },
        style: { x: 473, y: 227 },
      },
      {
        id: 'node-3',
        data: { cluster: 'a' },
        style: { x: 349, y: 212 },
      },
      {
        id: 'node-4',
        data: { cluster: 'b' },
        style: { x: 234, y: 201 },
      },
      {
        id: 'node-5',
        data: { cluster: 'b' },
        style: { x: 338, y: 333 },
      },
      {
        id: 'node-6',
        data: { cluster: 'b' },
        style: { x: 365, y: 91 },
      },
    ],
    edges: [
      {
        id: 'edge-0',
        source: 'node-0',
        target: 'node-2',
      },
      {
        id: 'edge-1',
        source: 'node-1',
        target: 'node-2',
      },
      {
        id: 'edge-2',
        source: 'node-2',
        target: 'node-3',
      },
      {
        id: 'edge-3',
        source: 'node-3',
        target: 'node-4',
      },
      {
        id: 'edge-4',
        source: 'node-3',
        target: 'node-5',
      },
      {
        id: 'edge-5',
        source: 'node-3',
        target: 'node-6',
      },
    ],
  },
  plugins: [
    {
      type: 'bubble-sets',
      members: ['node-0', 'node-1', 'node-2', 'node-3'],
      fill: '#7e3feb', // 气泡填充颜色
      fillOpacity: 0.1, // 填充透明度
      stroke: '#7e3feb', // 边框颜色
      strokeOpacity: 1, // 边框透明度
    },
  ],
  behaviors: ['drag-canvas', 'zoom-canvas'],
});

graph.render();
```


### 标签配置

您可以配置标签的位置、背景、偏移量等属性，以增强可视化效果。

```js
const graph = new Graph({
  plugins: [
    {
      type: 'bubble-sets',
      members: ['node-0', 'node-1', 'node-2', 'node-3'],
      label: true, // 显示标签
      labelPlacement: 'top', // 标签位置
      labelBackground: true, // 显示标签背景
      labelPadding: 5, // 标签内边距
    },
  ],
});
```

效果如下：



```ts

import { Graph } from '@antv/g6';

const graph = new Graph({
  container: 'container',
  width: 300,
  height: 150,
  autoFit: 'center',
  data: {
    nodes: [
      {
        id: 'node-0',
        data: { cluster: 'a' },
        style: { x: 555, y: 151 },
      },
      {
        id: 'node-1',
        data: { cluster: 'a' },
        style: { x: 532, y: 323 },
      },
      {
        id: 'node-2',
        data: { cluster: 'a' },
        style: { x: 473, y: 227 },
      },
      {
        id: 'node-3',
        data: { cluster: 'a' },
        style: { x: 349, y: 212 },
      },
      {
        id: 'node-4',
        data: { cluster: 'b' },
        style: { x: 234, y: 201 },
      },
      {
        id: 'node-5',
        data: { cluster: 'b' },
        style: { x: 338, y: 333 },
      },
      {
        id: 'node-6',
        data: { cluster: 'b' },
        style: { x: 365, y: 91 },
      },
    ],
    edges: [
      {
        id: 'edge-0',
        source: 'node-0',
        target: 'node-2',
      },
      {
        id: 'edge-1',
        source: 'node-1',
        target: 'node-2',
      },
      {
        id: 'edge-2',
        source: 'node-2',
        target: 'node-3',
      },
      {
        id: 'edge-3',
        source: 'node-3',
        target: 'node-4',
      },
      {
        id: 'edge-4',
        source: 'node-3',
        target: 'node-5',
      },
      {
        id: 'edge-5',
        source: 'node-3',
        target: 'node-6',
      },
    ],
  },
  plugins: [
    {
      key: 'bubble-sets-a',
      type: 'bubble-sets',
      members: ['node-0', 'node-1', 'node-2', 'node-3'],
      label: true, // 显示标签
      labelText: 'cluster-a',
      labelPlacement: 'top', // 标签位置
      labelBackground: true, // 显示标签背景
      labelPadding: 5, // 标签内边距
    },
  ],
  behaviors: ['drag-canvas', 'zoom-canvas'],
});

graph.render();
```


## 实际案例



```ts

import { Graph } from '@antv/g6';

fetch('https://assets.antv.antgroup.com/g6/collection.json')
  .then((res) => res.json())
  .then((data) => {
    const groupedNodesByCluster = data.nodes.reduce((acc, node) => {
      const cluster = node.data.cluster;
      acc[cluster] ||= [];
      acc[cluster].push(node.id);
      return acc;
    }, {});

    const createStyle = (baseColor) => ({
      fill: baseColor,
      stroke: baseColor,
      labelFill: '#fff',
      labelPadding: 2,
      labelBackgroundFill: baseColor,
      labelBackgroundRadius: 5,
    });

    const graph = new Graph({
      container: 'container',
      data,
      behaviors: ['zoom-canvas', 'drag-canvas', 'drag-element'],
      node: {
        palette: { field: 'cluster' },
      },
      layout: {
        type: 'force',
        preventOverlap: true,
        linkDistance: (d) => {
          if (d.source === 'node0' || d.target === 'node0') {
            return 200;
          }
          return 80;
        },
      },
      plugins: [
        {
          key: 'bubble-sets-a',
          type: 'bubble-sets',
          members: groupedNodesByCluster['a'],
          labelText: 'cluster-a',
          ...createStyle('#1783FF'),
        },
        {
          key: 'bubble-sets-b',
          type: 'bubble-sets',
          members: groupedNodesByCluster['b'],
          labelText: 'cluster-b',
          ...createStyle('#00C9C9'),
        },
        {
          key: 'bubble-sets-c',
          type: 'bubble-sets',
          members: groupedNodesByCluster['c'],
          labelText: 'cluster-c',
          ...createStyle('#F08F56'),
        },
        {
          key: 'bubble-sets-d',
          type: 'bubble-sets',
          members: groupedNodesByCluster['d'],
          labelText: 'cluster-d',
          ...createStyle('#D580FF'),
        },
      ],
      autoFit: 'center',
    });

    graph.render();
  });
```
### demo-runtime.ts

```ts
import { Graph } from '@antv/g6';
import GUI from 'lil-gui';

export function addPanel(renderPanel: (gui: GUI) => void) {
  const gui = new GUI({ container: document.body });
  gui.title('Control');
  Object.assign(gui.domElement.style, { position: 'absolute', top: '0', right: '0', zIndex: '10' });
  renderPanel(gui);
  addEventListener('pagehide', () => gui.destroy(), { once: true });
}

export async function createGraph(
  options: ConstructorParameters<typeof Graph>[0],
  size: { width?: number; height?: number } = {},
  renderPanel?: (gui: GUI, graph: Graph) => void,
) {
  const container = document.createElement('div');
  Object.assign(container.style, {
    width: '100%',
    maxWidth: `${size.width || 600}px`,
    height: `${size.height || 400}px`,
  });
  document.getElementById('container')!.append(container);
  const graph = new Graph({ ...size, ...options, width: container.clientWidth, container, autoResize: true });
  addEventListener('pagehide', () => graph.destroy(), { once: true });
  await graph.render();
  if (renderPanel) addPanel((gui) => renderPanel(gui, graph));
  return container;
}
```
