diff --git a/demoV2/Layouter/Force.js b/demoV2/Layouter/Force.js new file mode 100644 index 0000000..3a5fb4a --- /dev/null +++ b/demoV2/Layouter/Force.js @@ -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()); + // }) + } +}); + diff --git a/src/Model/SVModel.ts b/src/Model/SVModel.ts index cbe9f8e..349d087 100644 --- a/src/Model/SVModel.ts +++ b/src/Model/SVModel.ts @@ -2,7 +2,7 @@ import { Util } from "../Common/util"; import { Style } from "../options"; import { BoundingRect } from "../Common/boundingRect"; 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'; diff --git a/src/RegisteredShape/force.ts b/src/RegisteredShape/force.ts new file mode 100644 index 0000000..9bb0725 --- /dev/null +++ b/src/RegisteredShape/force.ts @@ -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'); \ No newline at end of file diff --git a/src/StructV.ts b/src/StructV.ts index a714352..3b2d320 100644 --- a/src/StructV.ts +++ b/src/StructV.ts @@ -5,6 +5,7 @@ import G6 from '@antv/g6'; import Pointer from "./RegisteredShape/pointer"; import LinkListNode from "./RegisteredShape/linkListNode"; import BinaryTreeNode from "./RegisteredShape/binaryTreeNode"; +import ForceNode from "./RegisteredShape/force"; import CLenQueuePointer from "./RegisteredShape/clenQueuePointer"; import TwoCellNode from "./RegisteredShape/twoCellNode"; import ArrayNode from "./RegisteredShape/arrayNode"; @@ -20,7 +21,7 @@ import { SVNode } from "./Model/SVNode"; export interface StructV { - (DOMContainer: HTMLElement, engineOptions: EngineOptions): Engine; + (DOMContainer: HTMLElement, engineOptions: EngineOptions, isForce: boolean): Engine; Group: typeof Group; Bound: typeof Bound; Vector: typeof Vector, @@ -42,8 +43,8 @@ export interface StructV { } -export const SV: StructV = function(DOMContainer: HTMLElement, engineOptions: EngineOptions = { }) { - return new Engine(DOMContainer, engineOptions); +export const SV: StructV = function(DOMContainer: HTMLElement, engineOptions: EngineOptions = { }, isForce: boolean) { + return new Engine(DOMContainer, engineOptions, isForce); } SV.Group = Group; @@ -61,6 +62,7 @@ SV.registeredShape = [ Cursor, ArrayNode, CLenQueuePointer, + ForceNode ]; SV.registerShape = Util.registerShape; diff --git a/src/View/renderer.ts b/src/View/renderer.ts index a81ebc1..29e89ce 100644 --- a/src/View/renderer.ts +++ b/src/View/renderer.ts @@ -20,7 +20,7 @@ export class Renderer { private g6Instance: Graph; // g6 实例 private shadowG6Instance: Graph; - constructor(engine: Engine, DOMContainer: HTMLElement, behaviorsModes: Modes) { + constructor(engine: Engine, DOMContainer: HTMLElement, behaviorsModes: Modes, isForce: boolean) { this.engine = engine; const enable: boolean = this.engine.animationOptions.enable, @@ -41,6 +41,19 @@ export class Renderer { 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实例 this.g6Instance = new Graph({ container: DOMContainer, @@ -52,10 +65,32 @@ export class Renderer { duration: duration, easing: timingFunction }, - fitView: false, 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); + }; + } } /** diff --git a/src/View/viewContainer.ts b/src/View/viewContainer.ts index 8cbfcd7..04962af 100644 --- a/src/View/viewContainer.ts +++ b/src/View/viewContainer.ts @@ -30,12 +30,12 @@ export class ViewContainer { public clickSelectNode: SVNode; // 点击选中的节点 - constructor(engine: Engine, DOMContainer: HTMLElement) { + constructor(engine: Engine, DOMContainer: HTMLElement, isForce: boolean) { const behaviorsModes: Modes = InitG6Behaviors(engine, this); this.engine = engine; 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.layoutGroupTable = new Map(); this.prevModelList = []; diff --git a/src/engine.ts b/src/engine.ts index 9b637f6..cafaff5 100644 --- a/src/engine.ts +++ b/src/engine.ts @@ -18,7 +18,7 @@ export class Engine { public animationOptions: AnimationOptions; public behaviorOptions: BehaviorOptions; - constructor(DOMContainer: HTMLElement, engineOptions: EngineOptions) { + constructor(DOMContainer: HTMLElement, engineOptions: EngineOptions, isForce: boolean) { this.engineOptions = Object.assign({}, engineOptions); this.viewOptions = Object.assign({ @@ -43,7 +43,7 @@ export class Engine { }, engineOptions.behavior); 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 force */ - public render(source: Sources, force: boolean = false) { + public render(source: Sources) { if (source === undefined || source === null) { return; } `` let stringSource = JSON.stringify(source); - if (force === false && this.prevStringSource === stringSource) { + if (this.prevStringSource === stringSource) { return; }