diff --git a/packages/g6/__tests__/demos/index.ts b/packages/g6/__tests__/demos/index.ts
index 1bc24598444..1c6aa2e4d88 100644
--- a/packages/g6/__tests__/demos/index.ts
+++ b/packages/g6/__tests__/demos/index.ts
@@ -80,6 +80,7 @@ export * from './layout-radial-prevent-overlap-unstrict';
export * from './layout-radial-sort';
export * from './plugin-contextmenu';
export * from './plugin-grid-line';
+export * from './plugin-legend';
export * from './plugin-toolbar-build-in';
export * from './plugin-toolbar-iconfont';
export * from './plugin-tooltip';
diff --git a/packages/g6/__tests__/demos/plugin-legend.ts b/packages/g6/__tests__/demos/plugin-legend.ts
new file mode 100644
index 00000000000..05ae525bfb5
--- /dev/null
+++ b/packages/g6/__tests__/demos/plugin-legend.ts
@@ -0,0 +1,79 @@
+import { Graph } from '@/src';
+import data from '@@/dataset/cluster.json';
+import { isObject } from '@antv/util';
+
+export const pluginLegend: TestCase = async (context) => {
+ const { nodes, edges } = data;
+ const findCluster = (id: string) => {
+ return nodes.find(({ id: node }) => node === id)?.data.cluster;
+ };
+ const graph = new Graph({
+ ...context,
+ data: {
+ nodes,
+ edges: edges.map(({ source, target }) => {
+ return {
+ source,
+ target,
+ id: `${source}-${target}`,
+ data: {
+ cluster: `${findCluster(source)}-${findCluster(target)}`,
+ },
+ };
+ }),
+ },
+ layout: { type: 'd3force' },
+ behaviors: ['drag-canvas', 'drag-element'],
+ node: {
+ style: {
+ labelText: (d) => d.id,
+ lineWidth: 0,
+ type: (item: any) => {
+ if (item.data.cluster === 'a') return 'diamond';
+ if (item.data.cluster === 'b') return 'rect';
+ if (item.data.cluster === 'c') return 'triangle';
+ return 'circle';
+ },
+ color: (item: any) => {
+ if (item.data.cluster === 'a') return 'red';
+ if (item.data.cluster === 'b') return 'blue';
+ if (item.data.cluster === 'c') return 'green';
+ return '#99add1';
+ },
+ },
+ },
+ plugins: [
+ {
+ key: 'legend',
+ type: 'legend',
+ titleText: 'Cluster Legend',
+ nodeField: 'cluster',
+ edgeField: 'cluster',
+ trigger: 'click',
+ },
+ ],
+ });
+
+ await graph.render();
+
+ pluginLegend.form = (panel) => {
+ const config = {
+ trigger: 'hover',
+ };
+ return [
+ panel
+ .add(config, 'trigger', ['hover', 'click'])
+ .name('Change Trigger Method')
+ .onChange((trigger: string) => {
+ graph.setPlugins((plugins) =>
+ plugins.map((plugin) => {
+ if (isObject(plugin) && plugin.type === 'legend') return { ...plugin, trigger };
+ return plugin;
+ }),
+ );
+ }),
+ ];
+ };
+
+ return graph;
+};
diff --git a/packages/g6/__tests__/snapshots/plugins/legend/click-again.svg b/packages/g6/__tests__/snapshots/plugins/legend/click-again.svg
new file mode 100644
index 00000000000..e9a9a60c11c
--- /dev/null
+++ b/packages/g6/__tests__/snapshots/plugins/legend/click-again.svg
@@ -0,0 +1,1137 @@
+
\ No newline at end of file
diff --git a/packages/g6/__tests__/snapshots/plugins/legend/click.svg b/packages/g6/__tests__/snapshots/plugins/legend/click.svg
new file mode 100644
index 00000000000..06cebce256f
--- /dev/null
+++ b/packages/g6/__tests__/snapshots/plugins/legend/click.svg
@@ -0,0 +1,1176 @@
+
\ No newline at end of file
diff --git a/packages/g6/__tests__/snapshots/plugins/legend/mouseenter.svg b/packages/g6/__tests__/snapshots/plugins/legend/mouseenter.svg
new file mode 100644
index 00000000000..e337346d4d6
--- /dev/null
+++ b/packages/g6/__tests__/snapshots/plugins/legend/mouseenter.svg
@@ -0,0 +1,1176 @@
+
\ No newline at end of file
diff --git a/packages/g6/__tests__/snapshots/plugins/legend/mouseleave.svg b/packages/g6/__tests__/snapshots/plugins/legend/mouseleave.svg
new file mode 100644
index 00000000000..01ffa144cbe
--- /dev/null
+++ b/packages/g6/__tests__/snapshots/plugins/legend/mouseleave.svg
@@ -0,0 +1,1137 @@
+
\ No newline at end of file
diff --git a/packages/g6/__tests__/snapshots/plugins/legend/normal.svg b/packages/g6/__tests__/snapshots/plugins/legend/normal.svg
new file mode 100644
index 00000000000..4e198dce4b3
--- /dev/null
+++ b/packages/g6/__tests__/snapshots/plugins/legend/normal.svg
@@ -0,0 +1,1137 @@
+
\ No newline at end of file
diff --git a/packages/g6/__tests__/unit/plugins/legend.spec.ts b/packages/g6/__tests__/unit/plugins/legend.spec.ts
new file mode 100644
index 00000000000..aa10f720129
--- /dev/null
+++ b/packages/g6/__tests__/unit/plugins/legend.spec.ts
@@ -0,0 +1,67 @@
+import { Legend } from '@/src/plugins/legend';
+import { pluginLegend } from '@@/demos';
+import { createDemoGraph } from '@@/utils';
+
+const mockEvent: any = {
+ __data__: {
+ id: 'node__0',
+ index: 0,
+ style: {
+ layout: 'flex',
+ labelText: 'a',
+ markerLineWidth: 3,
+ marker: 'diamond',
+ markerStroke: '#000000',
+ markerFill: 'red',
+ spacing: 4,
+ markerSize: 16,
+ labelFontSize: 16,
+ markerOpacity: 1,
+ labelOpacity: 1,
+ },
+ },
+};
+
+describe('plugin legend', () => {
+ it('normal', async () => {
+ const graph = await createDemoGraph(pluginLegend);
+ await expect(graph).toMatchSnapshot(__filename, 'normal');
+ graph.destroy();
+ });
+
+ it('click', async () => {
+ const graph = await createDemoGraph(pluginLegend);
+
+ const legend = graph.getPluginInstance