diff --git a/packages/g6/__tests__/demo/case/index.ts b/packages/g6/__tests__/demo/case/index.ts
index 8555bfd2dca..b36f67222a6 100644
--- a/packages/g6/__tests__/demo/case/index.ts
+++ b/packages/g6/__tests__/demo/case/index.ts
@@ -19,6 +19,8 @@ export * from './layout-combo-combined';
export * from './layout-concentric';
export * from './layout-dagre-flow';
export * from './layout-dagre-flow-combo';
+export * from './layout-dendrogram-basic';
+export * from './layout-dendrogram-tb';
export * from './layout-force';
export * from './layout-fruchterman-basic';
export * from './layout-fruchterman-cluster';
diff --git a/packages/g6/__tests__/demo/case/layout-dendrogram-basic.ts b/packages/g6/__tests__/demo/case/layout-dendrogram-basic.ts
new file mode 100644
index 00000000000..b19a14df4fe
--- /dev/null
+++ b/packages/g6/__tests__/demo/case/layout-dendrogram-basic.ts
@@ -0,0 +1,34 @@
+import { Graph, Utils } from '@/src';
+import data from '@@/dataset/algorithm-category.json';
+import type { STDTestCase } from '../types';
+
+export const layoutDendrogramBasic: STDTestCase = async (context) => {
+ const graph = new Graph({
+ ...context,
+ autoFit: 'view',
+ data: Utils.treeToGraphData(data),
+ node: {
+ style: {
+ labelText: (d) => d.id,
+ labelPlacement: (model) => (model.style!.children?.length ? 'left' : 'right'),
+ ports: [{ placement: 'right' }, { placement: 'left' }],
+ },
+ },
+ edge: {
+ style: {
+ type: 'cubic-horizontal',
+ },
+ },
+ layout: {
+ type: 'dendrogram',
+ direction: 'LR',
+ nodeSep: 36,
+ rankSep: 250,
+ },
+ behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node', 'collapse-expand-tree'],
+ });
+
+ await graph.render();
+
+ return graph;
+};
diff --git a/packages/g6/__tests__/demo/case/layout-dendrogram-tb.ts b/packages/g6/__tests__/demo/case/layout-dendrogram-tb.ts
new file mode 100644
index 00000000000..61dcd121972
--- /dev/null
+++ b/packages/g6/__tests__/demo/case/layout-dendrogram-tb.ts
@@ -0,0 +1,41 @@
+import { Graph, Utils } from '@/src';
+import data from '@@/dataset/algorithm-category.json';
+import type { STDTestCase } from '../types';
+
+export const layoutDendrogramTb: STDTestCase = async (context) => {
+ const graph = new Graph({
+ ...context,
+ autoFit: 'view',
+ data: Utils.treeToGraphData(data),
+ node: {
+ style: (model) => {
+ const hasChildren = !!model.style!.children?.length;
+ return {
+ labelMaxWidth: 200,
+ labelPlacement: hasChildren ? 'right' : 'bottom',
+ labelText: model.id,
+ labelTextAlign: 'start',
+ labelTextBaseline: hasChildren ? 'middle' : 'bottom',
+ transform: hasChildren ? '' : 'rotate(90)',
+ ports: [{ placement: 'bottom' }, { placement: 'top' }],
+ };
+ },
+ },
+ edge: {
+ style: {
+ type: 'cubic-vertical',
+ },
+ },
+ layout: {
+ type: 'dendrogram',
+ direction: 'TB',
+ nodeSep: 40,
+ rankSep: 100,
+ },
+ behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node', 'collapse-expand-tree'],
+ });
+
+ await graph.render();
+
+ return graph;
+};
diff --git a/packages/g6/__tests__/snapshots/layouts/dendrogram/basic.svg b/packages/g6/__tests__/snapshots/layouts/dendrogram/basic.svg
new file mode 100644
index 00000000000..ff70f58d2fc
--- /dev/null
+++ b/packages/g6/__tests__/snapshots/layouts/dendrogram/basic.svg
@@ -0,0 +1,1899 @@
+
\ No newline at end of file
diff --git a/packages/g6/__tests__/snapshots/layouts/dendrogram/tb.svg b/packages/g6/__tests__/snapshots/layouts/dendrogram/tb.svg
new file mode 100644
index 00000000000..2a0a5eaff96
--- /dev/null
+++ b/packages/g6/__tests__/snapshots/layouts/dendrogram/tb.svg
@@ -0,0 +1,1951 @@
+
\ No newline at end of file
diff --git a/packages/g6/__tests__/unit/layouts/dendrogram.spec.ts b/packages/g6/__tests__/unit/layouts/dendrogram.spec.ts
new file mode 100644
index 00000000000..6746c671595
--- /dev/null
+++ b/packages/g6/__tests__/unit/layouts/dendrogram.spec.ts
@@ -0,0 +1,16 @@
+import { layoutDendrogramBasic, layoutDendrogramTb } from '@@/demo/case';
+import { createDemoGraph } from '@@/utils';
+
+describe('dendrogram', () => {
+ it('basic', async () => {
+ const graph = await createDemoGraph(layoutDendrogramBasic);
+ await expect(graph).toMatchSnapshot(__filename, 'basic');
+ graph.destroy();
+ });
+
+ it('tb', async () => {
+ const graph = await createDemoGraph(layoutDendrogramTb);
+ await expect(graph).toMatchSnapshot(__filename, 'tb');
+ graph.destroy();
+ });
+});
diff --git a/packages/site/examples/net/dendrogram/demo/basicDendrogram.js b/packages/site/examples/net/dendrogram/demo/basicDendrogram.js
deleted file mode 100644
index 197dcf814dc..00000000000
--- a/packages/site/examples/net/dendrogram/demo/basicDendrogram.js
+++ /dev/null
@@ -1,158 +0,0 @@
-import { Graph, Extensions, extend } from '@antv/g6';
-
-const ExtGraph = extend(Graph, {
- edges: {
- 'cubic-horizontal-edge': Extensions.CubicHorizontalEdge,
- 'cubic-vertical-edge': Extensions.CubicVerticalEdge,
- },
-});
-const layoutConfigs = {
- LR: {
- type: 'dendrogram',
- direction: 'LR', // H / V / LR / RL / TB / BT
- nodeSep: 36,
- rankSep: 250,
- },
- TB: {
- type: 'dendrogram',
- direction: 'TB', // H / V / LR / RL / TB / BT
- nodeSep: 40,
- rankSep: 100,
- },
-};
-
-const container = document.getElementById('container');
-const width = container.scrollWidth;
-const height = container.scrollHeight || 500;
-
-fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json')
- .then((res) => res.json())
- .then((data) => {
- const graph = new ExtGraph({
- container,
- width,
- height,
- transforms: [
- {
- type: 'transform-v4-data',
- activeLifecycle: ['read'],
- },
- ],
- modes: {
- default: ['drag-canvas', 'zoom-canvas', 'drag-node', 'collapse-expand-tree'],
- },
- node: (model) => {
- const configRelatedToLayout =
- model.data.layoutDirection === 'TB'
- ? {
- labelShape: {
- text: model.id,
- position: 'bottom',
- offsetX: 0,
- },
- anchorPoints: [
- [0.5, 0],
- [0.5, 1],
- ],
- }
- : {
- labelShape: {
- text: model.id,
- position: model.data.childrenIds?.length ? 'left' : 'right',
- offsetX: model.data.childrenIds?.length ? -10 : 10,
- maxWidth: '300%',
- },
- anchorPoints: [
- [0, 0.5],
- [1, 0.5],
- ],
- };
- return {
- id: model.id,
- data: {
- ...model.data,
- labelBackgroundShape: {},
- ...configRelatedToLayout,
- animates: {
- update: [
- {
- fields: ['x', 'y'],
- duration: 500,
- shapeId: 'group',
- order: 0,
- },
- ],
- hide: [
- {
- fields: ['opacity'],
- duration: 200,
- shapeId: 'keyShape',
- },
- {
- fields: ['opacity'],
- duration: 200,
- shapeId: 'labelShape',
- },
- ],
- show: [
- {
- fields: ['opacity'],
- duration: 1000,
- shapeId: 'keyShape',
- },
- {
- fields: ['opacity'],
- duration: 1000,
- shapeId: 'labelShape',
- },
- ],
- },
- },
- };
- },
- edge: {
- type: 'cubic-horizontal-edge',
- },
- layout: layoutConfigs.LR,
- autoFit: 'view',
- data: {
- type: 'treeData',
- value: data,
- },
- });
-
- const btnContainer = document.createElement('div');
- btnContainer.style.position = 'absolute';
- container.appendChild(btnContainer);
- const tip = document.createElement('span');
- tip.innerHTML = '👉 Change configs:';
- btnContainer.appendChild(tip);
-
- Object.keys(layoutConfigs).forEach((name, i) => {
- const btn = document.createElement('a');
- btn.innerHTML = name;
- btn.style.backgroundColor = 'rgba(255, 255, 255, 0.8)';
- btn.style.padding = '4px';
- btn.style.marginLeft = i > 0 ? '24px' : '8px';
- btnContainer.appendChild(btn);
- btn.addEventListener('click', () => {
- const updateEdges = graph.getAllEdgesData().map((edge) => ({
- id: edge.id,
- data: {
- type: name === 'LR' ? 'cubic-horizontal-edge' : 'cubic-vertical-edge',
- },
- }));
- const updateNodes = graph.getAllNodesData().map((node) => ({
- id: node.id,
- data: {
- layoutDirection: name,
- },
- }));
- graph.updateData('node', updateNodes);
- graph.updateData('edge', updateEdges);
- graph.layout(layoutConfigs[name]);
- });
- });
-
-window.graph = graph;
- });
diff --git a/packages/site/examples/net/dendrogram/demo/basicDendrogram.ts b/packages/site/examples/net/dendrogram/demo/basicDendrogram.ts
new file mode 100644
index 00000000000..1d68b2ecadb
--- /dev/null
+++ b/packages/site/examples/net/dendrogram/demo/basicDendrogram.ts
@@ -0,0 +1,40 @@
+import { Graph, Utils } from '@antv/g6';
+
+fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json')
+ .then((res) => res.json())
+ .then((data) => {
+ const graph = new Graph({
+ container: 'container',
+ autoFit: 'view',
+ data: Utils.treeToGraphData(data),
+ node: {
+ style: {
+ labelText: (d) => d.id,
+ labelPlacement: (model) => (model.style!.children?.length ? 'left' : 'right'),
+ ports: [
+ {
+ placement: 'right',
+ },
+ {
+ placement: 'left',
+ },
+ ],
+ },
+ },
+
+ edge: {
+ style: {
+ type: 'cubic-horizontal',
+ },
+ },
+ layout: {
+ type: 'dendrogram',
+ direction: 'LR', // H / V / LR / RL / TB / BT
+ nodeSep: 36,
+ rankSep: 250,
+ },
+ behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node', 'collapse-expand-tree'],
+ });
+
+ graph.render();
+ });
diff --git a/packages/site/examples/net/dendrogram/demo/graphDataWithDendrogram.js b/packages/site/examples/net/dendrogram/demo/graphDataWithDendrogram.js
deleted file mode 100644
index b0ad7ff11b0..00000000000
--- a/packages/site/examples/net/dendrogram/demo/graphDataWithDendrogram.js
+++ /dev/null
@@ -1,177 +0,0 @@
-import { Graph, Extensions, extend } from '@antv/g6';
-
-const ExtGraph = extend(Graph, {
- edges: {
- 'cubic-horizontal-edge': Extensions.CubicHorizontalEdge,
- 'cubic-vertical-edge': Extensions.CubicVerticalEdge,
- },
- behaviors: {
- 'activate-relations': Extensions.ActivateRelations,
- },
-});
-
-const layoutConfigs = {
- LR: {
- type: 'dendrogram',
- direction: 'LR', // H / V / LR / RL / TB / BT
- nodeSep: 40,
- rankSep: 70,
- },
- TB: {
- type: 'dendrogram',
- direction: 'TB', // H / V / LR / RL / TB / BT
- nodeSep: 40,
- rankSep: 40,
- },
-};
-
-const container = document.getElementById('container');
-const width = container.scrollWidth;
-const height = container.scrollHeight || 500;
-
-fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/relations.json')
- .then((res) => res.json())
- .then((data) => {
- data.nodes.forEach((node, i) => (node.cluster = i % 3));
- const graph = new ExtGraph({
- container,
- width,
- height,
- transforms: [
- {
- type: 'transform-v4-data',
- activeLifecycle: ['read'],
- },
- ],
- modes: {
- default: ['drag-canvas', 'zoom-canvas', 'drag-node', 'collapse-expand-tree', 'activate-relations'],
- },
- theme: {
- type: 'spec',
- specification: {
- node: {
- dataTypeField: 'cluster',
- },
- },
- },
- node: (model) => {
- return {
- id: model.id,
- data: {
- ...model.data,
- type: 'rect-node',
- // lodLevels: [],
- keyShape: {
- width: 50,
- height: 20,
- },
- labelShape: {
- text: model.id,
- position: 'bottom',
- maxWidth: '120%',
- lod: Math.floor(Math.random() * 5 - 3),
- fontSize: 8,
- },
- labelBackgroundShape: {},
- anchorPoints:
- model.data.layoutDirection === 'TB'
- ? [
- [0.5, 0],
- [0.5, 1],
- ]
- : [
- [0, 0.5],
- [1, 0.5],
- ],
- animates: {
- update: [
- {
- fields: ['x', 'y'],
- duration: 500,
- shapeId: 'group',
- order: 0,
- },
- ],
- hide: [
- {
- fields: ['opacity'],
- duration: 200,
- shapeId: 'keyShape',
- },
- {
- fields: ['opacity'],
- duration: 200,
- shapeId: 'labelShape',
- },
- ],
- show: [
- {
- fields: ['opacity'],
- duration: 1000,
- shapeId: 'keyShape',
- },
- {
- fields: ['opacity'],
- duration: 1000,
- shapeId: 'labelShape',
- },
- ],
- },
- },
- };
- },
- edge: {
- type: 'cubic-horizontal-edge',
- keyShape: {
- opacity: 0.5,
- endArrow: true,
- },
- },
- layout: layoutConfigs.LR,
- autoFit: 'view',
- data: {
- type: 'graphData',
- value: data,
- },
- edgeState: {
- active: {
- lineWidth: 3,
- },
- },
- });
-
- const btnContainer = document.createElement('div');
- btnContainer.style.position = 'absolute';
- container.appendChild(btnContainer);
- const tip = document.createElement('span');
- tip.innerHTML = '👉 Change configs:';
- btnContainer.appendChild(tip);
-
- Object.keys(layoutConfigs).forEach((name, i) => {
- const btn = document.createElement('a');
- btn.innerHTML = name;
- btn.style.backgroundColor = 'rgba(255, 255, 255, 0.8)';
- btn.style.padding = '4px';
- btn.style.marginLeft = i > 0 ? '24px' : '8px';
- btnContainer.appendChild(btn);
- btn.addEventListener('click', () => {
- const updateEdges = graph.getAllEdgesData().map((edge) => ({
- id: edge.id,
- data: {
- type: name === 'LR' ? 'cubic-horizontal-edge' : 'cubic-vertical-edge',
- },
- }));
- const updateNodes = graph.getAllNodesData().map((node) => ({
- id: node.id,
- data: {
- layoutDirection: name,
- },
- }));
- graph.updateData('node', updateNodes);
- graph.updateData('edge', updateEdges);
- graph.layout(layoutConfigs[name]);
- });
- });
-
-window.graph = graph;
- });
diff --git a/packages/site/examples/net/dendrogram/demo/meta.json b/packages/site/examples/net/dendrogram/demo/meta.json
index c59df35c737..38938e3ca22 100644
--- a/packages/site/examples/net/dendrogram/demo/meta.json
+++ b/packages/site/examples/net/dendrogram/demo/meta.json
@@ -5,7 +5,7 @@
},
"demos": [
{
- "filename": "basicDendrogram.js",
+ "filename": "basicDendrogram.ts",
"title": {
"zh": "生态树",
"en": "Basic Dendrogram Layout"
@@ -13,12 +13,12 @@
"screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*P-qOSoDNuckAAAAAAAAAAAAADmJ7AQ/original"
},
{
- "filename": "graphDataWithDendrogram.js",
+ "filename": "tbDendrogram.ts",
"title": {
- "zh": "图数据使用生态树",
- "en": "Basic Dendrogram Layout with Graph Data"
+ "zh": "至上而下的生态树",
+ "en": "Top to Bottom Dendrogram"
},
- "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*Ti5tSZHz7vgAAAAAAAAAAAAADmJ7AQ/original"
+ "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*nTKmRKkyUVUAAAAAAAAAAABkARQnAQ"
}
]
}
diff --git a/packages/site/examples/net/dendrogram/demo/tbDendrogram.ts b/packages/site/examples/net/dendrogram/demo/tbDendrogram.ts
new file mode 100644
index 00000000000..6e02641d4ca
--- /dev/null
+++ b/packages/site/examples/net/dendrogram/demo/tbDendrogram.ts
@@ -0,0 +1,46 @@
+import { Graph, Utils } from '@antv/g6';
+
+fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json')
+ .then((res) => res.json())
+ .then((data) => {
+ const graph = new Graph({
+ container: 'container',
+ autoFit: 'view',
+ data: Utils.treeToGraphData(data),
+ node: {
+ style: (model) => {
+ const hasChildren = !!model.style!.children?.length;
+ return {
+ labelText: model.id,
+ labelPlacement: hasChildren ? 'right' : 'bottom',
+ labelMaxWidth: 200,
+ labelTextAlign: 'start',
+ transform: hasChildren ? '' : 'rotate(90deg)',
+ ports: [
+ {
+ placement: 'bottom',
+ },
+ {
+ placement: 'top',
+ },
+ ],
+ };
+ },
+ },
+
+ edge: {
+ style: {
+ type: 'cubic-vertical',
+ },
+ },
+ layout: {
+ type: 'dendrogram',
+ direction: 'TB', // H / V / LR / RL / TB / BT
+ nodeSep: 40,
+ rankSep: 100,
+ },
+ behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node', 'collapse-expand-tree'],
+ });
+
+ graph.render();
+ });