diff --git a/examples/net/gpuLayout/API.en.md b/examples/net/gpuLayout/API.en.md
new file mode 100644
index 0000000000..a19b0d6084
--- /dev/null
+++ b/examples/net/gpuLayout/API.en.md
@@ -0,0 +1,31 @@
+---
+title: API
+---
+
+## center
+
+**Type**: Array
**Example**: [ 0, 0 ]
**Default**: The center of the graph
**Required**: false
**Description**: The center of the layout
+
+## maxIteration
+
+**Type**: Number
**Default**: 1000
**Required**: false
**Description**: The maximum iteration number
+
+## gravity
+
+**Type**: Number
**Default**: 10
**Required**: false
**Description**: The gravity, which will affect the compactness of the layout
+
+## speed
+
+**Type**: Number
**Default**: 1
**Required**: false
**Description**: The moving speed of each iteraction. Large value of the speed might lead to violent swing
+
+## clustering
+
+**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether to layout by cluster
+
+## clusterGravity
+
+**Type**: Number
**Default**: 10
**Required**: false
**Description**: The gravity of each cluster, which will affect the compactness of each cluster. Takes effect only when `clustering` is `true`
+
+## workerEnabled
+
+**Type**: Boolean
**Default**: false
**Required**: false
**Description**: Whether to enable the web-worker in case layout calculation takes too long to block page interaction
diff --git a/examples/net/gpuLayout/API.zh.md b/examples/net/gpuLayout/API.zh.md
new file mode 100644
index 0000000000..41102fb8e7
--- /dev/null
+++ b/examples/net/gpuLayout/API.zh.md
@@ -0,0 +1,31 @@
+---
+title: API
+---
+
+## center
+
+**类型**: Array
**示例**:[ 0, 0 ]
**默认值**:图的中心
**是否必须**:false
**说明**:布局的中心
+
+## maxIteration
+
+**类型**: Number
**默认值**:1000
**是否必须**:false
**说明**:最大迭代次数
+
+## gravity
+
+**类型**: Number
**默认值**:10
**是否必须**:false
**说明**:重力的大小,影响布局的紧凑程度
+
+## speed
+
+**类型**: Number
**默认值**:1
**是否必须**:false
**说明**:每次迭代节点移动的速度。速度太快可能会导致强烈震荡
+
+## clustering
+
+**类型**: Boolean
**默认值**:false
**是否必须**:false
**说明**:是否按照聚类布局
+
+## clusterGravity
+
+**类型**: Number
**默认值**:10
**是否必须**:false
**说明**:聚类内部的重力大小,影响聚类的紧凑程度,在 `clustering` 为 `true` 时生效
+
+## workerEnabled
+
+**类型**: Boolean
**默认值**: false
**是否必须**: false
**说明**: 是否启用 web-worker 以防布局计算时间过长阻塞页面交互
diff --git a/examples/net/gpuLayout/demo/basicFruchterman.js b/examples/net/gpuLayout/demo/basicFruchterman.js
new file mode 100644
index 0000000000..7273780e47
--- /dev/null
+++ b/examples/net/gpuLayout/demo/basicFruchterman.js
@@ -0,0 +1,462 @@
+import G6 from '@antv/g6';
+
+const data = {
+ nodes: [
+ {
+ id: '0',
+ label: '0',
+ cluster: 'a',
+ },
+ {
+ id: '1',
+ label: '1',
+ cluster: 'a',
+ },
+ {
+ id: '2',
+ label: '2',
+ cluster: 'a',
+ },
+ {
+ id: '3',
+ label: '3',
+ cluster: 'a',
+ },
+ {
+ id: '4',
+ label: '4',
+ cluster: 'a',
+ },
+ {
+ id: '5',
+ label: '5',
+ cluster: 'a',
+ },
+ {
+ id: '6',
+ label: '6',
+ cluster: 'a',
+ },
+ {
+ id: '7',
+ label: '7',
+ cluster: 'a',
+ },
+ {
+ id: '8',
+ label: '8',
+ cluster: 'a',
+ },
+ {
+ id: '9',
+ label: '9',
+ cluster: 'a',
+ },
+ {
+ id: '10',
+ label: '10',
+ cluster: 'a',
+ },
+ {
+ id: '11',
+ label: '11',
+ cluster: 'a',
+ },
+ {
+ id: '12',
+ label: '12',
+ cluster: 'a',
+ },
+ {
+ id: '13',
+ label: '13',
+ cluster: 'b',
+ },
+ {
+ id: '14',
+ label: '14',
+ cluster: 'b',
+ },
+ {
+ id: '15',
+ label: '15',
+ cluster: 'b',
+ },
+ {
+ id: '16',
+ label: '16',
+ cluster: 'b',
+ },
+ {
+ id: '17',
+ label: '17',
+ cluster: 'b',
+ },
+ {
+ id: '18',
+ label: '18',
+ cluster: 'c',
+ },
+ {
+ id: '19',
+ label: '19',
+ cluster: 'c',
+ },
+ {
+ id: '20',
+ label: '20',
+ cluster: 'c',
+ },
+ {
+ id: '21',
+ label: '21',
+ cluster: 'c',
+ },
+ {
+ id: '22',
+ label: '22',
+ cluster: 'c',
+ },
+ {
+ id: '23',
+ label: '23',
+ cluster: 'c',
+ },
+ {
+ id: '24',
+ label: '24',
+ cluster: 'c',
+ },
+ {
+ id: '25',
+ label: '25',
+ cluster: 'c',
+ },
+ {
+ id: '26',
+ label: '26',
+ cluster: 'c',
+ },
+ {
+ id: '27',
+ label: '27',
+ cluster: 'c',
+ },
+ {
+ id: '28',
+ label: '28',
+ cluster: 'c',
+ },
+ {
+ id: '29',
+ label: '29',
+ cluster: 'c',
+ },
+ {
+ id: '30',
+ label: '30',
+ cluster: 'c',
+ },
+ {
+ id: '31',
+ label: '31',
+ cluster: 'd',
+ },
+ {
+ id: '32',
+ label: '32',
+ cluster: 'd',
+ },
+ {
+ id: '33',
+ label: '33',
+ cluster: 'd',
+ },
+ ],
+ edges: [
+ {
+ source: '0',
+ target: '1',
+ },
+ {
+ source: '0',
+ target: '2',
+ },
+ {
+ source: '0',
+ target: '3',
+ },
+ {
+ source: '0',
+ target: '4',
+ },
+ {
+ source: '0',
+ target: '5',
+ },
+ {
+ source: '0',
+ target: '7',
+ },
+ {
+ source: '0',
+ target: '8',
+ },
+ {
+ source: '0',
+ target: '9',
+ },
+ {
+ source: '0',
+ target: '10',
+ },
+ {
+ source: '0',
+ target: '11',
+ },
+ {
+ source: '0',
+ target: '13',
+ },
+ {
+ source: '0',
+ target: '14',
+ },
+ {
+ source: '0',
+ target: '15',
+ },
+ {
+ source: '0',
+ target: '16',
+ },
+ {
+ source: '2',
+ target: '3',
+ },
+ {
+ source: '4',
+ target: '5',
+ },
+ {
+ source: '4',
+ target: '6',
+ },
+ {
+ source: '5',
+ target: '6',
+ },
+ {
+ source: '7',
+ target: '13',
+ },
+ {
+ source: '8',
+ target: '14',
+ },
+ {
+ source: '9',
+ target: '10',
+ },
+ {
+ source: '10',
+ target: '22',
+ },
+ {
+ source: '10',
+ target: '14',
+ },
+ {
+ source: '10',
+ target: '12',
+ },
+ {
+ source: '10',
+ target: '24',
+ },
+ {
+ source: '10',
+ target: '21',
+ },
+ {
+ source: '10',
+ target: '20',
+ },
+ {
+ source: '11',
+ target: '24',
+ },
+ {
+ source: '11',
+ target: '22',
+ },
+ {
+ source: '11',
+ target: '14',
+ },
+ {
+ source: '12',
+ target: '13',
+ },
+ {
+ source: '16',
+ target: '17',
+ },
+ {
+ source: '16',
+ target: '18',
+ },
+ {
+ source: '16',
+ target: '21',
+ },
+ {
+ source: '16',
+ target: '22',
+ },
+ {
+ source: '17',
+ target: '18',
+ },
+ {
+ source: '17',
+ target: '20',
+ },
+ {
+ source: '18',
+ target: '19',
+ },
+ {
+ source: '19',
+ target: '20',
+ },
+ {
+ source: '19',
+ target: '33',
+ },
+ {
+ source: '19',
+ target: '22',
+ },
+ {
+ source: '19',
+ target: '23',
+ },
+ {
+ source: '20',
+ target: '21',
+ },
+ {
+ source: '21',
+ target: '22',
+ },
+ {
+ source: '22',
+ target: '24',
+ },
+ {
+ source: '22',
+ target: '25',
+ },
+ {
+ source: '22',
+ target: '26',
+ },
+ {
+ source: '22',
+ target: '23',
+ },
+ {
+ source: '22',
+ target: '28',
+ },
+ {
+ source: '22',
+ target: '30',
+ },
+ {
+ source: '22',
+ target: '31',
+ },
+ {
+ source: '22',
+ target: '32',
+ },
+ {
+ source: '22',
+ target: '33',
+ },
+ {
+ source: '23',
+ target: '28',
+ },
+ {
+ source: '23',
+ target: '27',
+ },
+ {
+ source: '23',
+ target: '29',
+ },
+ {
+ source: '23',
+ target: '30',
+ },
+ {
+ source: '23',
+ target: '31',
+ },
+ {
+ source: '23',
+ target: '33',
+ },
+ {
+ source: '32',
+ target: '33',
+ },
+ ],
+};
+
+const width = document.getElementById('container').scrollWidth;
+const height = document.getElementById('container').scrollHeight || 500;
+const graph = new G6.Graph({
+ container: 'container',
+ width,
+ height,
+ modes: {
+ default: ['drag-canvas', 'drag-node'],
+ },
+ animate: true,
+ defaultNode: {
+ size: 30,
+ style: {
+ lineWidth: 2,
+ stroke: '#5B8FF9',
+ fill: '#C6E5FF',
+ },
+ },
+ defaultEdge: {
+ size: 1,
+ color: '#e2e2e2',
+ style: {
+ endArrow: {
+ path: 'M 0,0 L 8,4 L 8,-4 Z',
+ fill: '#e2e2e2'
+ },
+ },
+ },
+});
+
+graph.data(data);
+graph.render();
+
+const gpuLayout = new G6.Layout['fruchtermanGPU']({
+ canvasEl: graph.get('canvas').get('el'),
+ width,
+ height,
+ onLayoutEnd: () => {
+ graph.refreshPositions();
+ }
+})
+gpuLayout.init(data);
+gpuLayout.execute();
diff --git a/examples/net/gpuLayout/demo/fruchtermanClustering.js b/examples/net/gpuLayout/demo/fruchtermanClustering.js
new file mode 100644
index 0000000000..ff9b396a05
--- /dev/null
+++ b/examples/net/gpuLayout/demo/fruchtermanClustering.js
@@ -0,0 +1,496 @@
+import G6 from '@antv/g6';
+
+const data = {
+ nodes: [
+ {
+ id: '0',
+ label: '0',
+ cluster: 'a',
+ },
+ {
+ id: '1',
+ label: '1',
+ cluster: 'a',
+ },
+ {
+ id: '2',
+ label: '2',
+ cluster: 'a',
+ },
+ {
+ id: '3',
+ label: '3',
+ cluster: 'a',
+ },
+ {
+ id: '4',
+ label: '4',
+ cluster: 'a',
+ },
+ {
+ id: '5',
+ label: '5',
+ cluster: 'a',
+ },
+ {
+ id: '6',
+ label: '6',
+ cluster: 'a',
+ },
+ {
+ id: '7',
+ label: '7',
+ cluster: 'a',
+ },
+ {
+ id: '8',
+ label: '8',
+ cluster: 'a',
+ },
+ {
+ id: '9',
+ label: '9',
+ cluster: 'a',
+ },
+ {
+ id: '10',
+ label: '10',
+ cluster: 'a',
+ },
+ {
+ id: '11',
+ label: '11',
+ cluster: 'a',
+ },
+ {
+ id: '12',
+ label: '12',
+ cluster: 'a',
+ },
+ {
+ id: '13',
+ label: '13',
+ cluster: 'b',
+ },
+ {
+ id: '14',
+ label: '14',
+ cluster: 'b',
+ },
+ {
+ id: '15',
+ label: '15',
+ cluster: 'b',
+ },
+ {
+ id: '16',
+ label: '16',
+ cluster: 'b',
+ },
+ {
+ id: '17',
+ label: '17',
+ cluster: 'b',
+ },
+ {
+ id: '18',
+ label: '18',
+ cluster: 'c',
+ },
+ {
+ id: '19',
+ label: '19',
+ cluster: 'c',
+ },
+ {
+ id: '20',
+ label: '20',
+ cluster: 'c',
+ },
+ {
+ id: '21',
+ label: '21',
+ cluster: 'c',
+ },
+ {
+ id: '22',
+ label: '22',
+ cluster: 'c',
+ },
+ {
+ id: '23',
+ label: '23',
+ cluster: 'c',
+ },
+ {
+ id: '24',
+ label: '24',
+ cluster: 'c',
+ },
+ {
+ id: '25',
+ label: '25',
+ cluster: 'c',
+ },
+ {
+ id: '26',
+ label: '26',
+ cluster: 'c',
+ },
+ {
+ id: '27',
+ label: '27',
+ cluster: 'c',
+ },
+ {
+ id: '28',
+ label: '28',
+ cluster: 'c',
+ },
+ {
+ id: '29',
+ label: '29',
+ cluster: 'c',
+ },
+ {
+ id: '30',
+ label: '30',
+ cluster: 'c',
+ },
+ {
+ id: '31',
+ label: '31',
+ cluster: 'd',
+ },
+ {
+ id: '32',
+ label: '32',
+ cluster: 'd',
+ },
+ {
+ id: '33',
+ label: '33',
+ cluster: 'd',
+ },
+ ],
+ edges: [
+ {
+ source: '0',
+ target: '1',
+ },
+ {
+ source: '0',
+ target: '2',
+ },
+ {
+ source: '0',
+ target: '3',
+ },
+ {
+ source: '0',
+ target: '4',
+ },
+ {
+ source: '0',
+ target: '5',
+ },
+ {
+ source: '0',
+ target: '7',
+ },
+ {
+ source: '0',
+ target: '8',
+ },
+ {
+ source: '0',
+ target: '9',
+ },
+ {
+ source: '0',
+ target: '10',
+ },
+ {
+ source: '0',
+ target: '11',
+ },
+ {
+ source: '0',
+ target: '13',
+ },
+ {
+ source: '0',
+ target: '14',
+ },
+ {
+ source: '0',
+ target: '15',
+ },
+ {
+ source: '0',
+ target: '16',
+ },
+ {
+ source: '2',
+ target: '3',
+ },
+ {
+ source: '4',
+ target: '5',
+ },
+ {
+ source: '4',
+ target: '6',
+ },
+ {
+ source: '5',
+ target: '6',
+ },
+ {
+ source: '7',
+ target: '13',
+ },
+ {
+ source: '8',
+ target: '14',
+ },
+ {
+ source: '9',
+ target: '10',
+ },
+ {
+ source: '10',
+ target: '22',
+ },
+ {
+ source: '10',
+ target: '14',
+ },
+ {
+ source: '10',
+ target: '12',
+ },
+ {
+ source: '10',
+ target: '24',
+ },
+ {
+ source: '10',
+ target: '21',
+ },
+ {
+ source: '10',
+ target: '20',
+ },
+ {
+ source: '11',
+ target: '24',
+ },
+ {
+ source: '11',
+ target: '22',
+ },
+ {
+ source: '11',
+ target: '14',
+ },
+ {
+ source: '12',
+ target: '13',
+ },
+ {
+ source: '16',
+ target: '17',
+ },
+ {
+ source: '16',
+ target: '18',
+ },
+ {
+ source: '16',
+ target: '21',
+ },
+ {
+ source: '16',
+ target: '22',
+ },
+ {
+ source: '17',
+ target: '18',
+ },
+ {
+ source: '17',
+ target: '20',
+ },
+ {
+ source: '18',
+ target: '19',
+ },
+ {
+ source: '19',
+ target: '20',
+ },
+ {
+ source: '19',
+ target: '33',
+ },
+ {
+ source: '19',
+ target: '22',
+ },
+ {
+ source: '19',
+ target: '23',
+ },
+ {
+ source: '20',
+ target: '21',
+ },
+ {
+ source: '21',
+ target: '22',
+ },
+ {
+ source: '22',
+ target: '24',
+ },
+ {
+ source: '22',
+ target: '25',
+ },
+ {
+ source: '22',
+ target: '26',
+ },
+ {
+ source: '22',
+ target: '23',
+ },
+ {
+ source: '22',
+ target: '28',
+ },
+ {
+ source: '22',
+ target: '30',
+ },
+ {
+ source: '22',
+ target: '31',
+ },
+ {
+ source: '22',
+ target: '32',
+ },
+ {
+ source: '22',
+ target: '33',
+ },
+ {
+ source: '23',
+ target: '28',
+ },
+ {
+ source: '23',
+ target: '27',
+ },
+ {
+ source: '23',
+ target: '29',
+ },
+ {
+ source: '23',
+ target: '30',
+ },
+ {
+ source: '23',
+ target: '31',
+ },
+ {
+ source: '23',
+ target: '33',
+ },
+ {
+ source: '32',
+ target: '33',
+ },
+ ],
+};
+
+const colors = [
+ '#BDD2FD',
+ '#BDEFDB',
+ '#C2C8D5',
+ '#FBE5A2',
+ '#F6C3B7',
+ '#B6E3F5',
+ '#D3C6EA',
+ '#FFD8B8',
+ '#AAD8D8',
+ '#FFD6E7',
+];
+const strokes = [
+ '#5B8FF9',
+ '#5AD8A6',
+ '#5D7092',
+ '#F6BD16',
+ '#E8684A',
+ '#6DC8EC',
+ '#9270CA',
+ '#FF9D4D',
+ '#269A99',
+ '#FF99C3',
+];
+
+const nodes = data.nodes;
+const clusterMap = new Map();
+let clusterId = 0;
+nodes.forEach(function(node) {
+ // cluster
+ if (node.cluster && clusterMap.get(node.cluster) === undefined) {
+ clusterMap.set(node.cluster, clusterId);
+ clusterId++;
+ }
+ const cid = clusterMap.get(node.cluster);
+ if (!node.style) {
+ node.style = {};
+ }
+ node.style.fill = colors[cid % colors.length];
+ node.style.stroke = strokes[cid % strokes.length];
+});
+const graphDiv = document.getElementById('container');
+const width = graphDiv.scrollWidth;
+const height = graphDiv.scrollHeight;
+const graph = new G6.Graph({
+ container: 'container',
+ width,
+ height,
+ modes: {
+ default: ['drag-canvas', 'drag-node'],
+ },
+ layout: {
+ type: 'fruchterman',
+ gravity: 10,
+ speed: 5,
+ clustering: true,
+ },
+ animate: true,
+ defaultNode: {
+ size: 20,
+ style: {
+ lineWidth: 2,
+ },
+ },
+ defaultEdge: {
+ size: 1,
+ color: '#e2e2e2',
+ style: {
+ endArrow: {
+ path: 'M 0,0 L 8,4 L 8,-4 Z',
+ fill: '#e2e2e2'
+ },
+ },
+ },
+});
+graph.data(data);
+graph.render();
diff --git a/examples/net/gpuLayout/demo/fruchtermanComplexData.js b/examples/net/gpuLayout/demo/fruchtermanComplexData.js
new file mode 100644
index 0000000000..b8a525613b
--- /dev/null
+++ b/examples/net/gpuLayout/demo/fruchtermanComplexData.js
@@ -0,0 +1,51 @@
+import G6 from '@antv/g6';
+
+const width = document.getElementById('container').scrollWidth;
+const height = document.getElementById('container').scrollHeight || 500;
+const graph = new G6.Graph({
+ container: 'container',
+ width,
+ height,
+ modes: {
+ default: ['drag-canvas', 'drag-node'],
+ },
+ animate: true,
+ defaultNode: {
+ size: 10,
+ style: {
+ lineWidth: 2,
+ stroke: '#5B8FF9',
+ fill: '#C6E5FF',
+ },
+ },
+ defaultEdge: {
+ size: 1,
+ color: '#e2e2e2',
+ style: {
+ endArrow: {
+ path: 'M 0,0 L 8,4 L 8,-4 Z',
+ fill: '#e2e2e2'
+ },
+ },
+ },
+});
+
+
+fetch('https://gw.alipayobjects.com/os/basement_prod/7bacd7d1-4119-4ac1-8be3-4c4b9bcbc25f.json')
+ .then(res => res.json())
+ .then(data => {
+ graph.data(data);
+ graph.render();
+
+ const gpuLayout = new G6.Layout['fruchtermanGPU']({
+ canvasEl: graph.get('canvas').get('el'),
+ width,
+ height,
+ maxIteration: 1000,
+ onLayoutEnd: () => {
+ graph.refreshPositions();
+ }
+ })
+ gpuLayout.init(data);
+ gpuLayout.execute();
+ });
diff --git a/examples/net/gpuLayout/demo/fruchtermanConfigurationTranslate.js b/examples/net/gpuLayout/demo/fruchtermanConfigurationTranslate.js
new file mode 100644
index 0000000000..1b5fc0145a
--- /dev/null
+++ b/examples/net/gpuLayout/demo/fruchtermanConfigurationTranslate.js
@@ -0,0 +1,538 @@
+import G6 from '@antv/g6';
+
+const data = {
+ nodes: [
+ {
+ id: '0',
+ label: '0',
+ cluster: 'a',
+ },
+ {
+ id: '1',
+ label: '1',
+ cluster: 'a',
+ },
+ {
+ id: '2',
+ label: '2',
+ cluster: 'a',
+ },
+ {
+ id: '3',
+ label: '3',
+ cluster: 'a',
+ },
+ {
+ id: '4',
+ label: '4',
+ cluster: 'a',
+ },
+ {
+ id: '5',
+ label: '5',
+ cluster: 'a',
+ },
+ {
+ id: '6',
+ label: '6',
+ cluster: 'a',
+ },
+ {
+ id: '7',
+ label: '7',
+ cluster: 'a',
+ },
+ {
+ id: '8',
+ label: '8',
+ cluster: 'a',
+ },
+ {
+ id: '9',
+ label: '9',
+ cluster: 'a',
+ },
+ {
+ id: '10',
+ label: '10',
+ cluster: 'a',
+ },
+ {
+ id: '11',
+ label: '11',
+ cluster: 'a',
+ },
+ {
+ id: '12',
+ label: '12',
+ cluster: 'a',
+ },
+ {
+ id: '13',
+ label: '13',
+ cluster: 'b',
+ },
+ {
+ id: '14',
+ label: '14',
+ cluster: 'b',
+ },
+ {
+ id: '15',
+ label: '15',
+ cluster: 'b',
+ },
+ {
+ id: '16',
+ label: '16',
+ cluster: 'b',
+ },
+ {
+ id: '17',
+ label: '17',
+ cluster: 'b',
+ },
+ {
+ id: '18',
+ label: '18',
+ cluster: 'c',
+ },
+ {
+ id: '19',
+ label: '19',
+ cluster: 'c',
+ },
+ {
+ id: '20',
+ label: '20',
+ cluster: 'c',
+ },
+ {
+ id: '21',
+ label: '21',
+ cluster: 'c',
+ },
+ {
+ id: '22',
+ label: '22',
+ cluster: 'c',
+ },
+ {
+ id: '23',
+ label: '23',
+ cluster: 'c',
+ },
+ {
+ id: '24',
+ label: '24',
+ cluster: 'c',
+ },
+ {
+ id: '25',
+ label: '25',
+ cluster: 'c',
+ },
+ {
+ id: '26',
+ label: '26',
+ cluster: 'c',
+ },
+ {
+ id: '27',
+ label: '27',
+ cluster: 'c',
+ },
+ {
+ id: '28',
+ label: '28',
+ cluster: 'c',
+ },
+ {
+ id: '29',
+ label: '29',
+ cluster: 'c',
+ },
+ {
+ id: '30',
+ label: '30',
+ cluster: 'c',
+ },
+ {
+ id: '31',
+ label: '31',
+ cluster: 'd',
+ },
+ {
+ id: '32',
+ label: '32',
+ cluster: 'd',
+ },
+ {
+ id: '33',
+ label: '33',
+ cluster: 'd',
+ },
+ ],
+ edges: [
+ {
+ source: '0',
+ target: '1',
+ },
+ {
+ source: '0',
+ target: '2',
+ },
+ {
+ source: '0',
+ target: '3',
+ },
+ {
+ source: '0',
+ target: '4',
+ },
+ {
+ source: '0',
+ target: '5',
+ },
+ {
+ source: '0',
+ target: '7',
+ },
+ {
+ source: '0',
+ target: '8',
+ },
+ {
+ source: '0',
+ target: '9',
+ },
+ {
+ source: '0',
+ target: '10',
+ },
+ {
+ source: '0',
+ target: '11',
+ },
+ {
+ source: '0',
+ target: '13',
+ },
+ {
+ source: '0',
+ target: '14',
+ },
+ {
+ source: '0',
+ target: '15',
+ },
+ {
+ source: '0',
+ target: '16',
+ },
+ {
+ source: '2',
+ target: '3',
+ },
+ {
+ source: '4',
+ target: '5',
+ },
+ {
+ source: '4',
+ target: '6',
+ },
+ {
+ source: '5',
+ target: '6',
+ },
+ {
+ source: '7',
+ target: '13',
+ },
+ {
+ source: '8',
+ target: '14',
+ },
+ {
+ source: '9',
+ target: '10',
+ },
+ {
+ source: '10',
+ target: '22',
+ },
+ {
+ source: '10',
+ target: '14',
+ },
+ {
+ source: '10',
+ target: '12',
+ },
+ {
+ source: '10',
+ target: '24',
+ },
+ {
+ source: '10',
+ target: '21',
+ },
+ {
+ source: '10',
+ target: '20',
+ },
+ {
+ source: '11',
+ target: '24',
+ },
+ {
+ source: '11',
+ target: '22',
+ },
+ {
+ source: '11',
+ target: '14',
+ },
+ {
+ source: '12',
+ target: '13',
+ },
+ {
+ source: '16',
+ target: '17',
+ },
+ {
+ source: '16',
+ target: '18',
+ },
+ {
+ source: '16',
+ target: '21',
+ },
+ {
+ source: '16',
+ target: '22',
+ },
+ {
+ source: '17',
+ target: '18',
+ },
+ {
+ source: '17',
+ target: '20',
+ },
+ {
+ source: '18',
+ target: '19',
+ },
+ {
+ source: '19',
+ target: '20',
+ },
+ {
+ source: '19',
+ target: '33',
+ },
+ {
+ source: '19',
+ target: '22',
+ },
+ {
+ source: '19',
+ target: '23',
+ },
+ {
+ source: '20',
+ target: '21',
+ },
+ {
+ source: '21',
+ target: '22',
+ },
+ {
+ source: '22',
+ target: '24',
+ },
+ {
+ source: '22',
+ target: '25',
+ },
+ {
+ source: '22',
+ target: '26',
+ },
+ {
+ source: '22',
+ target: '23',
+ },
+ {
+ source: '22',
+ target: '28',
+ },
+ {
+ source: '22',
+ target: '30',
+ },
+ {
+ source: '22',
+ target: '31',
+ },
+ {
+ source: '22',
+ target: '32',
+ },
+ {
+ source: '22',
+ target: '33',
+ },
+ {
+ source: '23',
+ target: '28',
+ },
+ {
+ source: '23',
+ target: '27',
+ },
+ {
+ source: '23',
+ target: '29',
+ },
+ {
+ source: '23',
+ target: '30',
+ },
+ {
+ source: '23',
+ target: '31',
+ },
+ {
+ source: '23',
+ target: '33',
+ },
+ {
+ source: '32',
+ target: '33',
+ },
+ ],
+};
+
+const colors = [
+ '#BDD2FD',
+ '#BDEFDB',
+ '#C2C8D5',
+ '#FBE5A2',
+ '#F6C3B7',
+ '#B6E3F5',
+ '#D3C6EA',
+ '#FFD8B8',
+ '#AAD8D8',
+ '#FFD6E7',
+];
+const strokes = [
+ '#5B8FF9',
+ '#5AD8A6',
+ '#5D7092',
+ '#F6BD16',
+ '#E8684A',
+ '#6DC8EC',
+ '#9270CA',
+ '#FF9D4D',
+ '#269A99',
+ '#FF99C3',
+];
+
+const nodes = data.nodes;
+const clusterMap = new Map();
+let clusterId = 0;
+nodes.forEach(function(node) {
+ // cluster
+ if (node.cluster && clusterMap.get(node.cluster) === undefined) {
+ clusterMap.set(node.cluster, clusterId);
+ clusterId++;
+ }
+ const cid = clusterMap.get(node.cluster);
+ if (!node.style) {
+ node.style = {};
+ }
+ node.style.fill = colors[cid % colors.length];
+ node.style.stroke = strokes[cid % strokes.length];
+});
+const graphDiv = document.getElementById('container');
+const descriptionDiv = document.createElement('div');
+descriptionDiv.innerHTML = 'Fruchterman layout, gravity = 1';
+graphDiv.appendChild(descriptionDiv);
+const width = graphDiv.scrollWidth;
+const height = graphDiv.scrollHeight - 30;
+const graph = new G6.Graph({
+ container: 'container',
+ width,
+ height,
+ modes: {
+ default: ['drag-canvas', 'drag-node'],
+ },
+ layout: {
+ type: 'fruchterman',
+ gravity: 1,
+ speed: 5,
+ },
+ animate: true,
+ defaultNode: {
+ size: 20,
+ style: {
+ lineWidth: 2,
+ },
+ },
+ defaultEdge: {
+ size: 1,
+ color: '#e2e2e2',
+ style: {
+ endArrow: {
+ path: 'M 0,0 L 8,4 L 8,-4 Z',
+ fill: '#e2e2e2'
+ },
+ },
+ },
+});
+graph.data(data);
+graph.render();
+
+layoutConfigTranslation();
+
+function layoutConfigTranslation() {
+ setTimeout(function() {
+ descriptionDiv.innerHTML = 'Fructherman layout, gravity = 5';
+ graph.updateLayout({
+ gravity: 5,
+ });
+ }, 1000);
+
+ setTimeout(function() {
+ descriptionDiv.innerHTML = 'Fructherman layout, gravity = 10, layout by cluster';
+ graph.updateLayout({
+ gravity: 10,
+ clustering: true,
+ });
+ }, 2500);
+
+ setTimeout(function() {
+ descriptionDiv.innerHTML = 'Fructherman layout, gravity = 20, layout by cluster';
+ graph.updateLayout({
+ gravity: 20,
+ });
+ }, 4000);
+
+ setTimeout(function() {
+ descriptionDiv.innerHTML = 'Fructherman layout, gravity = 50, layout by cluster';
+ graph.updateLayout({
+ gravity: 50,
+ });
+ }, 5500);
+
+ setTimeout(function() {
+ descriptionDiv.innerHTML = 'Fructherman layout, gravity = 80, layout by cluster';
+ graph.updateLayout({
+ gravity: 80,
+ });
+ }, 7000);
+}
diff --git a/examples/net/gpuLayout/demo/meta.json b/examples/net/gpuLayout/demo/meta.json
new file mode 100644
index 0000000000..78ba29dde3
--- /dev/null
+++ b/examples/net/gpuLayout/demo/meta.json
@@ -0,0 +1,40 @@
+{
+ "title": {
+ "zh": "中文分类",
+ "en": "Category"
+ },
+ "demos": [
+ {
+ "filename": "basicFruchterman.js",
+ "title": {
+ "zh": "基本 Fruchterman 布局",
+ "en": "Basic Fruchterman Layout"
+ },
+ "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*1KY7SLEXxqMAAAAAAAAAAABkARQnAQ"
+ },
+ {
+ "filename": "fruchtermanComplexData.js",
+ "title": {
+ "zh": "Fruchterman 复杂数据",
+ "en": "Fruchterman with Complex Data"
+ },
+ "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*0sl9RZ7Cp28AAAAAAAAAAABkARQnAQ"
+ },
+ {
+ "filename": "fruchtermanClustering.js",
+ "title": {
+ "zh": "Fruchterman 聚类布局",
+ "en": "Fruchterman with Clustering"
+ },
+ "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*WO1OTbNE_ugAAAAAAAAAAABkARQnAQ"
+ },
+ {
+ "filename": "fruchtermanConfigurationTranslate.js",
+ "title": {
+ "zh": "Fruchterman 布局参数动态变化",
+ "en": "Update the Configurations for Fruchterman"
+ },
+ "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*W9MoQoKaQbYAAAAAAAAAAABkARQnAQ"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/examples/net/gpuLayout/index.en.md b/examples/net/gpuLayout/index.en.md
new file mode 100644
index 0000000000..fa0f3d406d
--- /dev/null
+++ b/examples/net/gpuLayout/index.en.md
@@ -0,0 +1,15 @@
+---
+title: GPU Layout
+order: 12
+---
+
+Fruchterman Reingold Layout is a kind of force-directed layout in theory. The differences are the definitions of attracitve force and repulsive force.
+
+## Usage
+
+As the demo below, you can deploy it in `layout` while instantiating Graph. it can also be used for [Subgraph Layout](/zh/docs/manual/middle/layout/#%E5%AD%90%E5%9B%BE%E5%B8%83%E5%B1%80). By tuning the parameters, you can adjust the iteration number, layout compactness, layout by clusters, and so on.
+
+- Example 1 : Basic Fruchterman layout.
+- Example 2 : Fruchterman clustering layout.
+- Example 3 : Translate the layout parameters in dynamic.
+- Example 4 : Fruchterman layout with web-worker in case layout calculation takes too long to block page interaction.
diff --git a/examples/net/gpuLayout/index.zh.md b/examples/net/gpuLayout/index.zh.md
new file mode 100644
index 0000000000..1f42f5c7be
--- /dev/null
+++ b/examples/net/gpuLayout/index.zh.md
@@ -0,0 +1,15 @@
+---
+title: GPU 图布局
+order: 12
+---
+
+Fruchterman Reingold 布局算法在原理上而言属于力导向布局算法。其引力与斥力的定义方式与经典的 Force Diected 力导向图布局有少许不同。
+
+## 使用指南
+
+G6 内置的 Fruchterman 布局可在实例化 Graph 时使用该布局。除此之外,还可以如[子图布局](/zh/docs/manual/middle/layout/#%E5%AD%90%E5%9B%BE%E5%B8%83%E5%B1%80)所示单独使用布局。该布局可以通过配置调整迭代次数、紧凑程度、是否按照聚类布局等。
+
+- 代码演示 1 :基本的 Fruchterman 布局。
+- 代码演示 2 :Fruchterman 的聚类布局。
+- 代码演示 3 :Fruchterman 布局参数动态变化。
+- 代码演示 4 :Fruchterman 使用 web-worker 以避免阻塞页面。
diff --git a/gatsby-browser.js b/gatsby-browser.js
index d314bea4b2..de19224340 100644
--- a/gatsby-browser.js
+++ b/gatsby-browser.js
@@ -1,4 +1,4 @@
-// window.g6 = require('./src/index.ts'); // import the source for debugging
-window.g6 = require('./dist/g6.min.js'); // import the package for webworker
+window.g6 = require('./src/index.ts'); // import the source for debugging
+// window.g6 = require('./dist/g6.min.js'); // import the package for webworker
window.insertCss = require('insert-css');
window.Chart = require('@antv/chart-node-g6');
diff --git a/src/layout/gpu/fruchterman.ts b/src/layout/gpu/fruchterman.ts
index 398136a0fa..19c582aaa0 100644
--- a/src/layout/gpu/fruchterman.ts
+++ b/src/layout/gpu/fruchterman.ts
@@ -3,10 +3,9 @@
* @author shiwu.wyy@antfin.com
*/
-import { EdgeConfig, IPointTuple, NodeConfig, NodeIdxMap } from '../types';
-import { BaseLayout } from './layout';
+import { EdgeConfig, IPointTuple, NodeConfig, NodeIdxMap } from '../../types';
+import { BaseLayout } from '../layout';
import { isNumber } from '@antv/util';
-import { Point } from '@antv/g-base';
import { World } from '@antv/g-webgpu';
const lineIndexBufferData = [];
@@ -56,6 +55,7 @@ const convertWebGLCoord2Canvas = (c: number, size: number) => {
return ((c + 1) / 2) * size;
}
+
const gCode = `
import { globalInvocationID } from 'g-webgpu';
@@ -73,6 +73,12 @@ class Fruchterman {
@in
u_K2: float;
+
+ @in
+ u_CenterX: float;
+
+ @in
+ u_CenterY: float;
@in
u_Gravity: float;
@@ -86,15 +92,14 @@ class Fruchterman {
calcRepulsive(i: int, currentNode: vec4): vec2 {
let dx = 0, dy = 0;
for (let j = 0; j < VERTEX_COUNT; j++) {
- if (i != j + 1) {
+ if (i != j) {
const nextNode = this.u_Data[j];
const xDist = currentNode[0] - nextNode[0];
const yDist = currentNode[1] - nextNode[1];
- const dist = sqrt(xDist * xDist + yDist * yDist) + 0.01;
+ const dist = (xDist * xDist + yDist * yDist) + 0.01;
if (dist > 0.0) {
- const repulsiveF = this.u_K2 / dist;
- dx += xDist / dist * repulsiveF;
- dy += yDist / dist * repulsiveF;
+ dx += this.u_K2 * xDist / dist ;
+ dy += this.u_K2 * yDist / dist ;
}
}
}
@@ -102,9 +107,10 @@ class Fruchterman {
}
calcGravity(currentNode: vec4): vec2 {
- const d = sqrt(currentNode[0] * currentNode[0] + currentNode[1] * currentNode[1]);
- const gf = 0.01 * this.u_K * this.u_Gravity * d;
- return [gf * currentNode[0] / d, gf * currentNode[1] / d];
+ const vx = currentNode[0] - this.u_CenterX;
+ const vy = currentNode[1] - this.u_CenterY;
+ const gf = 0.01 * this.u_K * this.u_Gravity;
+ return [gf * vx, gf * vy];
}
calcAttractive(currentNode: vec4): vec2 {
@@ -128,10 +134,10 @@ class Fruchterman {
const xDist = currentNode[0] - nextNode[0];
const yDist = currentNode[1] - nextNode[1];
const dist = sqrt(xDist * xDist + yDist * yDist) + 0.01;
- const attractiveF = dist * dist / this.u_K;
+ const attractiveF = dist / this.u_K;
if (dist > 0.0) {
- dx -= xDist / dist * attractiveF;
- dy -= yDist / dist * attractiveF;
+ dx -= xDist * attractiveF;
+ dy -= yDist * attractiveF;
}
}
return [dx, dy];
@@ -180,6 +186,8 @@ class Fruchterman {
currentNode[3]
];
}
+//const currentDis = this.u_MaxDisplace;
+ //this.u_MaxDisplace = currentDis * 0.99;
}
}
`;
@@ -207,7 +215,7 @@ export default class FruchtermanGPULayout extends BaseLayout {
/** 重力大小,影响图的紧凑程度 */
public gravity: number = 10;
/** 速度 */
- public speed: number = 1;
+ public speed: number = 0.1;
/** 是否产生聚类力 */
public clustering: boolean = false;
/** 聚类力大小 */
@@ -230,7 +238,7 @@ export default class FruchtermanGPULayout extends BaseLayout {
maxIteration: 1000,
center: [0, 0],
gravity: 10,
- speed: 1,
+ speed: 0.1,
clustering: false,
clusterGravity: 10,
};
@@ -277,8 +285,10 @@ export default class FruchtermanGPULayout extends BaseLayout {
self.height = window.innerHeight;
}
const center = self.center;
- const maxDisplace = self.width / 10;
- const k = Math.sqrt((self.width * self.height) / (nodes.length + 1));
+ const area = self.height * self.width;
+ const maxDisplace = Math.sqrt(area) / 10;
+ const k2 = area / (nodes.length + 1);
+ const k = Math.sqrt(k2);
const gravity = self.gravity;
const speed = self.speed;
const clustering = self.clustering;
@@ -332,13 +342,14 @@ export default class FruchtermanGPULayout extends BaseLayout {
const compute = world.createComputePipeline({
shader: gCode,
dispatch: [numParticles, 1, 1],
- maxIteration: self.maxIteration,
+ maxIteration,
onCompleted: (finalParticleData) => {
+ console.log(maxIteration, gravity, center, k, k2, maxDisplace, speed)
self.nodes.forEach((node, i) => {
const x = finalParticleData[4 * i];
const y = finalParticleData[4 * i + 1];
- node.x = convertWebGLCoord2Canvas(x, self.width);
- node.y = convertWebGLCoord2Canvas(y, self.height);
+ node.x = x; //convertWebGLCoord2Canvas(x, self.width);
+ node.y = y; //convertWebGLCoord2Canvas(y, self.height);
});
self.onLayoutEnd && self.onLayoutEnd();
// setTimeElapsed(window.performance.now() - timeStart);
@@ -357,20 +368,32 @@ export default class FruchtermanGPULayout extends BaseLayout {
world.setBinding(
compute,
'u_K',
- Math.sqrt((numParticles * numParticles) / (numParticles + 1) / 300),
+ k,
);
world.setBinding(
compute,
'u_K2',
- (numParticles * numParticles) / (numParticles + 1) / 300 / 300,
+ k2,
);
- world.setBinding(compute, 'u_Gravity', 50);
- world.setBinding(compute, 'u_Speed', 0.1);
+ world.setBinding(
+ compute,
+ 'u_CenterX',
+ self.width / 2,
+ );
+ world.setBinding(
+ compute,
+ 'u_CenterY',
+ self.height / 2,
+ );
+ world.setBinding(compute, 'u_Gravity', gravity);
+ world.setBinding(compute, 'u_Speed', speed);
world.setBinding(
compute,
'u_MaxDisplace',
- Math.sqrt(numParticles * numParticles) / 10,
+ maxDisplace,
);
+ world.setBinding(compute, 'u_CenterX', center[0]);
+ world.setBinding(compute, 'u_CenterY', center[1]);
world.setBinding(compute, 'MAX_EDGE_PER_VERTEX', maxEdgePerVetex);
world.setBinding(compute, 'VERTEX_COUNT', numParticles);
}
diff --git a/src/layout/gpu/gpu-demo.ts b/src/layout/gpu/gpu-demo.ts
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/src/layout/index.ts b/src/layout/index.ts
index fb8e615798..70e8532c29 100644
--- a/src/layout/index.ts
+++ b/src/layout/index.ts
@@ -17,6 +17,7 @@ import MDS from './mds';
import Radial from './radial/radial';
import Random from './random';
import ComboForce from './comboForce';
+import FruchtermanGPU from './gpu/fruchterman';
const layouts = {
circular: Circular,
@@ -30,6 +31,7 @@ const layouts = {
mds: MDS,
radial: Radial,
random: Random,
+ fruchtermanGPU: FruchtermanGPU
};
// 注册布局