fix: 添加力导向布局

This commit is contained in:
廖威敬 2022-04-06 11:17:34 +08:00
parent 2cdc35ff30
commit 1f9ddfc672
7 changed files with 168 additions and 13 deletions

71
demoV2/Layouter/Force.js Normal file
View File

@ -0,0 +1,71 @@
SV.registerLayout('Force', {
defineOptions() {
return {
node: {
default: {
type: 'force-node',
label: '[data]',
size: 20,
labelOptions: {
style: { fontSize: 20 }
},
style: {
stroke: 'red',
fill: 'red'
}
}
},
link: {
next: {
type: 'line',
sourceAnchor: 0,
targetAnchor: 0,
style: {
stroke: '#333',
lineAppendWidth: 6,
cursor: 'pointer',
// endArrow: 'default',
startArrow: {
path: G6.Arrow.circle(2, -1),
fill: '#333'
}
}
}
},
marker: {
headExternal: {
type: 'pointer',
anchor: 3,
style: {
fill: '#f08a5d'
}
},
external: {
type: 'pointer',
anchor: 0,
style: {
fill: '#f08a5d'
}
}
},
indexLabel: {
index: { position: 'bottom' },
indexRight: { position: 'right' }
},
behavior: {
dragNode: true
}
};
},
layout(e) {
console.log("here is the layout of Force")
// e.forEach((item, index) => {
// console.log(item.getBound());
// })
}
});

View File

@ -2,7 +2,7 @@ import { Util } from "../Common/util";
import { Style } from "../options"; import { Style } from "../options";
import { BoundingRect } from "../Common/boundingRect"; import { BoundingRect } from "../Common/boundingRect";
import { EdgeConfig, Item, NodeConfig } from "@antv/g6-core"; import { EdgeConfig, Item, NodeConfig } from "@antv/g6-core";
import { Graph } from "_@antv_g6-pc@0.5.0@@antv/g6-pc"; import { Graph } from "@antv/g6";
import merge from 'merge'; import merge from 'merge';

View File

@ -0,0 +1,47 @@
import { Util } from "../Common/util";
export default Util.registerShape('force-node', {
draw(cfg, group) {
// cfg.size = cfg.size;
const size = 15;
const wrapperRect = group.addShape('circle', {
attrs: {
r: size,
stroke: 'rgb(35, 120, 180)',
// cursor: cfg.style.cursor,
fill: 'rgb(31, 119, 180)'
},
name: 'wrapper'
});
if (cfg.label) {
const style = (cfg.labelCfg && cfg.labelCfg.style) || {};
group.addShape('text', {
attrs: {
x: 0, // 居中
y: 0,
textAlign: 'center',
textBaseline: 'middle',
text: cfg.label,
fill: style.fill || '#000',
fontSize: style.fontSize || 10,
cursor: cfg.style.cursor
},
name: 'text',
draggable: true
});
}
return wrapperRect;
},
getAnchorPoints() {
return [
[0.5, 0.5],
[0, 0.5],
[1, 0]
];
}
}, 'rect');

View File

@ -5,6 +5,7 @@ import G6 from '@antv/g6';
import Pointer from "./RegisteredShape/pointer"; import Pointer from "./RegisteredShape/pointer";
import LinkListNode from "./RegisteredShape/linkListNode"; import LinkListNode from "./RegisteredShape/linkListNode";
import BinaryTreeNode from "./RegisteredShape/binaryTreeNode"; import BinaryTreeNode from "./RegisteredShape/binaryTreeNode";
import ForceNode from "./RegisteredShape/force";
import CLenQueuePointer from "./RegisteredShape/clenQueuePointer"; import CLenQueuePointer from "./RegisteredShape/clenQueuePointer";
import TwoCellNode from "./RegisteredShape/twoCellNode"; import TwoCellNode from "./RegisteredShape/twoCellNode";
import ArrayNode from "./RegisteredShape/arrayNode"; import ArrayNode from "./RegisteredShape/arrayNode";
@ -20,7 +21,7 @@ import { SVNode } from "./Model/SVNode";
export interface StructV { export interface StructV {
(DOMContainer: HTMLElement, engineOptions: EngineOptions): Engine; (DOMContainer: HTMLElement, engineOptions: EngineOptions, isForce: boolean): Engine;
Group: typeof Group; Group: typeof Group;
Bound: typeof Bound; Bound: typeof Bound;
Vector: typeof Vector, Vector: typeof Vector,
@ -42,8 +43,8 @@ export interface StructV {
} }
export const SV: StructV = function(DOMContainer: HTMLElement, engineOptions: EngineOptions = { }) { export const SV: StructV = function(DOMContainer: HTMLElement, engineOptions: EngineOptions = { }, isForce: boolean) {
return new Engine(DOMContainer, engineOptions); return new Engine(DOMContainer, engineOptions, isForce);
} }
SV.Group = Group; SV.Group = Group;
@ -61,6 +62,7 @@ SV.registeredShape = [
Cursor, Cursor,
ArrayNode, ArrayNode,
CLenQueuePointer, CLenQueuePointer,
ForceNode
]; ];
SV.registerShape = Util.registerShape; SV.registerShape = Util.registerShape;

View File

@ -20,7 +20,7 @@ export class Renderer {
private g6Instance: Graph; // g6 实例 private g6Instance: Graph; // g6 实例
private shadowG6Instance: Graph; private shadowG6Instance: Graph;
constructor(engine: Engine, DOMContainer: HTMLElement, behaviorsModes: Modes) { constructor(engine: Engine, DOMContainer: HTMLElement, behaviorsModes: Modes, isForce: boolean) {
this.engine = engine; this.engine = engine;
const enable: boolean = this.engine.animationOptions.enable, const enable: boolean = this.engine.animationOptions.enable,
@ -41,6 +41,19 @@ export class Renderer {
container: DOMContainer.cloneNode() as HTMLElement container: DOMContainer.cloneNode() as HTMLElement
}); });
const forceOption = {
type: 'force',
linkDistance: 100, // 边长
preventOverlap: true, // boolean防止节点重叠
// alphaDecay: 0, // 迭代阈值的衰减率。范围 [0, 1]。默认值0.028
workEnabled: true, // 启用以防布局计算时间过长阻塞页面交互
nodeStrength: -1, // 节点作用力,正数标识引力,负数表示斥力
nodeSpacing: (d) => { // 设置了防止重叠后,节点边缘间距的最小值
return 20;
},
center: [DOMContainer.offsetWidth / 2, DOMContainer.offsetHeight / 3],
};
const layout = isForce ? forceOption : null;
// 初始化g6实例 // 初始化g6实例
this.g6Instance = new Graph({ this.g6Instance = new Graph({
container: DOMContainer, container: DOMContainer,
@ -52,10 +65,32 @@ export class Renderer {
duration: duration, duration: duration,
easing: timingFunction easing: timingFunction
}, },
fitView: false,
modes: behaviorsModes, modes: behaviorsModes,
plugins: [tooltip] plugins: [tooltip],
layout,
}); });
/**
*
*/
function refreshDragedNodePosition(e) {
const model = e.item.get('model');
model.fx = e.x;
model.fy = e.y;
}
this.g6Instance.on('node:dragstart', (e) => {
this.g6Instance.layout();
refreshDragedNodePosition(e);
});
this.g6Instance.on('node:drag', (e) => {
refreshDragedNodePosition(e);
});
if (typeof window !== 'undefined') {
window.onresize = () => {
if (!this.g6Instance || this.g6Instance.get('destroyed')) return;
if (!DOMContainer || !DOMContainer.scrollWidth || !DOMContainer.scrollHeight) return;
this.g6Instance.changeSize(DOMContainer.scrollWidth, DOMContainer.scrollHeight);
};
}
} }
/** /**

View File

@ -30,12 +30,12 @@ export class ViewContainer {
public clickSelectNode: SVNode; // 点击选中的节点 public clickSelectNode: SVNode; // 点击选中的节点
constructor(engine: Engine, DOMContainer: HTMLElement) { constructor(engine: Engine, DOMContainer: HTMLElement, isForce: boolean) {
const behaviorsModes: Modes = InitG6Behaviors(engine, this); const behaviorsModes: Modes = InitG6Behaviors(engine, this);
this.engine = engine; this.engine = engine;
this.layoutProvider = new LayoutProvider(engine, this); this.layoutProvider = new LayoutProvider(engine, this);
this.renderer = new Renderer(engine, DOMContainer, behaviorsModes); this.renderer = new Renderer(engine, DOMContainer, behaviorsModes, isForce);
this.reconcile = new Reconcile(engine, this.renderer); this.reconcile = new Reconcile(engine, this.renderer);
this.layoutGroupTable = new Map(); this.layoutGroupTable = new Map();
this.prevModelList = []; this.prevModelList = [];

View File

@ -18,7 +18,7 @@ export class Engine {
public animationOptions: AnimationOptions; public animationOptions: AnimationOptions;
public behaviorOptions: BehaviorOptions; public behaviorOptions: BehaviorOptions;
constructor(DOMContainer: HTMLElement, engineOptions: EngineOptions) { constructor(DOMContainer: HTMLElement, engineOptions: EngineOptions, isForce: boolean) {
this.engineOptions = Object.assign({}, engineOptions); this.engineOptions = Object.assign({}, engineOptions);
this.viewOptions = Object.assign({ this.viewOptions = Object.assign({
@ -43,7 +43,7 @@ export class Engine {
}, engineOptions.behavior); }, engineOptions.behavior);
this.modelConstructor = new ModelConstructor(this); this.modelConstructor = new ModelConstructor(this);
this.viewContainer = new ViewContainer(this, DOMContainer); this.viewContainer = new ViewContainer(this, DOMContainer, isForce);
} }
/** /**
@ -51,13 +51,13 @@ export class Engine {
* @param sources * @param sources
* @param force * @param force
*/ */
public render(source: Sources, force: boolean = false) { public render(source: Sources) {
if (source === undefined || source === null) { if (source === undefined || source === null) {
return; return;
} }
`` ``
let stringSource = JSON.stringify(source); let stringSource = JSON.stringify(source);
if (force === false && this.prevStringSource === stringSource) { if (this.prevStringSource === stringSource) {
return; return;
} }