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 }; // 注册布局