From 9990dd6def24e48d2f4690584ae8fb448abf3a84 Mon Sep 17 00:00:00 2001 From: brickmaker Date: Mon, 9 Aug 2021 11:00:59 +0800 Subject: [PATCH 1/3] =?UTF-8?q?perf:=20=E2=9A=A1=EF=B8=8F=20new=20force=20?= =?UTF-8?q?computing=20method=20for=20force=20directed=20layout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 + .../graphin/src/layout/force/ForceLayout.ts | 25 +++- .../graphin/src/layout/force/ForceNBody.ts | 120 ++++++++++++++++++ 3 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 packages/graphin/src/layout/force/ForceNBody.ts diff --git a/package.json b/package.json index 7b979ed83..e2b6d4b64 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "release:patch": "changelog -p && git add CHANGELOG.md && git commit -m 'updated CHANGELOG.md' && npm version patch && git push origin && git push origin --tags" }, "devDependencies": { + "@types/d3-quadtree": "^3.0.2", "@types/jest": "^25.2.3", "@types/react": "^16.9.11", "@types/react-dom": "^16.9.3", @@ -79,6 +80,7 @@ "@ant-design/icons-react": "^2.0.1", "@antv/gatsby-theme-antv": "latest", "antd": "^4.12.3", + "d3-quadtree": "^3.0.1", "rc-footer": "^0.6.6", "react": "^17.0.1", "react-dom": "^17.0.1" diff --git a/packages/graphin/src/layout/force/ForceLayout.ts b/packages/graphin/src/layout/force/ForceLayout.ts index a286e6c81..ad49da831 100644 --- a/packages/graphin/src/layout/force/ForceLayout.ts +++ b/packages/graphin/src/layout/force/ForceLayout.ts @@ -5,6 +5,7 @@ import Spring from './Spring'; import { getDegree } from '../utils/graph'; import { GraphinData as Data, IUserNode as NodeType } from '../../typings/type'; import { Item, Graph } from '@antv/g6/'; +import { forceNBody } from './ForceNBody'; type ForceNodeType = Node; @@ -39,9 +40,7 @@ export interface ForceProps { /** 其他节点的施加力的因子 */ others?: number; /** 向心力的中心点,默认为画布的中心 */ - center?: ( - node: NodeType, - ) => { + center?: (node: NodeType) => { x: number; y: number; }; @@ -406,7 +405,8 @@ class ForceLayout { }; tick = (interval: number) => { - this.updateCoulombsLaw(); + // this.updateCoulombsLaw(); + this.updateCoulombsLawOptimized(); this.updateHookesLaw(); this.attractToCentre(); this.updateVelocity(interval); @@ -414,6 +414,23 @@ class ForceLayout { }; /** 布局算法 */ + updateCoulombsLawOptimized = () => { + // 用force-n-body结合 Barnes-Hut approximation 优化的方法 + const { coulombDisScale } = this.props; + const { repulsion } = this.props; + const nodes = this.nodes.map(n => { + const point = this.nodePoints.get(n.id).p; + return { + x: point.x, + y: point.y, + }; + }); + const forces = forceNBody(nodes, coulombDisScale, repulsion); + this.nodes.forEach((node, i) => { + this.nodePoints.get(node.id).updateAcc(new Vector(forces[i].vx, forces[i].vy)); + }); + }; + updateCoulombsLaw = () => { const len = this.nodes.length; diff --git a/packages/graphin/src/layout/force/ForceNBody.ts b/packages/graphin/src/layout/force/ForceNBody.ts new file mode 100644 index 000000000..4d7fdc48f --- /dev/null +++ b/packages/graphin/src/layout/force/ForceNBody.ts @@ -0,0 +1,120 @@ +import { quadtree } from 'd3-quadtree'; + +const theta2 = 0.81; // Barnes-Hut approximation threshold + +interface Node { + x: number; + y: number; +} + +interface InternalNode { + x: number; + y: number; + vx: number; + vy: number; +} + +export function forceNBodyBruteForce(nodes: Node[], coulombDisScale: number, repulsion: number) { + return nodes.map((a, i) => { + const v = { vx: 0, vy: 0 }; + + nodes.forEach((b, j) => { + if (i === j) return; + const dx = a.x - b.x; + const dy = a.y - b.y; + const len = Math.sqrt(dx * dx + dy * dy); + const dis = len * coulombDisScale; + const force = repulsion / (dis * dis) || 0; + + v.vx += (dx / len || 0) * force; + v.vy += (dy / len || 0) * force; + }); + + return v; + }); +} + +export function forceNBody(nodes: Node[], coulombDisScale: number, repulsion: number) { + const weight = repulsion / (coulombDisScale * coulombDisScale); + const data = nodes.map((n, i) => ({ + index: i, + ...n, + vx: 0, + vy: 0, + weight, + })); + + const tree = quadtree( + data, + d => d.x, + d => d.y, + ).visitAfter(accumulate); // init internal node + + data.forEach(n => { + computeForce(n, tree); + }); + + return data.map(n => ({ + vx: n.vx, + vy: n.vy, + })); +} + +// @ts-ignore +function accumulate(quad) { + let accWeight = 0; + let accX = 0; + let accY = 0; + + if (quad.length) { + // internal node, accumulate 4 child quads + for (let i = 0; i < 4; i++) { + const q = quad[i]; + if (q && q.weight) { + accWeight += q.weight; + accX += q.x * q.weight; + accY += q.y * q.weight; + } + } + quad.x = accX / accWeight; + quad.y = accY / accWeight; + quad.weight = accWeight; + } else { + // leaf node + const q = quad; + quad.x = q.data.x; + quad.y = q.data.y; + quad.weight = q.data.weight; + } +} + +// @ts-ignore +function computeForce(node: InternalNode, tree) { + // @ts-ignore + const apply = (quad, x1: number, y1: number, x2: number, y2: number) => { + const dx = node.x - quad.x; + const dy = node.y - quad.y; + const width = x2 - x1; + const len2 = dx * dx + dy * dy; + const len = Math.sqrt(len2); + + // far node, apply Barnes-Hut approximation + if ((width * width) / theta2 < len2) { + node.vx += ((dx / len) * quad.weight) / len2; + node.vy += ((dy / len) * quad.weight) / len2; + + return true; + } + // near quad, compute force directly + if (quad.length) return false; // internal node, visit children + + // leaf node + + if (quad.data !== node) { + node.vx += ((dx / len) * quad.data.weight) / len2; + node.vy += ((dy / len) * quad.data.weight) / len2; + } + }; + + tree.visit(apply); +} From dec3feac77a4190002f7bc7e2e5a49b6053bbcec Mon Sep 17 00:00:00 2001 From: "pomelo.lcw" Date: Mon, 9 Aug 2021 11:50:56 +0800 Subject: [PATCH 2/3] chore:update deps --- package.json | 2 -- packages/graphin/package.json | 8 +++++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index e2b6d4b64..7b979ed83 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ "release:patch": "changelog -p && git add CHANGELOG.md && git commit -m 'updated CHANGELOG.md' && npm version patch && git push origin && git push origin --tags" }, "devDependencies": { - "@types/d3-quadtree": "^3.0.2", "@types/jest": "^25.2.3", "@types/react": "^16.9.11", "@types/react-dom": "^16.9.3", @@ -80,7 +79,6 @@ "@ant-design/icons-react": "^2.0.1", "@antv/gatsby-theme-antv": "latest", "antd": "^4.12.3", - "d3-quadtree": "^3.0.1", "rc-footer": "^0.6.6", "react": "^17.0.1", "react-dom": "^17.0.1" diff --git a/packages/graphin/package.json b/packages/graphin/package.json index 1bad4d201..f75efcf4a 100644 --- a/packages/graphin/package.json +++ b/packages/graphin/package.json @@ -1,6 +1,6 @@ { "name": "@antv/graphin", - "version": "2.2.0", + "version": "2.3.0", "description": "the react toolkit for graph analysis based on g6", "main": "lib/index.js", "module": "es/index.js", @@ -40,7 +40,8 @@ "typescript": "^4.1.3", "webpack": "^4.41.5", "webpack-bundle-analyzer": "^3.6.0", - "webpack-cli": "^3.3.10" + "webpack-cli": "^3.3.10", + "@types/d3-quadtree": "^3.0.2" }, "sideEffects": [ "*.css" @@ -50,7 +51,8 @@ "dependencies": { "@antv/g6": "4.3.4", "@antv/util": "^2.0.10", - "lodash-es": "^4.17.21" + "lodash-es": "^4.17.21", + "d3-quadtree": "^3.0.1" }, "peerDependencies": { "react": ">=16.9.0", From c259e8f8eaff8e67c55e6d683cf5953426242a2e Mon Sep 17 00:00:00 2001 From: "pomelo.lcw" Date: Mon, 9 Aug 2021 11:51:17 +0800 Subject: [PATCH 3/3] chore:update components version --- packages/graphin-components/package.json | 2 +- packages/graphin-components/src/index.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/graphin-components/package.json b/packages/graphin-components/package.json index f486f7144..0ede16d52 100644 --- a/packages/graphin-components/package.json +++ b/packages/graphin-components/package.json @@ -1,6 +1,6 @@ { "name": "@antv/graphin-components", - "version": "2.2.0", + "version": "2.3.0", "description": "Components for graphin", "main": "lib/index.js", "module": "es/index.js", diff --git a/packages/graphin-components/src/index.ts b/packages/graphin-components/src/index.ts index bd72bf41f..48d232b26 100644 --- a/packages/graphin-components/src/index.ts +++ b/packages/graphin-components/src/index.ts @@ -1,13 +1,14 @@ +export { default as Combo } from './Combo'; export { default as ContextMenu } from './ContextMenu'; export { default as CreateEdge } from './CreateEdge'; export { default as EdgeBundling } from './EdgeBundling'; export { default as FishEye } from './FishEye'; +export { default as Grid } from './Grid'; export { default as Hull, HullCfg } from './Hull'; export { default as LayoutSelector } from './LayoutSelector'; export { default as Legend } from './Legend'; export { default as MiniMap } from './MiniMap'; export { default as Toolbar } from './Toolbar'; export { default as Tooltip } from './Tooltip'; -export { default as Grid } from './Grid'; export * from './typing'; export { default as VisSettingPanel } from './VisSettingPanel';