mirror of
https://gitee.com/antv/g6.git
synced 2024-12-02 19:58:46 +08:00
Merge branch 'master' of https://github.com/antvis/G6 into elint-fix-behavior
This commit is contained in:
commit
98864ecf6c
22
.eslintrc.js
22
.eslintrc.js
@ -1,12 +1,12 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
extends: [require.resolve('@umijs/fabric/dist/eslint')],
|
extends: [require.resolve('@umijs/fabric/dist/eslint')],
|
||||||
globals: {
|
globals: {
|
||||||
$: true,
|
$: true,
|
||||||
_: true,
|
_: true,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'no-bitwise': 0,
|
'no-bitwise': 0,
|
||||||
'import/order': 0,
|
'import/order': 0,
|
||||||
'no-plusplus': 0,
|
'no-plusplus': 0,
|
||||||
},
|
},
|
||||||
};
|
};
|
@ -1,3 +1,3 @@
|
|||||||
// window.g6 = require('./src/index.ts'); // import the source for debugging
|
// 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('./dist/g6.min.js') // import the package for webworker
|
||||||
window.insertCss = require('insert-css');
|
window.insertCss = require('insert-css');
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@antv/g6",
|
"name": "@antv/g6",
|
||||||
"version": "3.3.0-beta.3",
|
"version": "3.3.0-beta.5",
|
||||||
"description": "A Graph Visualization Framework in JavaScript",
|
"description": "A Graph Visualization Framework in JavaScript",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"antv",
|
"antv",
|
||||||
@ -49,7 +49,7 @@
|
|||||||
"site:develop": "GATSBY=true gatsby develop --open",
|
"site:develop": "GATSBY=true gatsby develop --open",
|
||||||
"start": "npm run site:develop",
|
"start": "npm run site:develop",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test-live": "DEBUG_MODE=1 jest --watch ./tests/unit/behavior/brush-select-spec.ts",
|
"test-live": "DEBUG_MODE=1 jest --watch ./tests/unit/layout/web-worker-spec.ts",
|
||||||
"lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx",
|
"lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx",
|
||||||
"watch": "father build -w",
|
"watch": "father build -w",
|
||||||
"cdn": "antv-bin upload -n @antv/g6"
|
"cdn": "antv-bin upload -n @antv/g6"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export default {
|
export default {
|
||||||
version: '3.3.0-beta.3',
|
version: '3.3.0-beta.5',
|
||||||
rootContainerClassName: 'root-container',
|
rootContainerClassName: 'root-container',
|
||||||
nodeContainerClassName: 'node-container',
|
nodeContainerClassName: 'node-container',
|
||||||
edgeContainerClassName: 'edge-container',
|
edgeContainerClassName: 'edge-container',
|
||||||
|
@ -5,8 +5,8 @@ import ShapeBase from '@antv/g-canvas/lib/shape/base';
|
|||||||
import each from '@antv/util/lib/each'
|
import each from '@antv/util/lib/each'
|
||||||
import isNil from '@antv/util/lib/is-nil';
|
import isNil from '@antv/util/lib/is-nil';
|
||||||
import wrapBehavior from '@antv/util/lib/wrap-behavior';
|
import wrapBehavior from '@antv/util/lib/wrap-behavior';
|
||||||
import { IGraph } from '../../interface/graph';
|
import Graph from "../graph"
|
||||||
import { IG6GraphEvent, Matrix } from '../../types';
|
import { IG6GraphEvent, Matrix, Item } from '../../types';
|
||||||
import { cloneEvent, isViewportChanged } from '../../util/base';
|
import { cloneEvent, isViewportChanged } from '../../util/base';
|
||||||
|
|
||||||
type Fun = () => void
|
type Fun = () => void
|
||||||
@ -35,14 +35,14 @@ const EVENTS = [
|
|||||||
'touchend',
|
'touchend',
|
||||||
];
|
];
|
||||||
export default class EventController {
|
export default class EventController {
|
||||||
private graph: IGraph
|
private graph: Graph
|
||||||
private extendEvents: any[]
|
private extendEvents: any[]
|
||||||
private canvasHandler: Fun;
|
private canvasHandler!: Fun;
|
||||||
private dragging: boolean
|
private dragging: boolean
|
||||||
private preItem
|
private preItem: Item | null = null
|
||||||
public destroyed: boolean
|
public destroyed: boolean
|
||||||
|
|
||||||
constructor(graph: IGraph) {
|
constructor(graph: Graph) {
|
||||||
this.graph = graph
|
this.graph = graph
|
||||||
this.extendEvents = []
|
this.extendEvents = []
|
||||||
this.dragging = false
|
this.dragging = false
|
||||||
@ -53,7 +53,7 @@ export default class EventController {
|
|||||||
// 初始化 G6 中的事件
|
// 初始化 G6 中的事件
|
||||||
private initEvents() {
|
private initEvents() {
|
||||||
const self = this
|
const self = this
|
||||||
const graph: IGraph = this.graph;
|
const graph: Graph = this.graph;
|
||||||
const canvas: Canvas = graph.get('canvas');
|
const canvas: Canvas = graph.get('canvas');
|
||||||
const el = canvas.get('el');
|
const el = canvas.get('el');
|
||||||
const extendEvents = this.extendEvents;
|
const extendEvents = this.extendEvents;
|
||||||
@ -245,8 +245,8 @@ export default class EventController {
|
|||||||
|
|
||||||
this.dragging = false
|
this.dragging = false
|
||||||
this.preItem = null
|
this.preItem = null
|
||||||
this.extendEvents.length = 0
|
this.extendEvents.length = 0;
|
||||||
this.canvasHandler = null
|
(this.canvasHandler as Fun | null) = null
|
||||||
this.destroyed = true
|
this.destroyed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import isString from '@antv/util/lib/is-string'
|
|||||||
import upperFirst from '@antv/util/lib/upper-first'
|
import upperFirst from '@antv/util/lib/upper-first'
|
||||||
import Edge from '../../item/edge';
|
import Edge from '../../item/edge';
|
||||||
import Node from '../../item/node';
|
import Node from '../../item/node';
|
||||||
import { EdgeConfig, Item, ITEM_TYPE, ModelConfig, NodeConfig, NodeMapConfig } from '../../types';
|
import { EdgeConfig, Item, ITEM_TYPE, ModelConfig, NodeConfig, NodeMap } from '../../types';
|
||||||
import Graph from '../graph';
|
import Graph from '../graph';
|
||||||
|
|
||||||
import { IEdge, INode } from '../../interface/item';
|
import { IEdge, INode } from '../../interface/item';
|
||||||
@ -36,12 +36,12 @@ export default class ItemController {
|
|||||||
* @returns {(Item)}
|
* @returns {(Item)}
|
||||||
* @memberof ItemController
|
* @memberof ItemController
|
||||||
*/
|
*/
|
||||||
public addItem<T extends Item>(type: ITEM_TYPE, model: ModelConfig): T {
|
public addItem<T extends Item>(type: ITEM_TYPE, model: ModelConfig) {
|
||||||
const graph = this.graph;
|
const graph = this.graph;
|
||||||
const parent: Group = graph.get(type + 'Group') || graph.get('group');
|
const parent: Group = graph.get(type + 'Group') || graph.get('group');
|
||||||
const upperType = upperFirst(type);
|
const upperType = upperFirst(type);
|
||||||
|
|
||||||
let item;
|
let item: Item | null = null;
|
||||||
let styles = graph.get(type + upperFirst(STATE_SUFFIX)) || {};
|
let styles = graph.get(type + upperFirst(STATE_SUFFIX)) || {};
|
||||||
const defaultModel = graph.get(CFG_PREFIX + upperType);
|
const defaultModel = graph.get(CFG_PREFIX + upperType);
|
||||||
|
|
||||||
@ -79,8 +79,8 @@ export default class ItemController {
|
|||||||
graph.emit('beforeadditem', { type, model });
|
graph.emit('beforeadditem', { type, model });
|
||||||
|
|
||||||
if(type === EDGE) {
|
if(type === EDGE) {
|
||||||
let source: string | Item = (model as EdgeConfig).source
|
let source: string | Item | undefined = (model as EdgeConfig).source
|
||||||
let target: string | Item = (model as EdgeConfig).target
|
let target: string | Item | undefined = (model as EdgeConfig).target
|
||||||
|
|
||||||
if (source && isString(source)) {
|
if (source && isString(source)) {
|
||||||
source = graph.findById(source);
|
source = graph.findById(source);
|
||||||
@ -110,11 +110,13 @@ export default class ItemController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
graph.get(type + 's').push(item);
|
if (item) {
|
||||||
graph.get('itemMap')[item.get('id')] = item;
|
graph.get(type + 's').push(item);
|
||||||
graph.autoPaint();
|
graph.get('itemMap')[item.get('id')] = item;
|
||||||
graph.emit('afteradditem', { item, model });
|
graph.autoPaint();
|
||||||
return item;
|
graph.emit('afteradditem', { item, model });
|
||||||
|
return item as T;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -230,7 +232,7 @@ export default class ItemController {
|
|||||||
items.splice(index, 1);
|
items.splice(index, 1);
|
||||||
|
|
||||||
const itemId: string = item.get('id')
|
const itemId: string = item.get('id')
|
||||||
const itemMap: NodeMapConfig = graph.get('itemMap')
|
const itemMap: NodeMap = graph.get('itemMap')
|
||||||
delete itemMap[itemId];
|
delete itemMap[itemId];
|
||||||
|
|
||||||
if (type === NODE) {
|
if (type === NODE) {
|
||||||
@ -276,7 +278,7 @@ export default class ItemController {
|
|||||||
* @param {string[]} states 状态名称集合
|
* @param {string[]} states 状态名称集合
|
||||||
* @memberof ItemController
|
* @memberof ItemController
|
||||||
*/
|
*/
|
||||||
public clearItemStates(item: Item | string, states: string | string[]): void {
|
public clearItemStates(item: Item | string, states?: string | string[]): void {
|
||||||
const graph = this.graph;
|
const graph = this.graph;
|
||||||
|
|
||||||
if (isString(item)) {
|
if (isString(item)) {
|
||||||
@ -354,7 +356,7 @@ export default class ItemController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public destroy() {
|
public destroy() {
|
||||||
this.graph = null;
|
(this.graph as Graph | null) = null;
|
||||||
this.destroyed = true;
|
this.destroyed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,10 @@ import isString from '@antv/util/lib/is-string'
|
|||||||
import Behavior from '../../behavior/behavior'
|
import Behavior from '../../behavior/behavior'
|
||||||
import { IBehavior } from '../../interface/behavior';
|
import { IBehavior } from '../../interface/behavior';
|
||||||
import { IGraph, IMode, IModeType } from '../../interface/graph';
|
import { IGraph, IMode, IModeType } from '../../interface/graph';
|
||||||
|
import Graph from '../graph';
|
||||||
|
|
||||||
export default class ModeController {
|
export default class ModeController {
|
||||||
private graph: IGraph
|
private graph: Graph
|
||||||
public destroyed: boolean
|
public destroyed: boolean
|
||||||
/**
|
/**
|
||||||
* modes = {
|
* modes = {
|
||||||
@ -32,7 +33,7 @@ export default class ModeController {
|
|||||||
*/
|
*/
|
||||||
public mode: string
|
public mode: string
|
||||||
private currentBehaves: IBehavior[]
|
private currentBehaves: IBehavior[]
|
||||||
constructor(graph: IGraph) {
|
constructor(graph: Graph) {
|
||||||
this.graph = graph
|
this.graph = graph
|
||||||
this.destroyed = false
|
this.destroyed = false
|
||||||
this.modes = graph.get('modes') || {
|
this.modes = graph.get('modes') || {
|
||||||
@ -62,7 +63,7 @@ export default class ModeController {
|
|||||||
const behaviors = this.modes[mode];
|
const behaviors = this.modes[mode];
|
||||||
const behaves: IBehavior[] = [];
|
const behaves: IBehavior[] = [];
|
||||||
let behave: IBehavior;
|
let behave: IBehavior;
|
||||||
each(behaviors, behavior => {
|
each(behaviors || [], behavior => {
|
||||||
const BehaviorInstance = Behavior.getBehavior(behavior.type)
|
const BehaviorInstance = Behavior.getBehavior(behavior.type)
|
||||||
if (!BehaviorInstance) {
|
if (!BehaviorInstance) {
|
||||||
return;
|
return;
|
||||||
@ -70,14 +71,14 @@ export default class ModeController {
|
|||||||
|
|
||||||
behave = new BehaviorInstance(behavior);
|
behave = new BehaviorInstance(behavior);
|
||||||
if(behave) {
|
if(behave) {
|
||||||
behave.bind(graph)
|
behave.bind(graph as IGraph)
|
||||||
behaves.push(behave);
|
behaves.push(behave);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.currentBehaves = behaves;
|
this.currentBehaves = behaves;
|
||||||
}
|
}
|
||||||
|
|
||||||
private mergeBehaviors(modeBehaviors: IModeType[], behaviors): IModeType[] {
|
private mergeBehaviors(modeBehaviors: IModeType[], behaviors: IModeType[]): IModeType[] {
|
||||||
each(behaviors, behavior => {
|
each(behaviors, behavior => {
|
||||||
if (modeBehaviors.indexOf(behavior) < 0) {
|
if (modeBehaviors.indexOf(behavior) < 0) {
|
||||||
if (isString(behavior)) {
|
if (isString(behavior)) {
|
||||||
@ -89,7 +90,7 @@ export default class ModeController {
|
|||||||
return modeBehaviors;
|
return modeBehaviors;
|
||||||
}
|
}
|
||||||
|
|
||||||
private filterBehaviors(modeBehaviors: IModeType[], behaviors): IModeType[] {
|
private filterBehaviors(modeBehaviors: IModeType[], behaviors: IModeType[]): IModeType[] {
|
||||||
const result: IModeType[] = [];
|
const result: IModeType[] = [];
|
||||||
modeBehaviors.forEach(behavior => {
|
modeBehaviors.forEach(behavior => {
|
||||||
let type: string = ''
|
let type: string = ''
|
||||||
@ -105,7 +106,7 @@ export default class ModeController {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setMode(mode: string): ModeController {
|
public setMode(mode: string) {
|
||||||
const modes = this.modes;
|
const modes = this.modes;
|
||||||
const graph = this.graph;
|
const graph = this.graph;
|
||||||
const current = mode
|
const current = mode
|
||||||
@ -143,22 +144,24 @@ export default class ModeController {
|
|||||||
*/
|
*/
|
||||||
public manipulateBehaviors(behaviors: IModeType[] | IModeType, modes: string[] | string, isAdd: boolean): ModeController {
|
public manipulateBehaviors(behaviors: IModeType[] | IModeType, modes: string[] | string, isAdd: boolean): ModeController {
|
||||||
const self = this
|
const self = this
|
||||||
let behaves = behaviors
|
let behaves: IModeType[]
|
||||||
if(!isArray(behaviors)) {
|
if(!isArray(behaviors)) {
|
||||||
behaves = [ behaviors ]
|
behaves = [ behaviors ]
|
||||||
|
}else {
|
||||||
|
behaves = behaviors
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isArray(modes)) {
|
if(isArray(modes)) {
|
||||||
each(modes, mode => {
|
each(modes, mode => {
|
||||||
if (!self.modes[mode]) {
|
if (!self.modes[mode]) {
|
||||||
if (isAdd) {
|
if (isAdd) {
|
||||||
self.modes[mode] = [].concat(behaves);
|
self.modes[mode] = behaves
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (isAdd) {
|
if (isAdd) {
|
||||||
self.modes[mode] = this.mergeBehaviors(self.modes[mode], behaves);
|
self.modes[mode] = this.mergeBehaviors(self.modes[mode] || [], behaves);
|
||||||
} else {
|
} else {
|
||||||
self.modes[mode] = this.filterBehaviors(self.modes[mode], behaves);
|
self.modes[mode] = this.filterBehaviors(self.modes[mode] || [], behaves);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -173,14 +176,14 @@ export default class ModeController {
|
|||||||
|
|
||||||
if(!this.modes[currentMode]) {
|
if(!this.modes[currentMode]) {
|
||||||
if (isAdd) {
|
if (isAdd) {
|
||||||
self.modes[currentMode] = [].concat(behaves);
|
self.modes[currentMode] = behaves
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAdd) {
|
if (isAdd) {
|
||||||
self.modes[currentMode] = this.mergeBehaviors(self.modes[currentMode], behaves);
|
self.modes[currentMode] = this.mergeBehaviors(self.modes[currentMode] || [], behaves);
|
||||||
} else {
|
} else {
|
||||||
self.modes[currentMode] = this.filterBehaviors(self.modes[currentMode], behaves);
|
self.modes[currentMode] = this.filterBehaviors(self.modes[currentMode] || [], behaves);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.setMode(this.mode)
|
self.setMode(this.mode)
|
||||||
@ -189,9 +192,9 @@ export default class ModeController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public destroy() {
|
public destroy() {
|
||||||
this.graph = null;
|
(this.graph as Graph | null) = null;
|
||||||
this.modes = null;
|
(this.modes as IMode | null) = null;
|
||||||
this.currentBehaves = null;
|
(this.currentBehaves as IBehavior[] | null) = null;
|
||||||
this.destroyed = true;
|
this.destroyed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,21 +2,25 @@ import each from '@antv/util/lib/each'
|
|||||||
import isString from '@antv/util/lib/is-string'
|
import isString from '@antv/util/lib/is-string'
|
||||||
import { IGraph, IStates } from '../../interface/graph';
|
import { IGraph, IStates } from '../../interface/graph';
|
||||||
import { Item } from '../../types';
|
import { Item } from '../../types';
|
||||||
|
import Graph from '../graph';
|
||||||
|
import { INode } from '../../interface/item';
|
||||||
|
|
||||||
interface ICachedStates {
|
interface ICachedStates {
|
||||||
enabled: IStates;
|
enabled: IStates;
|
||||||
disabled: IStates;
|
disabled: IStates;
|
||||||
}
|
}
|
||||||
|
|
||||||
let timer = null
|
|
||||||
|
let timer: number | null = null
|
||||||
const TIME_OUT = 16;
|
const TIME_OUT = 16;
|
||||||
|
|
||||||
|
|
||||||
export default class StateController {
|
export default class StateController {
|
||||||
private graph: IGraph
|
private graph: Graph
|
||||||
private cachedStates: ICachedStates
|
private cachedStates: ICachedStates
|
||||||
public destroyed: boolean
|
public destroyed: boolean
|
||||||
|
|
||||||
constructor(graph: IGraph) {
|
constructor(graph: Graph) {
|
||||||
this.graph = graph
|
this.graph = graph
|
||||||
/**
|
/**
|
||||||
* this.cachedStates = {
|
* this.cachedStates = {
|
||||||
@ -43,7 +47,7 @@ export default class StateController {
|
|||||||
* @returns
|
* @returns
|
||||||
* @memberof State
|
* @memberof State
|
||||||
*/
|
*/
|
||||||
private checkCache(item: Item, state: string, cache: object) {
|
private checkCache(item: Item, state: string, cache: { [key: string]: any }) {
|
||||||
if (!cache[state]) {
|
if (!cache[state]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -62,11 +66,11 @@ export default class StateController {
|
|||||||
* @param {object} states
|
* @param {object} states
|
||||||
* @memberof State
|
* @memberof State
|
||||||
*/
|
*/
|
||||||
private cacheState(item: Item, state: string, states: object) {
|
private cacheState(item: Item, state: string, states: IStates) {
|
||||||
if (!states[state]) {
|
if (!states[state]) {
|
||||||
states[state] = [];
|
states[state] = [];
|
||||||
}
|
}
|
||||||
states[state].push(item);
|
states[state].push(item as INode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -132,7 +136,7 @@ export default class StateController {
|
|||||||
* @memberof State
|
* @memberof State
|
||||||
*/
|
*/
|
||||||
public updateGraphStates() {
|
public updateGraphStates() {
|
||||||
const states = this.graph.get('states')
|
const states = this.graph.get('states') as IStates
|
||||||
const cachedStates = this.cachedStates;
|
const cachedStates = this.cachedStates;
|
||||||
|
|
||||||
each(cachedStates.disabled, (val, key) => {
|
each(cachedStates.disabled, (val, key) => {
|
||||||
@ -143,22 +147,22 @@ export default class StateController {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
each(cachedStates.enabled, (val, key) => {
|
each(cachedStates.enabled, (val: INode[], key) => {
|
||||||
if (!states[key]) {
|
if (!states[key]) {
|
||||||
states[key] = val;
|
states[key] = val;
|
||||||
} else {
|
} else {
|
||||||
const map = {};
|
const map: { [key: string]: boolean } = {};
|
||||||
states[key].forEach(item => {
|
states[key].forEach(item => {
|
||||||
if (!item.destroyed) {
|
if (!item.destroyed) {
|
||||||
map[item.get('id')] = true;
|
map[item.get('id')] = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
val.forEach(item => {
|
val.forEach((item:Item) => {
|
||||||
if (!item.destroyed) {
|
if (!item.destroyed) {
|
||||||
const id = item.get('id');
|
const id = item.get('id');
|
||||||
if (!map[id]) {
|
if (!map[id]) {
|
||||||
map[id] = true;
|
map[id] = true;
|
||||||
states[key].push(item);
|
states[key].push(item as INode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -173,8 +177,8 @@ export default class StateController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public destroy() {
|
public destroy() {
|
||||||
this.graph = null;
|
(this.graph as Graph | null) = null;
|
||||||
this.cachedStates = null;
|
(this.cachedStates as ICachedStates | null) = null;
|
||||||
if (timer) {
|
if (timer) {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,12 @@ import { IGraph } from "../../interface/graph";
|
|||||||
import { Item, Matrix, Padding } from '../../types';
|
import { Item, Matrix, Padding } from '../../types';
|
||||||
import { formatPadding } from '../../util/base'
|
import { formatPadding } from '../../util/base'
|
||||||
import { applyMatrix, invertMatrix } from '../../util/math';
|
import { applyMatrix, invertMatrix } from '../../util/math';
|
||||||
|
import Graph from '../graph';
|
||||||
|
|
||||||
export default class ViewController {
|
export default class ViewController {
|
||||||
private graph: IGraph = null
|
private graph: Graph
|
||||||
public destroyed: boolean = false
|
public destroyed: boolean = false
|
||||||
constructor(graph: IGraph) {
|
constructor(graph: Graph) {
|
||||||
this.graph = graph
|
this.graph = graph
|
||||||
this.destroyed = false
|
this.destroyed = false
|
||||||
}
|
}
|
||||||
@ -53,7 +54,7 @@ export default class ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getFormatPadding(): number[] {
|
public getFormatPadding(): number[] {
|
||||||
const padding = this.graph.get<Padding>('fitViewPadding')
|
const padding = this.graph.get('fitViewPadding') as Padding
|
||||||
return formatPadding(padding)
|
return formatPadding(padding)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +92,7 @@ export default class ViewController {
|
|||||||
* @param x 视口 x 坐标
|
* @param x 视口 x 坐标
|
||||||
* @param y 视口 y 坐标
|
* @param y 视口 y 坐标
|
||||||
*/
|
*/
|
||||||
public getClientByPoint(x, y): Point {
|
public getClientByPoint(x: number, y: number): Point {
|
||||||
const canvas: Canvas = this.graph.get('canvas');
|
const canvas: Canvas = this.graph.get('canvas');
|
||||||
const canvasPoint = this.getCanvasByPoint(x, y);
|
const canvasPoint = this.getCanvasByPoint(x, y);
|
||||||
const point = canvas.getClientByPoint(canvasPoint.x, canvasPoint.y);
|
const point = canvas.getClientByPoint(canvasPoint.x, canvasPoint.y);
|
||||||
@ -104,7 +105,7 @@ export default class ViewController {
|
|||||||
* @param x 视口 x 坐标
|
* @param x 视口 x 坐标
|
||||||
* @param y 视口 y 坐标
|
* @param y 视口 y 坐标
|
||||||
*/
|
*/
|
||||||
public getCanvasByPoint(x, y): Point {
|
public getCanvasByPoint(x: number, y: number): Point {
|
||||||
const viewportMatrix: Matrix = this.graph.get('group').getMatrix();
|
const viewportMatrix: Matrix = this.graph.get('group').getMatrix();
|
||||||
return applyMatrix({ x, y }, viewportMatrix);
|
return applyMatrix({ x, y }, viewportMatrix);
|
||||||
}
|
}
|
||||||
@ -146,7 +147,7 @@ export default class ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public destroy() {
|
public destroy() {
|
||||||
this.graph = null
|
(this.graph as Graph | null) = null
|
||||||
this.destroyed = false
|
this.destroyed = false
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -10,7 +10,7 @@ import each from '@antv/util/lib/each';
|
|||||||
import isPlainObject from '@antv/util/lib/is-plain-object';
|
import isPlainObject from '@antv/util/lib/is-plain-object';
|
||||||
import isString from '@antv/util/lib/is-string';
|
import isString from '@antv/util/lib/is-string';
|
||||||
import { GraphAnimateConfig, GraphOptions, IGraph, IModeOption, IModeType, IStates } from '../interface/graph';
|
import { GraphAnimateConfig, GraphOptions, IGraph, IModeOption, IModeType, IStates } from '../interface/graph';
|
||||||
import { IEdge, INode } from '../interface/item';
|
import { IEdge, INode, IItemBase } from '../interface/item';
|
||||||
import {
|
import {
|
||||||
EdgeConfig,
|
EdgeConfig,
|
||||||
GraphData,
|
GraphData,
|
||||||
@ -20,7 +20,7 @@ import {
|
|||||||
Matrix,
|
Matrix,
|
||||||
ModelConfig,
|
ModelConfig,
|
||||||
NodeConfig,
|
NodeConfig,
|
||||||
NodeMapConfig,
|
NodeMap,
|
||||||
Padding,
|
Padding,
|
||||||
TreeGraphData,
|
TreeGraphData,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
@ -37,6 +37,7 @@ import {
|
|||||||
StateController,
|
StateController,
|
||||||
ViewController,
|
ViewController,
|
||||||
} from './controller';
|
} from './controller';
|
||||||
|
import PluginBase from "../plugins/base"
|
||||||
|
|
||||||
const NODE = 'node';
|
const NODE = 'node';
|
||||||
const EDGE = 'edge';
|
const EDGE = 'edge';
|
||||||
@ -57,13 +58,13 @@ export interface PrivateGraphOption extends GraphOptions {
|
|||||||
|
|
||||||
groups: GroupConfig[];
|
groups: GroupConfig[];
|
||||||
|
|
||||||
itemMap: NodeMapConfig;
|
itemMap: NodeMap;
|
||||||
|
|
||||||
callback: () => void;
|
callback: () => void;
|
||||||
|
|
||||||
groupBBoxs: IGroupBBox;
|
groupBBoxs: IGroupBBox;
|
||||||
|
|
||||||
groupNodes: NodeMapConfig;
|
groupNodes: NodeMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 格式:
|
* 格式:
|
||||||
@ -77,7 +78,7 @@ export interface PrivateGraphOption extends GraphOptions {
|
|||||||
|
|
||||||
export default class Graph extends EventEmitter implements IGraph {
|
export default class Graph extends EventEmitter implements IGraph {
|
||||||
private animating: boolean;
|
private animating: boolean;
|
||||||
private _cfg: GraphOptions;
|
private _cfg: GraphOptions & { [key: string]: any };
|
||||||
public destroyed: boolean;
|
public destroyed: boolean;
|
||||||
|
|
||||||
constructor(cfg: GraphOptions) {
|
constructor(cfg: GraphOptions) {
|
||||||
@ -114,7 +115,7 @@ export default class Graph extends EventEmitter implements IGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private initCanvas() {
|
private initCanvas() {
|
||||||
let container: string | HTMLElement = this.get('container');
|
let container: string | HTMLElement | null = this.get('container');
|
||||||
if (isString(container)) {
|
if (isString(container)) {
|
||||||
container = document.getElementById(container);
|
container = document.getElementById(container);
|
||||||
this.set('container', container);
|
this.set('container', container);
|
||||||
@ -186,7 +187,7 @@ export default class Graph extends EventEmitter implements IGraph {
|
|||||||
this.set('group', group);
|
this.set('group', group);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getDefaultCfg(): PrivateGraphOption {
|
public getDefaultCfg(): Partial<PrivateGraphOption> {
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
* Container could be dom object or dom id
|
* Container could be dom object or dom id
|
||||||
@ -315,7 +316,7 @@ export default class Graph extends EventEmitter implements IGraph {
|
|||||||
/**
|
/**
|
||||||
* 帧回调函数,用于自定义节点运动路径,为空时线性运动
|
* 帧回调函数,用于自定义节点运动路径,为空时线性运动
|
||||||
*/
|
*/
|
||||||
onFrame: null,
|
onFrame: undefined,
|
||||||
/**
|
/**
|
||||||
* 动画时长(ms)
|
* 动画时长(ms)
|
||||||
*/
|
*/
|
||||||
@ -325,7 +326,7 @@ export default class Graph extends EventEmitter implements IGraph {
|
|||||||
*/
|
*/
|
||||||
easing: 'easeLinear',
|
easing: 'easeLinear',
|
||||||
},
|
},
|
||||||
callback: null,
|
callback: undefined,
|
||||||
/**
|
/**
|
||||||
* group类型
|
* group类型
|
||||||
*/
|
*/
|
||||||
@ -441,18 +442,18 @@ export default class Graph extends EventEmitter implements IGraph {
|
|||||||
* @param {(item: T, index: number) => T} fn 指定规则
|
* @param {(item: T, index: number) => T} fn 指定规则
|
||||||
* @return {T} 元素实例
|
* @return {T} 元素实例
|
||||||
*/
|
*/
|
||||||
public find<T extends Item>(type: ITEM_TYPE, fn: (item: T, index?: number) => boolean): T {
|
public find<T extends Item>(type: ITEM_TYPE, fn: (item: T, index?: number) => boolean): T | undefined {
|
||||||
let result;
|
let result: T | undefined;
|
||||||
const items = this.get(type + 's');
|
const items = this.get(type + 's');
|
||||||
|
|
||||||
each(items, (item, i) => {
|
each(items, (item, i) => {
|
||||||
if (fn(item, i)) {
|
if (fn(item, i)) {
|
||||||
result = item;
|
result = item;
|
||||||
return false;
|
return result
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -462,7 +463,7 @@ export default class Graph extends EventEmitter implements IGraph {
|
|||||||
* @return {array} 元素实例
|
* @return {array} 元素实例
|
||||||
*/
|
*/
|
||||||
public findAll<T extends Item>(type: ITEM_TYPE, fn: (item: T, index?: number) => boolean): T[] {
|
public findAll<T extends Item>(type: ITEM_TYPE, fn: (item: T, index?: number) => boolean): T[] {
|
||||||
const result = [];
|
const result: T[] = [];
|
||||||
|
|
||||||
each(this.get(type + 's'), (item, i) => {
|
each(this.get(type + 's'), (item, i) => {
|
||||||
if (fn(item, i)) {
|
if (fn(item, i)) {
|
||||||
@ -739,7 +740,7 @@ export default class Graph extends EventEmitter implements IGraph {
|
|||||||
* @param {ModelConfig} model 元素数据模型
|
* @param {ModelConfig} model 元素数据模型
|
||||||
* @return {Item} 元素实例
|
* @return {Item} 元素实例
|
||||||
*/
|
*/
|
||||||
public addItem(type: ITEM_TYPE, model: ModelConfig): Item {
|
public addItem(type: ITEM_TYPE, model: ModelConfig) {
|
||||||
if (type === 'group') {
|
if (type === 'group') {
|
||||||
const { groupId, nodes, type: groupType, zIndex, title } = model;
|
const { groupId, nodes, type: groupType, zIndex, title } = model;
|
||||||
let groupTitle = title;
|
let groupTitle = title;
|
||||||
@ -813,23 +814,25 @@ export default class Graph extends EventEmitter implements IGraph {
|
|||||||
throw new Error('data must be defined first');
|
throw new Error('data must be defined first');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { nodes = [], edges = [] } = data;
|
||||||
|
|
||||||
this.clear();
|
this.clear();
|
||||||
|
|
||||||
this.emit('beforerender');
|
this.emit('beforerender');
|
||||||
const autoPaint = this.get('autoPaint');
|
const autoPaint = this.get('autoPaint');
|
||||||
this.setAutoPaint(false);
|
this.setAutoPaint(false);
|
||||||
|
|
||||||
each(data.nodes, (node: NodeConfig) => {
|
each(nodes, (node: NodeConfig) => {
|
||||||
self.add('node', node);
|
self.add('node', node);
|
||||||
});
|
});
|
||||||
|
|
||||||
each(data.edges, (edge: EdgeConfig) => {
|
each(edges, (edge: EdgeConfig) => {
|
||||||
self.add('edge', edge);
|
self.add('edge', edge);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!this.get('groupByTypes')) {
|
if (!this.get('groupByTypes')) {
|
||||||
// 为提升性能,选择数量少的进行操作
|
// 为提升性能,选择数量少的进行操作
|
||||||
if (data.nodes.length < data.edges.length) {
|
if (data.nodes && data.edges && data.nodes.length < data.edges.length) {
|
||||||
const nodes = this.getNodes();
|
const nodes = this.getNodes();
|
||||||
|
|
||||||
// 遍历节点实例,将所有节点提前。
|
// 遍历节点实例,将所有节点提前。
|
||||||
@ -885,10 +888,10 @@ export default class Graph extends EventEmitter implements IGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 比较item
|
// 比较item
|
||||||
private diffItems(type: ITEM_TYPE, items, models: NodeConfig[] | EdgeConfig[]) {
|
private diffItems(type: ITEM_TYPE, items: { nodes: INode[], edges: IEdge[]}, models: NodeConfig[] | EdgeConfig[]) {
|
||||||
const self = this;
|
const self = this;
|
||||||
let item;
|
let item: INode;
|
||||||
const itemMap: NodeMapConfig = this.get('itemMap');
|
const itemMap: NodeMap = this.get('itemMap');
|
||||||
|
|
||||||
each(models, (model) => {
|
each(models, (model) => {
|
||||||
item = itemMap[model.id];
|
item = itemMap[model.id];
|
||||||
@ -906,7 +909,7 @@ export default class Graph extends EventEmitter implements IGraph {
|
|||||||
} else {
|
} else {
|
||||||
item = self.addItem(type, model);
|
item = self.addItem(type, model);
|
||||||
}
|
}
|
||||||
items[type + 's'].push(item);
|
(items as { [key:string]: any[]})[type + 's'].push(item);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -928,19 +931,22 @@ export default class Graph extends EventEmitter implements IGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const autoPaint: boolean = this.get('autoPaint');
|
const autoPaint: boolean = this.get('autoPaint');
|
||||||
const itemMap: NodeMapConfig = this.get('itemMap');
|
const itemMap: NodeMap = this.get('itemMap');
|
||||||
|
|
||||||
const items = {
|
const items: {
|
||||||
|
nodes: INode[],
|
||||||
|
edges: IEdge[]
|
||||||
|
} = {
|
||||||
nodes: [],
|
nodes: [],
|
||||||
edges: [],
|
edges: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setAutoPaint(false);
|
this.setAutoPaint(false);
|
||||||
|
|
||||||
this.diffItems('node', items, (data as GraphData).nodes);
|
this.diffItems('node', items, (data as GraphData).nodes!);
|
||||||
this.diffItems('edge', items, (data as GraphData).edges);
|
this.diffItems('edge', items, (data as GraphData).edges!);
|
||||||
|
|
||||||
each(itemMap, (item: INode, id: number) => {
|
each(itemMap, (item: INode & IEdge, id: number) => {
|
||||||
if (items.nodes.indexOf(item) < 0 && items.edges.indexOf(item) < 0) {
|
if (items.nodes.indexOf(item) < 0 && items.edges.indexOf(item) < 0) {
|
||||||
delete itemMap[id];
|
delete itemMap[id];
|
||||||
self.remove(item);
|
self.remove(item);
|
||||||
@ -969,7 +975,7 @@ export default class Graph extends EventEmitter implements IGraph {
|
|||||||
* @param {string} groupType group类型
|
* @param {string} groupType group类型
|
||||||
*/
|
*/
|
||||||
public renderCustomGroup(data: GraphData, groupType: string) {
|
public renderCustomGroup(data: GraphData, groupType: string) {
|
||||||
const { groups, nodes } = data;
|
const { groups, nodes = [] } = data;
|
||||||
|
|
||||||
// 第一种情况,,不存在groups,则不存在嵌套群组
|
// 第一种情况,,不存在groups,则不存在嵌套群组
|
||||||
let groupIndex = 10;
|
let groupIndex = 10;
|
||||||
@ -977,7 +983,7 @@ export default class Graph extends EventEmitter implements IGraph {
|
|||||||
// 存在单个群组
|
// 存在单个群组
|
||||||
// 获取所有有groupID的node
|
// 获取所有有groupID的node
|
||||||
const nodeInGroup = nodes.filter((node) => node.groupId);
|
const nodeInGroup = nodes.filter((node) => node.groupId);
|
||||||
const groupsArr = [];
|
const groupsArr: GroupConfig[] = [];
|
||||||
// 根据groupID分组
|
// 根据groupID分组
|
||||||
const groupIds = groupBy(nodeInGroup, 'groupId');
|
const groupIds = groupBy(nodeInGroup, 'groupId');
|
||||||
// tslint:disable-next-line:forin
|
// tslint:disable-next-line:forin
|
||||||
@ -1020,10 +1026,10 @@ export default class Graph extends EventEmitter implements IGraph {
|
|||||||
* @return {object} data
|
* @return {object} data
|
||||||
*/
|
*/
|
||||||
public save(): TreeGraphData | GraphData {
|
public save(): TreeGraphData | GraphData {
|
||||||
const nodes = [];
|
const nodes: NodeConfig[] = [];
|
||||||
const edges = [];
|
const edges: EdgeConfig[] = [];
|
||||||
each(this.get('nodes'), (node: INode) => {
|
each(this.get('nodes'), (node: INode) => {
|
||||||
nodes.push(node.getModel());
|
nodes.push(node.getModel() as NodeConfig);
|
||||||
});
|
});
|
||||||
|
|
||||||
each(this.get('edges'), (edge: IEdge) => {
|
each(this.get('edges'), (edge: IEdge) => {
|
||||||
@ -1123,7 +1129,7 @@ export default class Graph extends EventEmitter implements IGraph {
|
|||||||
const canvas: GCanvas = self.get('canvas');
|
const canvas: GCanvas = self.get('canvas');
|
||||||
|
|
||||||
canvas.animate(
|
canvas.animate(
|
||||||
(ratio) => {
|
(ratio: number) => {
|
||||||
each(toNodes, (data) => {
|
each(toNodes, (data) => {
|
||||||
const node: Item = self.findById(data.id);
|
const node: Item = self.findById(data.id);
|
||||||
|
|
||||||
@ -1188,7 +1194,7 @@ export default class Graph extends EventEmitter implements IGraph {
|
|||||||
|
|
||||||
each(nodes, (node: INode) => {
|
each(nodes, (node: INode) => {
|
||||||
model = node.getModel() as NodeConfig;
|
model = node.getModel() as NodeConfig;
|
||||||
node.updatePosition({ x: model.x, y: model.y });
|
node.updatePosition({ x: model.x!, y: model.y! });
|
||||||
});
|
});
|
||||||
|
|
||||||
each(edges, (edge: IEdge) => {
|
each(edges, (edge: IEdge) => {
|
||||||
@ -1280,7 +1286,12 @@ export default class Graph extends EventEmitter implements IGraph {
|
|||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
if (window.Blob && window.URL) {
|
if (window.Blob && window.URL) {
|
||||||
const arr = dataURL.split(',');
|
const arr = dataURL.split(',');
|
||||||
const mime = arr[0].match(/:(.*?);/)[1];
|
let mime = ""
|
||||||
|
if (arr && arr.length > 0 ) {
|
||||||
|
const match = arr[0].match(/:(.*?);/);
|
||||||
|
if (match && match.length >= 2) mime = match[1]
|
||||||
|
}
|
||||||
|
|
||||||
const bstr = atob(arr[1]);
|
const bstr = atob(arr[1]);
|
||||||
let n = bstr.length;
|
let n = bstr.length;
|
||||||
const u8arr = new Uint8Array(n);
|
const u8arr = new Uint8Array(n);
|
||||||
@ -1315,7 +1326,7 @@ export default class Graph extends EventEmitter implements IGraph {
|
|||||||
* 若 cfg 含有 type 字段或为 String 类型,且与现有布局方法不同,则更换布局
|
* 若 cfg 含有 type 字段或为 String 类型,且与现有布局方法不同,则更换布局
|
||||||
* 若 cfg 不包括 type ,则保持原有布局方法,仅更新布局配置项
|
* 若 cfg 不包括 type ,则保持原有布局方法,仅更新布局配置项
|
||||||
*/
|
*/
|
||||||
public updateLayout(cfg): void {
|
public updateLayout(cfg: any): void {
|
||||||
const layoutController = this.get('layoutController');
|
const layoutController = this.get('layoutController');
|
||||||
let newLayoutType;
|
let newLayoutType;
|
||||||
|
|
||||||
@ -1388,7 +1399,7 @@ export default class Graph extends EventEmitter implements IGraph {
|
|||||||
* 添加插件
|
* 添加插件
|
||||||
* @param {object} plugin 插件实例
|
* @param {object} plugin 插件实例
|
||||||
*/
|
*/
|
||||||
public addPlugin(plugin): void {
|
public addPlugin(plugin: PluginBase): void {
|
||||||
const self = this;
|
const self = this;
|
||||||
if (plugin.destroyed) {
|
if (plugin.destroyed) {
|
||||||
return;
|
return;
|
||||||
@ -1401,7 +1412,7 @@ export default class Graph extends EventEmitter implements IGraph {
|
|||||||
* 添加插件
|
* 添加插件
|
||||||
* @param {object} plugin 插件实例
|
* @param {object} plugin 插件实例
|
||||||
*/
|
*/
|
||||||
public removePlugin(plugin): void {
|
public removePlugin(plugin: PluginBase): void {
|
||||||
const plugins = this.get('plugins');
|
const plugins = this.get('plugins');
|
||||||
const index = plugins.indexOf(plugin);
|
const index = plugins.indexOf(plugin);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
@ -1428,7 +1439,7 @@ export default class Graph extends EventEmitter implements IGraph {
|
|||||||
this.get('layoutController').destroy();
|
this.get('layoutController').destroy();
|
||||||
this.get('customGroupControll').destroy();
|
this.get('customGroupControll').destroy();
|
||||||
this.get('canvas').destroy();
|
this.get('canvas').destroy();
|
||||||
this._cfg = null;
|
(this._cfg as any) = null;
|
||||||
this.destroyed = true;
|
this.destroyed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,13 +38,13 @@ export default class TreeGraph extends Graph implements ITreeGraph {
|
|||||||
layout.direction = 'TB';
|
layout.direction = 'TB';
|
||||||
}
|
}
|
||||||
if (layout.radial) {
|
if (layout.radial) {
|
||||||
return function(data) {
|
return (data: any) => {
|
||||||
const layoutData = Hierarchy[layout.type](data, layout);
|
const layoutData = Hierarchy[layout.type](data, layout);
|
||||||
radialLayout(layoutData);
|
radialLayout(layoutData);
|
||||||
return layoutData;
|
return layoutData;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return function(data) {
|
return (data: any) => {
|
||||||
return Hierarchy[layout.type](data, layout);
|
return Hierarchy[layout.type](data, layout);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -65,7 +65,7 @@ export default class TreeGraph extends Graph implements ITreeGraph {
|
|||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getDefaultCfg(): PrivateGraphOption {
|
public getDefaultCfg(): Partial<PrivateGraphOption> {
|
||||||
const cfg = super.getDefaultCfg();
|
const cfg = super.getDefaultCfg();
|
||||||
// 树图默认打开动画
|
// 树图默认打开动画
|
||||||
cfg.animate = true;
|
cfg.animate = true;
|
||||||
@ -78,14 +78,18 @@ export default class TreeGraph extends Graph implements ITreeGraph {
|
|||||||
* @param parent 父节点实例
|
* @param parent 父节点实例
|
||||||
* @param animate 是否开启动画
|
* @param animate 是否开启动画
|
||||||
*/
|
*/
|
||||||
private innerAddChild(treeData: TreeGraphData, parent: Item, animate: boolean): Item {
|
private innerAddChild(treeData: TreeGraphData, parent: Item | undefined, animate: boolean): Item {
|
||||||
const self = this;
|
const self = this;
|
||||||
const model = treeData.data;
|
const model = treeData.data;
|
||||||
// model 中应存储真实的数据,特别是真实的 children
|
|
||||||
model.x = treeData.x;
|
if (model) {
|
||||||
model.y = treeData.y;
|
// model 中应存储真实的数据,特别是真实的 children
|
||||||
model.depth = treeData.depth;
|
model.x = treeData.x;
|
||||||
const node = self.addItem('node', model);
|
model.y = treeData.y;
|
||||||
|
model.depth = treeData.depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
const node = self.addItem('node', model!);
|
||||||
if (parent) {
|
if (parent) {
|
||||||
node.set('parent', parent);
|
node.set('parent', parent);
|
||||||
if (animate) {
|
if (animate) {
|
||||||
@ -113,7 +117,7 @@ export default class TreeGraph extends Graph implements ITreeGraph {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 渲染到视图上应参考布局的children, 避免多绘制了收起的节点
|
// 渲染到视图上应参考布局的children, 避免多绘制了收起的节点
|
||||||
each(treeData.children, child => {
|
each(treeData.children || [], child => {
|
||||||
self.innerAddChild(child, node, animate);
|
self.innerAddChild(child, node, animate);
|
||||||
});
|
});
|
||||||
return node;
|
return node;
|
||||||
@ -125,7 +129,7 @@ export default class TreeGraph extends Graph implements ITreeGraph {
|
|||||||
* @param parent
|
* @param parent
|
||||||
* @param animate
|
* @param animate
|
||||||
*/
|
*/
|
||||||
private innerUpdateChild(data: TreeGraphData, parent: Item, animate: boolean) {
|
private innerUpdateChild(data: TreeGraphData, parent: Item | undefined, animate: boolean) {
|
||||||
const self = this;
|
const self = this;
|
||||||
const current = self.findById(data.id);
|
const current = self.findById(data.id);
|
||||||
|
|
||||||
@ -136,7 +140,7 @@ export default class TreeGraph extends Graph implements ITreeGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 更新新节点下所有子节点
|
// 更新新节点下所有子节点
|
||||||
each(data.children, (child: TreeGraphData) => {
|
each(data.children || [], (child: TreeGraphData) => {
|
||||||
self.innerUpdateChild(child, current, animate);
|
self.innerUpdateChild(child, current, animate);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -148,10 +152,10 @@ export default class TreeGraph extends Graph implements ITreeGraph {
|
|||||||
for (let i = children.length - 1; i >= 0; i--) {
|
for (let i = children.length - 1; i >= 0; i--) {
|
||||||
const child = children[i].getModel();
|
const child = children[i].getModel();
|
||||||
|
|
||||||
if (self.indexOfChild(data.children, child.id) === -1) {
|
if (self.indexOfChild(data.children || [], child.id) === -1) {
|
||||||
self.innerRemoveChild(child.id, {
|
self.innerRemoveChild(child.id, {
|
||||||
x: data.x,
|
x: data.x!,
|
||||||
y: data.y
|
y: data.y!
|
||||||
}, animate);
|
}, animate);
|
||||||
|
|
||||||
// 更新父节点下缓存的子节点 item 实例列表
|
// 更新父节点下缓存的子节点 item 实例列表
|
||||||
@ -169,7 +173,7 @@ export default class TreeGraph extends Graph implements ITreeGraph {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
current.set('model', data.data);
|
current.set('model', data.data);
|
||||||
current.updatePosition({ x: data.x, y: data.y });
|
current.updatePosition({ x: data.x!, y: data.y! });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -218,7 +222,7 @@ export default class TreeGraph extends Graph implements ITreeGraph {
|
|||||||
* 更改并应用树布局算法
|
* 更改并应用树布局算法
|
||||||
* @param {object} layout 布局算法
|
* @param {object} layout 布局算法
|
||||||
*/
|
*/
|
||||||
public updateLayout(layout) {
|
public updateLayout(layout: any) {
|
||||||
const self = this;
|
const self = this;
|
||||||
if (!layout) {
|
if (!layout) {
|
||||||
console.warn('layout cannot be null');
|
console.warn('layout cannot be null');
|
||||||
@ -246,7 +250,7 @@ export default class TreeGraph extends Graph implements ITreeGraph {
|
|||||||
|
|
||||||
self.setAutoPaint(false);
|
self.setAutoPaint(false);
|
||||||
|
|
||||||
self.innerUpdateChild(layoutData, null, animate);
|
self.innerUpdateChild(layoutData, undefined, animate);
|
||||||
|
|
||||||
if (fitView) {
|
if (fitView) {
|
||||||
const viewController: ViewController = self.get('viewController')
|
const viewController: ViewController = self.get('viewController')
|
||||||
@ -258,7 +262,7 @@ export default class TreeGraph extends Graph implements ITreeGraph {
|
|||||||
self.refresh();
|
self.refresh();
|
||||||
self.paint();
|
self.paint();
|
||||||
} else {
|
} else {
|
||||||
self.layoutAnimate(layoutData, null);
|
self.layoutAnimate(layoutData);
|
||||||
}
|
}
|
||||||
self.setAutoPaint(autoPaint);
|
self.setAutoPaint(autoPaint);
|
||||||
self.emit('afterrefreshlayout', { data, layoutData });
|
self.emit('afterrefreshlayout', { data, layoutData });
|
||||||
@ -276,13 +280,15 @@ export default class TreeGraph extends Graph implements ITreeGraph {
|
|||||||
parent = parent.get('id') as string;
|
parent = parent.get('id') as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parentData = self.findDataById(parent);
|
const parentData = self.findDataById(parent)
|
||||||
|
|
||||||
if (!parentData.children) {
|
if (parentData) {
|
||||||
parentData.children = [];
|
if (!parentData.children) {
|
||||||
|
parentData.children = [];
|
||||||
|
}
|
||||||
|
parentData.children.push(data);
|
||||||
|
self.changeData();
|
||||||
}
|
}
|
||||||
parentData.children.push(data);
|
|
||||||
self.changeData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -302,12 +308,14 @@ export default class TreeGraph extends Graph implements ITreeGraph {
|
|||||||
const parentModel = self.findById(parent).getModel();
|
const parentModel = self.findById(parent).getModel();
|
||||||
|
|
||||||
const current = self.findById(data.id);
|
const current = self.findById(data.id);
|
||||||
|
|
||||||
|
if (!parentModel.children) {
|
||||||
|
// 当 current 不存在时,children 为空数组
|
||||||
|
parentModel.children = [];
|
||||||
|
}
|
||||||
|
|
||||||
// 如果不存在该节点,则添加
|
// 如果不存在该节点,则添加
|
||||||
if (!current) {
|
if (!current) {
|
||||||
if (!parentModel.children) {
|
|
||||||
// 当 current 不存在时,children 为空数组
|
|
||||||
parentModel.children = [];
|
|
||||||
}
|
|
||||||
parentModel.children.push(data);
|
parentModel.children.push(data);
|
||||||
} else {
|
} else {
|
||||||
const index = self.indexOfChild(parentModel.children, data.id);
|
const index = self.indexOfChild(parentModel.children, data.id);
|
||||||
@ -330,7 +338,8 @@ export default class TreeGraph extends Graph implements ITreeGraph {
|
|||||||
|
|
||||||
const parent = node.get('parent');
|
const parent = node.get('parent');
|
||||||
if (parent && !parent.destroyed) {
|
if (parent && !parent.destroyed) {
|
||||||
const siblings = self.findDataById(parent.get('id')).children;
|
const parentNode = self.findDataById(parent.get('id'));
|
||||||
|
const siblings = (parentNode && parentNode.children) || [];
|
||||||
const model: NodeConfig = node.getModel() as NodeConfig
|
const model: NodeConfig = node.getModel() as NodeConfig
|
||||||
|
|
||||||
const index = self.indexOfChild(siblings, model.id);
|
const index = self.indexOfChild(siblings, model.id);
|
||||||
@ -345,19 +354,19 @@ export default class TreeGraph extends Graph implements ITreeGraph {
|
|||||||
* @param {TreeGraphData | undefined} parent 从哪个节点开始寻找,为空时从根节点开始查找
|
* @param {TreeGraphData | undefined} parent 从哪个节点开始寻找,为空时从根节点开始查找
|
||||||
* @return {TreeGraphData} 对应源数据
|
* @return {TreeGraphData} 对应源数据
|
||||||
*/
|
*/
|
||||||
public findDataById(id: string, parent?: TreeGraphData | undefined): TreeGraphData {
|
public findDataById(id: string, parent?: TreeGraphData | undefined): TreeGraphData | null {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
if (!parent) {
|
if (!parent) {
|
||||||
parent = self.get('data');
|
parent = self.get('data') as TreeGraphData;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id === parent.id) {
|
if (id === parent.id) {
|
||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = null;
|
let result: TreeGraphData | null = null;
|
||||||
each(parent.children, child => {
|
each(parent.children || [], child => {
|
||||||
if (child.id === id) {
|
if (child.id === id) {
|
||||||
result = child;
|
result = child;
|
||||||
return false;
|
return false;
|
||||||
@ -389,7 +398,7 @@ export default class TreeGraph extends Graph implements ITreeGraph {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.get('canvas').animate(ratio => {
|
this.get('canvas').animate((ratio: number) => {
|
||||||
traverseTree<TreeGraphData>(data, child => {
|
traverseTree<TreeGraphData>(data, child => {
|
||||||
const node = self.findById(child.id);
|
const node = self.findById(child.id);
|
||||||
|
|
||||||
@ -410,8 +419,8 @@ export default class TreeGraph extends Graph implements ITreeGraph {
|
|||||||
const attrs = onFrame(node, ratio, origin, data);
|
const attrs = onFrame(node, ratio, origin, data);
|
||||||
node.set('model', Object.assign(model, attrs));
|
node.set('model', Object.assign(model, attrs));
|
||||||
} else {
|
} else {
|
||||||
model.x = origin.x + (child.x - origin.x) * ratio;
|
model.x = origin.x + (child.x! - origin.x) * ratio;
|
||||||
model.y = origin.y + (child.y - origin.y) * ratio;
|
model.y = origin.y + (child.y! - origin.y) * ratio;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -22,8 +22,8 @@ export class G6GraphEvent extends GraphEvent implements IG6GraphEvent {
|
|||||||
public canvasY: number
|
public canvasY: number
|
||||||
public wheelDelta: number
|
public wheelDelta: number
|
||||||
public detail: number
|
public detail: number
|
||||||
public target: Item & Canvas;
|
public target!: Item & Canvas;
|
||||||
constructor(type, event) {
|
constructor(type: string, event: IG6GraphEvent) {
|
||||||
super(type, event)
|
super(type, event)
|
||||||
this.item = event.item
|
this.item = event.item
|
||||||
this.canvasX = event.canvasX
|
this.canvasX = event.canvasX
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import EventEmitter from '@antv/event-emitter';
|
import EventEmitter from '@antv/event-emitter';
|
||||||
import { AnimateCfg, Point } from '@antv/g-base/lib/types';
|
import { AnimateCfg, Point } from '@antv/g-base/lib/types';
|
||||||
import Graph from '../graph/graph';
|
import Graph from '../graph/graph';
|
||||||
import { EdgeConfig, GraphData, IG6GraphEvent, Item, ITEM_TYPE, ModelConfig, ModelStyle, NodeConfig, Padding, ShapeStyle, TreeGraphData } from '../types';
|
import { EdgeConfig, GraphData, IG6GraphEvent, Item, ITEM_TYPE, ModelConfig, ModelStyle, NodeConfig, Padding, ShapeStyle, TreeGraphData, LayoutConfig } from '../types';
|
||||||
import { IEdge, INode } from './item';
|
import { IEdge, INode } from './item';
|
||||||
|
import PluginBase from '../plugins/base';
|
||||||
|
|
||||||
export interface IModeOption {
|
export interface IModeOption {
|
||||||
type: string;
|
type: string;
|
||||||
@ -162,7 +163,7 @@ export interface IStates {
|
|||||||
[key: string]: INode[]
|
[key: string]: INode[]
|
||||||
}
|
}
|
||||||
export interface IGraph extends EventEmitter {
|
export interface IGraph extends EventEmitter {
|
||||||
getDefaultCfg(): GraphOptions;
|
getDefaultCfg(): Partial<GraphOptions>;
|
||||||
get<T = any>(key: string): T;
|
get<T = any>(key: string): T;
|
||||||
set<T = any>(key: string | object, value?: T): Graph;
|
set<T = any>(key: string | object, value?: T): Graph;
|
||||||
findById(id: string): Item;
|
findById(id: string): Item;
|
||||||
@ -445,7 +446,7 @@ export interface IGraph extends EventEmitter {
|
|||||||
* @param {(item: T, index: number) => T} fn 指定规则
|
* @param {(item: T, index: number) => T} fn 指定规则
|
||||||
* @return {T} 元素实例
|
* @return {T} 元素实例
|
||||||
*/
|
*/
|
||||||
find<T extends Item>(type: ITEM_TYPE, fn: (item: T, index: number) => boolean): T;
|
find<T extends Item>(type: ITEM_TYPE, fn: (item: T, index?: number) => boolean): T | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查找所有满足规则的元素
|
* 查找所有满足规则的元素
|
||||||
@ -453,7 +454,7 @@ export interface IGraph extends EventEmitter {
|
|||||||
* @param {string} fn 指定规则
|
* @param {string} fn 指定规则
|
||||||
* @return {array} 元素实例
|
* @return {array} 元素实例
|
||||||
*/
|
*/
|
||||||
findAll<T extends Item>(type: ITEM_TYPE, fn: (item: T, index: number) => boolean): T[];
|
findAll<T extends Item>(type: ITEM_TYPE, fn: (item: T, index?: number) => boolean): T[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查找所有处于指定状态的元素
|
* 查找所有处于指定状态的元素
|
||||||
@ -481,7 +482,7 @@ export interface IGraph extends EventEmitter {
|
|||||||
* 若 cfg 含有 type 字段或为 String 类型,且与现有布局方法不同,则更换布局
|
* 若 cfg 含有 type 字段或为 String 类型,且与现有布局方法不同,则更换布局
|
||||||
* 若 cfg 不包括 type ,则保持原有布局方法,仅更新布局配置项
|
* 若 cfg 不包括 type ,则保持原有布局方法,仅更新布局配置项
|
||||||
*/
|
*/
|
||||||
updateLayout(cfg): void;
|
updateLayout(cfg: LayoutConfig): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重新以当前示例中配置的属性进行一次布局
|
* 重新以当前示例中配置的属性进行一次布局
|
||||||
@ -492,13 +493,13 @@ export interface IGraph extends EventEmitter {
|
|||||||
* 添加插件
|
* 添加插件
|
||||||
* @param {object} plugin 插件实例
|
* @param {object} plugin 插件实例
|
||||||
*/
|
*/
|
||||||
addPlugin(plugin): void;
|
addPlugin(plugin: PluginBase): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加插件
|
* 添加插件
|
||||||
* @param {object} plugin 插件实例
|
* @param {object} plugin 插件实例
|
||||||
*/
|
*/
|
||||||
removePlugin(plugin): void;
|
removePlugin(plugin: PluginBase): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 收起分组
|
* 收起分组
|
||||||
@ -545,7 +546,7 @@ export interface ITreeGraph extends IGraph {
|
|||||||
* @param {TreeGraphData | undefined} parent 从哪个节点开始寻找,为空时从根节点开始查找
|
* @param {TreeGraphData | undefined} parent 从哪个节点开始寻找,为空时从根节点开始查找
|
||||||
* @return {TreeGraphData} 对应源数据
|
* @return {TreeGraphData} 对应源数据
|
||||||
*/
|
*/
|
||||||
findDataById(id: string, parent?: TreeGraphData | undefined): TreeGraphData;
|
findDataById(id: string, parent?: TreeGraphData | undefined): TreeGraphData | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 布局动画接口,用于数据更新时做节点位置更新的动画
|
* 布局动画接口,用于数据更新时做节点位置更新的动画
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { IGroup } from '@antv/g-base/lib/interfaces';
|
import { IGroup } from '@antv/g-base/lib/interfaces';
|
||||||
import { Point } from '@antv/g-base/lib/types';
|
import { Point } from '@antv/g-base/lib/types';
|
||||||
import Group from "@antv/g-canvas/lib/group";
|
import Group from "@antv/g-canvas/lib/group";
|
||||||
import { IBBox, IPoint, IShapeBase, Item, ModelConfig, ModelStyle, ShapeStyle } from '../types';
|
import { IBBox, IPoint, IShapeBase, Item, ModelConfig, ModelStyle, ShapeStyle, Indexable } from '../types';
|
||||||
|
|
||||||
|
|
||||||
// item 的配置项
|
// item 的配置项
|
||||||
@ -63,16 +63,16 @@ export type IItemBaseConfig = Partial<{
|
|||||||
target: string | Item;
|
target: string | Item;
|
||||||
|
|
||||||
linkCenter: boolean;
|
linkCenter: boolean;
|
||||||
}>
|
}> & Indexable<any>
|
||||||
|
|
||||||
export interface IItemBase {
|
export interface IItemBase {
|
||||||
_cfg: IItemBaseConfig;
|
_cfg: IItemBaseConfig | null;
|
||||||
|
|
||||||
destroyed: boolean;
|
destroyed: boolean;
|
||||||
|
|
||||||
isItem(): boolean;
|
isItem(): boolean;
|
||||||
|
|
||||||
getKeyShapeStyle(): ShapeStyle;
|
getKeyShapeStyle(): ShapeStyle | void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前元素的所有状态
|
* 获取当前元素的所有状态
|
||||||
@ -101,7 +101,7 @@ export interface IItemBase {
|
|||||||
*/
|
*/
|
||||||
setState(state: string, enable: boolean): void;
|
setState(state: string, enable: boolean): void;
|
||||||
|
|
||||||
clearStates(states: string | string[]): void;
|
clearStates(states?: string | string[]): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 节点的图形容器
|
* 节点的图形容器
|
||||||
@ -247,7 +247,7 @@ export interface INode extends IItemBase {
|
|||||||
* @param {Object} point 节点外面的一个点,用于计算交点、最近的锚点
|
* @param {Object} point 节点外面的一个点,用于计算交点、最近的锚点
|
||||||
* @return {Object} 连接点 {x,y}
|
* @return {Object} 连接点 {x,y}
|
||||||
*/
|
*/
|
||||||
getLinkPoint(point: IPoint): IPoint;
|
getLinkPoint(point: IPoint): IPoint | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加边
|
* 添加边
|
||||||
|
@ -5,11 +5,11 @@ import { EdgeConfig, GraphData, IPointTuple, NodeConfig } from '../types';
|
|||||||
*/
|
*/
|
||||||
export interface ILayout<Cfg = any> {
|
export interface ILayout<Cfg = any> {
|
||||||
/** 节点 */
|
/** 节点 */
|
||||||
nodes: NodeConfig[];
|
nodes: NodeConfig[] | null;
|
||||||
/** 边 */
|
/** 边 */
|
||||||
edges: EdgeConfig[];
|
edges: EdgeConfig[] | null;
|
||||||
/** 布局获得的位置 */
|
/** 布局获得的位置 */
|
||||||
positions: IPointTuple[];
|
positions: IPointTuple[] | null;
|
||||||
/** 是否已销毁 */
|
/** 是否已销毁 */
|
||||||
destroyed: boolean;
|
destroyed: boolean;
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ export type ILabelConfig = Partial<{
|
|||||||
refX: number;
|
refX: number;
|
||||||
refY: number;
|
refY: number;
|
||||||
autoRotate: boolean;
|
autoRotate: boolean;
|
||||||
|
style: LabelStyle;
|
||||||
}>
|
}>
|
||||||
|
|
||||||
export type ShapeOptions = Partial<{
|
export type ShapeOptions = Partial<{
|
||||||
@ -33,21 +34,21 @@ export type ShapeOptions = Partial<{
|
|||||||
drawShape(cfg?: ModelConfig, group?: GGroup): IShape
|
drawShape(cfg?: ModelConfig, group?: GGroup): IShape
|
||||||
drawLabel(cfg: ModelConfig, group: GGroup): IShape
|
drawLabel(cfg: ModelConfig, group: GGroup): IShape
|
||||||
getLabelStyleByPosition(cfg?: ModelConfig, labelCfg?: ILabelConfig, group?: GGroup): LabelStyle
|
getLabelStyleByPosition(cfg?: ModelConfig, labelCfg?: ILabelConfig, group?: GGroup): LabelStyle
|
||||||
getLabelStyle(cfg: ModelConfig, labelCfg, group: GGroup): LabelStyle
|
getLabelStyle(cfg: ModelConfig, labelCfg: ILabelConfig, group: GGroup): LabelStyle
|
||||||
getShapeStyle(cfg: ModelConfig): ShapeStyle
|
getShapeStyle(cfg: ModelConfig): ShapeStyle
|
||||||
getStateStyle(name: string, value: string | boolean, item: Item): ShapeStyle
|
getStateStyle(name: string, value: string | boolean, item: Item): ShapeStyle
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 绘制完成后的操作,便于用户继承现有的节点、边
|
* 绘制完成后的操作,便于用户继承现有的节点、边
|
||||||
*/
|
*/
|
||||||
afterDraw(cfg?: ModelConfig, group?: GGroup, rst?: IShape)
|
afterDraw(cfg?: ModelConfig, group?: GGroup, rst?: IShape): void
|
||||||
|
|
||||||
afterUpdate(cfg?: ModelConfig, item?: Item)
|
afterUpdate(cfg?: ModelConfig, item?: Item): void
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置节点、边状态
|
* 设置节点、边状态
|
||||||
*/
|
*/
|
||||||
setState(name?: string, value?: string | boolean, item?: Item)
|
setState(name?: string, value?: string | boolean, item?: Item): void
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -65,7 +66,7 @@ export type ShapeOptions = Partial<{
|
|||||||
getAnchorPoints(cfg?: ModelConfig): IPoint[]
|
getAnchorPoints(cfg?: ModelConfig): IPoint[]
|
||||||
|
|
||||||
// 如果没定义 update 方法,每次都调用 draw 方法
|
// 如果没定义 update 方法,每次都调用 draw 方法
|
||||||
update(cfg: ModelConfig, item: Item)
|
update(cfg: ModelConfig, item: Item): void
|
||||||
|
|
||||||
// 获取节点的大小,只对节点起效
|
// 获取节点的大小,只对节点起效
|
||||||
getSize: (cfg: ModelConfig) => number | number[]
|
getSize: (cfg: ModelConfig) => number | number[]
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import isNil from '@antv/util/lib/is-nil';
|
import isNil from '@antv/util/lib/is-nil';
|
||||||
import isPlainObject from '@antv/util/lib/is-plain-object'
|
import isPlainObject from '@antv/util/lib/is-plain-object'
|
||||||
import { IEdge, INode } from "../interface/item";
|
import { IEdge, INode } from "../interface/item";
|
||||||
import { EdgeConfig, IPoint, NodeConfig, SourceTarget } from '../types';
|
import { EdgeConfig, IPoint, NodeConfig, SourceTarget, Indexable, ModelConfig } from '../types';
|
||||||
import Item from './item';
|
import Item from './item';
|
||||||
import Node from './node'
|
import Node from './node'
|
||||||
|
|
||||||
const END_MAP = { source: 'start', target: 'end' };
|
const END_MAP: Indexable<string> = { source: 'start', target: 'end' };
|
||||||
const ITEM_NAME_SUFFIX = 'Node'; // 端点的后缀,如 sourceNode, targetNode
|
const ITEM_NAME_SUFFIX = 'Node'; // 端点的后缀,如 sourceNode, targetNode
|
||||||
const POINT_NAME_SUFFIX = 'Point'; // 起点或者结束点的后缀,如 startPoint, endPoint
|
const POINT_NAME_SUFFIX = 'Point'; // 起点或者结束点的后缀,如 startPoint, endPoint
|
||||||
const ANCHOR_NAME_SUFFIX = 'Anchor';
|
const ANCHOR_NAME_SUFFIX = 'Anchor';
|
||||||
@ -22,7 +22,7 @@ export default class Edge extends Item implements IEdge {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private setEnd(name: string, value: INode) {
|
private setEnd(name: SourceTarget, value: INode) {
|
||||||
const pointName = END_MAP[name] + POINT_NAME_SUFFIX;
|
const pointName = END_MAP[name] + POINT_NAME_SUFFIX;
|
||||||
const itemName = name + ITEM_NAME_SUFFIX;
|
const itemName = name + ITEM_NAME_SUFFIX;
|
||||||
const preItem = this.get(itemName);
|
const preItem = this.get(itemName);
|
||||||
@ -86,7 +86,7 @@ export default class Edge extends Item implements IEdge {
|
|||||||
* 获取端点的位置
|
* 获取端点的位置
|
||||||
* @param name
|
* @param name
|
||||||
*/
|
*/
|
||||||
private getEndPoint(name: string): NodeConfig | IPoint {
|
private getEndPoint(name: SourceTarget): NodeConfig | IPoint {
|
||||||
const itemName = name + ITEM_NAME_SUFFIX;
|
const itemName = name + ITEM_NAME_SUFFIX;
|
||||||
const pointName = END_MAP[name] + POINT_NAME_SUFFIX;
|
const pointName = END_MAP[name] + POINT_NAME_SUFFIX;
|
||||||
const item = this.get(itemName);
|
const item = this.get(itemName);
|
||||||
@ -101,7 +101,7 @@ export default class Edge extends Item implements IEdge {
|
|||||||
* 通过端点的中心获取控制点
|
* 通过端点的中心获取控制点
|
||||||
* @param model
|
* @param model
|
||||||
*/
|
*/
|
||||||
private getControlPointsByCenter(model) {
|
private getControlPointsByCenter(model: EdgeConfig) {
|
||||||
const sourcePoint = this.getEndPoint('source');
|
const sourcePoint = this.getEndPoint('source');
|
||||||
const targetPoint = this.getEndPoint('target');
|
const targetPoint = this.getEndPoint('target');
|
||||||
const shapeFactory = this.get('shapeFactory');
|
const shapeFactory = this.get('shapeFactory');
|
||||||
@ -112,7 +112,7 @@ export default class Edge extends Item implements IEdge {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getEndCenter(name: string): IPoint {
|
private getEndCenter(name: SourceTarget): IPoint {
|
||||||
const itemName = name + ITEM_NAME_SUFFIX;
|
const itemName = name + ITEM_NAME_SUFFIX;
|
||||||
const pointName = END_MAP[name] + POINT_NAME_SUFFIX;
|
const pointName = END_MAP[name] + POINT_NAME_SUFFIX;
|
||||||
const item = this.get(itemName);
|
const item = this.get(itemName);
|
||||||
@ -137,7 +137,7 @@ export default class Edge extends Item implements IEdge {
|
|||||||
public getShapeCfg(model: EdgeConfig): EdgeConfig {
|
public getShapeCfg(model: EdgeConfig): EdgeConfig {
|
||||||
const self = this;
|
const self = this;
|
||||||
const linkCenter: boolean = self.get('linkCenter'); // 如果连接到中心,忽视锚点、忽视控制点
|
const linkCenter: boolean = self.get('linkCenter'); // 如果连接到中心,忽视锚点、忽视控制点
|
||||||
const cfg: any = super.getShapeCfg(model);
|
const cfg: ModelConfig = super.getShapeCfg(model);
|
||||||
|
|
||||||
if (linkCenter) {
|
if (linkCenter) {
|
||||||
cfg.startPoint = self.getEndCenter('source');
|
cfg.startPoint = self.getEndCenter('source');
|
||||||
|
@ -6,7 +6,7 @@ import isString from '@antv/util/lib/is-string'
|
|||||||
import uniqueId from '@antv/util/lib/unique-id'
|
import uniqueId from '@antv/util/lib/unique-id'
|
||||||
import { IItemBase, IItemBaseConfig } from "../interface/item";
|
import { IItemBase, IItemBaseConfig } from "../interface/item";
|
||||||
import Shape from '../shape/shape';
|
import Shape from '../shape/shape';
|
||||||
import { IBBox, IPoint, IShapeBase, ModelConfig, ShapeStyle } from '../types';
|
import { IBBox, IPoint, IShapeBase, ModelConfig, ShapeStyle, Indexable } from '../types';
|
||||||
import { getBBox } from '../util/graphic';
|
import { getBBox } from '../util/graphic';
|
||||||
import { translate } from '../util/math';
|
import { translate } from '../util/math';
|
||||||
|
|
||||||
@ -16,13 +16,15 @@ const RESERVED_STYLES = [ 'fillStyle', 'strokeStyle',
|
|||||||
'path', 'points', 'img', 'symbol' ];
|
'path', 'points', 'img', 'symbol' ];
|
||||||
|
|
||||||
export default class ItemBase implements IItemBase {
|
export default class ItemBase implements IItemBase {
|
||||||
public _cfg: IItemBaseConfig = {}
|
public _cfg: IItemBaseConfig & {
|
||||||
|
[key: string]: unknown
|
||||||
|
} = {}
|
||||||
private defaultCfg: IItemBaseConfig = {
|
private defaultCfg: IItemBaseConfig = {
|
||||||
/**
|
/**
|
||||||
* id
|
* id
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
id: null,
|
id: undefined,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 类型
|
* 类型
|
||||||
@ -40,7 +42,7 @@ export default class ItemBase implements IItemBase {
|
|||||||
* g group
|
* g group
|
||||||
* @type {G.Group}
|
* @type {G.Group}
|
||||||
*/
|
*/
|
||||||
group: null,
|
group: undefined,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* is open animate
|
* is open animate
|
||||||
@ -68,7 +70,7 @@ export default class ItemBase implements IItemBase {
|
|||||||
* key shape to calculate item's bbox
|
* key shape to calculate item's bbox
|
||||||
* @type object
|
* @type object
|
||||||
*/
|
*/
|
||||||
keyShape: null,
|
keyShape: undefined,
|
||||||
/**
|
/**
|
||||||
* item's states, such as selected or active
|
* item's states, such as selected or active
|
||||||
* @type Array
|
* @type Array
|
||||||
@ -81,7 +83,7 @@ export default class ItemBase implements IItemBase {
|
|||||||
constructor(cfg: IItemBaseConfig) {
|
constructor(cfg: IItemBaseConfig) {
|
||||||
this._cfg = Object.assign(this.defaultCfg, this.getDefaultCfg(), cfg)
|
this._cfg = Object.assign(this.defaultCfg, this.getDefaultCfg(), cfg)
|
||||||
const group = cfg.group
|
const group = cfg.group
|
||||||
group.set('item', this)
|
if (group) group.set('item', this)
|
||||||
|
|
||||||
let id = this.get('model').id
|
let id = this.get('model').id
|
||||||
|
|
||||||
@ -90,7 +92,7 @@ export default class ItemBase implements IItemBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.set('id', id)
|
this.set('id', id)
|
||||||
group.set('id', id)
|
if (group) group.set('id', id)
|
||||||
|
|
||||||
const stateStyles = this.get('model').stateStyles;
|
const stateStyles = this.get('model').stateStyles;
|
||||||
this.set('stateStyles', stateStyles);
|
this.set('stateStyles', stateStyles);
|
||||||
@ -131,7 +133,7 @@ export default class ItemBase implements IItemBase {
|
|||||||
}
|
}
|
||||||
self.updatePosition(model);
|
self.updatePosition(model);
|
||||||
const cfg = self.getShapeCfg(model); // 可能会附加额外信息
|
const cfg = self.getShapeCfg(model); // 可能会附加额外信息
|
||||||
const shapeType: string = cfg.shape || cfg.type;
|
const shapeType = (cfg.shape as string )|| (cfg.type as string);
|
||||||
|
|
||||||
const keyShape: IShapeBase = shapeFactory.draw(shapeType, cfg, group);
|
const keyShape: IShapeBase = shapeFactory.draw(shapeType, cfg, group);
|
||||||
if (keyShape) {
|
if (keyShape) {
|
||||||
@ -141,7 +143,7 @@ export default class ItemBase implements IItemBase {
|
|||||||
}
|
}
|
||||||
// 防止由于用户外部修改 model 中的 shape 导致 shape 不更新
|
// 防止由于用户外部修改 model 中的 shape 导致 shape 不更新
|
||||||
this.set('currentShape', shapeType);
|
this.set('currentShape', shapeType);
|
||||||
this.resetStates(shapeFactory, shapeType);
|
this.resetStates(shapeFactory, shapeType!);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -149,7 +151,7 @@ export default class ItemBase implements IItemBase {
|
|||||||
* @param shapeFactory
|
* @param shapeFactory
|
||||||
* @param shapeType
|
* @param shapeType
|
||||||
*/
|
*/
|
||||||
private resetStates(shapeFactory, shapeType: string) {
|
private resetStates(shapeFactory: any, shapeType: string) {
|
||||||
const self = this;
|
const self = this;
|
||||||
const states: string[] = self.get('states');
|
const states: string[] = self.get('states');
|
||||||
each(states, state => {
|
each(states, state => {
|
||||||
@ -168,8 +170,8 @@ export default class ItemBase implements IItemBase {
|
|||||||
* @param {String} key 属性名
|
* @param {String} key 属性名
|
||||||
* @return {object | string | number} 属性值
|
* @return {object | string | number} 属性值
|
||||||
*/
|
*/
|
||||||
public get(key: string) {
|
public get<T = any>(key: string): T {
|
||||||
return this._cfg[key]
|
return this._cfg[key] as T
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -178,7 +180,7 @@ export default class ItemBase implements IItemBase {
|
|||||||
* @param {String|Object} key 属性名,也可以是对象
|
* @param {String|Object} key 属性名,也可以是对象
|
||||||
* @param {object | string | number} val 属性值
|
* @param {object | string | number} val 属性值
|
||||||
*/
|
*/
|
||||||
public set(key: string, val): void {
|
public set(key: string | object, val?: unknown): void {
|
||||||
if(isPlainObject(key)) {
|
if(isPlainObject(key)) {
|
||||||
this._cfg = Object.assign({}, this._cfg, key)
|
this._cfg = Object.assign({}, this._cfg, key)
|
||||||
} else {
|
} else {
|
||||||
@ -228,10 +230,10 @@ export default class ItemBase implements IItemBase {
|
|||||||
this.afterDraw()
|
this.afterDraw()
|
||||||
}
|
}
|
||||||
|
|
||||||
public getKeyShapeStyle(): ShapeStyle {
|
public getKeyShapeStyle(): ShapeStyle | void {
|
||||||
const keyShape = this.getKeyShape();
|
const keyShape = this.getKeyShape();
|
||||||
if (keyShape) {
|
if (keyShape) {
|
||||||
const styles: ShapeStyle = {};
|
const styles: ShapeStyle & Indexable<any> = {};
|
||||||
each(keyShape.attr(), (val, key) => {
|
each(keyShape.attr(), (val, key) => {
|
||||||
if (RESERVED_STYLES.indexOf(key) < 0) {
|
if (RESERVED_STYLES.indexOf(key) < 0) {
|
||||||
styles[key] = val;
|
styles[key] = val;
|
||||||
@ -239,6 +241,7 @@ export default class ItemBase implements IItemBase {
|
|||||||
});
|
});
|
||||||
return styles;
|
return styles;
|
||||||
}
|
}
|
||||||
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getShapeCfg(model: ModelConfig): ModelConfig {
|
public getShapeCfg(model: ModelConfig): ModelConfig {
|
||||||
@ -310,7 +313,7 @@ export default class ItemBase implements IItemBase {
|
|||||||
const originStates = self.getStates();
|
const originStates = self.getStates();
|
||||||
const shapeFactory = self.get('shapeFactory');
|
const shapeFactory = self.get('shapeFactory');
|
||||||
const model: ModelConfig = self.get('model')
|
const model: ModelConfig = self.get('model')
|
||||||
const shape: string = model.shape || model.type;
|
const shape = model.shape || model.type;
|
||||||
if (!states) {
|
if (!states) {
|
||||||
self.set('states', []);
|
self.set('states', []);
|
||||||
shapeFactory.setState(shape, originStates[0], false, self);
|
shapeFactory.setState(shape, originStates[0], false, self);
|
||||||
@ -416,7 +419,7 @@ export default class ItemBase implements IItemBase {
|
|||||||
*/
|
*/
|
||||||
public update(cfg: ModelConfig) {
|
public update(cfg: ModelConfig) {
|
||||||
const model: ModelConfig = this.get('model');
|
const model: ModelConfig = this.get('model');
|
||||||
const originPosition: IPoint = { x: model.x, y: model.y };
|
const originPosition: IPoint = { x: model.x!, y: model.y! };
|
||||||
|
|
||||||
// 直接将更新合到原数据模型上,可以保证用户在外部修改源数据然后刷新时的样式符合期待。
|
// 直接将更新合到原数据模型上,可以保证用户在外部修改源数据然后刷新时的样式符合期待。
|
||||||
Object.assign(model, cfg);
|
Object.assign(model, cfg);
|
||||||
@ -477,7 +480,7 @@ export default class ItemBase implements IItemBase {
|
|||||||
}
|
}
|
||||||
group.resetMatrix();
|
group.resetMatrix();
|
||||||
// G 4.0 element 中移除了矩阵相关方法,详见https://www.yuque.com/antv/blog/kxzk9g#4rMMV
|
// G 4.0 element 中移除了矩阵相关方法,详见https://www.yuque.com/antv/blog/kxzk9g#4rMMV
|
||||||
translate(group, { x, y });
|
translate(group, { x: x!, y: y! });
|
||||||
model.x = x;
|
model.x = x;
|
||||||
model.y = y;
|
model.y = y;
|
||||||
this.clearCache(); // 位置更新后需要清除缓存
|
this.clearCache(); // 位置更新后需要清除缓存
|
||||||
@ -566,7 +569,7 @@ export default class ItemBase implements IItemBase {
|
|||||||
group.stopAnimate();
|
group.stopAnimate();
|
||||||
}
|
}
|
||||||
group.remove();
|
group.remove();
|
||||||
this._cfg = null;
|
(this._cfg as IItemBaseConfig | null) = null;
|
||||||
this.destroyed = true;
|
this.destroyed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ export default class Node extends Item implements INode {
|
|||||||
* 根据锚点的索引获取连接点
|
* 根据锚点的索引获取连接点
|
||||||
* @param {Number} index 索引
|
* @param {Number} index 索引
|
||||||
*/
|
*/
|
||||||
public getLinkPointByAnchor(index): IPoint {
|
public getLinkPointByAnchor(index: number): IPoint {
|
||||||
const anchorPoints = this.getAnchorPoints();
|
const anchorPoints = this.getAnchorPoints();
|
||||||
return anchorPoints[index];
|
return anchorPoints[index];
|
||||||
}
|
}
|
||||||
@ -74,25 +74,25 @@ export default class Node extends Item implements INode {
|
|||||||
* 获取连接点
|
* 获取连接点
|
||||||
* @param point
|
* @param point
|
||||||
*/
|
*/
|
||||||
public getLinkPoint(point: IPoint): IPoint {
|
public getLinkPoint(point: IPoint): IPoint | null {
|
||||||
const keyShape: IShapeBase = this.get('keyShape');
|
const keyShape: IShapeBase = this.get('keyShape');
|
||||||
const type: string = keyShape.get('type');
|
const type: string = keyShape.get('type');
|
||||||
const bbox = this.getBBox();
|
const bbox = this.getBBox();
|
||||||
const { centerX, centerY } = bbox;
|
const { centerX, centerY } = bbox;
|
||||||
const anchorPoints = this.getAnchorPoints();
|
const anchorPoints = this.getAnchorPoints();
|
||||||
let intersectPoint: IPoint;
|
let intersectPoint: IPoint | null;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'circle':
|
case 'circle':
|
||||||
intersectPoint = getCircleIntersectByPoint({
|
intersectPoint = getCircleIntersectByPoint({
|
||||||
x: centerX,
|
x: centerX!,
|
||||||
y: centerY,
|
y: centerY!,
|
||||||
r: bbox.width / 2
|
r: bbox.width / 2
|
||||||
}, point);
|
}, point);
|
||||||
break;
|
break;
|
||||||
case 'ellipse':
|
case 'ellipse':
|
||||||
intersectPoint = getEllispeIntersectByPoint({
|
intersectPoint = getEllispeIntersectByPoint({
|
||||||
x: centerX,
|
x: centerX!,
|
||||||
y: centerY,
|
y: centerY!,
|
||||||
rx: bbox.width / 2,
|
rx: bbox.width / 2,
|
||||||
ry: bbox.height / 2
|
ry: bbox.height / 2
|
||||||
}, point);
|
}, point);
|
||||||
@ -109,7 +109,7 @@ export default class Node extends Item implements INode {
|
|||||||
linkPoint = this.getNearestPoint(anchorPoints, linkPoint);
|
linkPoint = this.getNearestPoint(anchorPoints, linkPoint);
|
||||||
}
|
}
|
||||||
if (!linkPoint) { // 如果最终依然没法找到锚点和连接点,直接返回中心点
|
if (!linkPoint) { // 如果最终依然没法找到锚点和连接点,直接返回中心点
|
||||||
linkPoint = { x: centerX, y: centerY };
|
linkPoint = { x: centerX, y: centerY } as IPoint;
|
||||||
}
|
}
|
||||||
return linkPoint;
|
return linkPoint;
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,9 @@
|
|||||||
* @author shiwu.wyy@antfin.com
|
* @author shiwu.wyy@antfin.com
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EdgeConfig, IPointTuple, NodeConfig } from '../types';
|
import { EdgeConfig, IPointTuple, NodeConfig, NodeIdxMap } from '../types';
|
||||||
import { BaseLayout } from './layout';
|
import { BaseLayout } from './layout';
|
||||||
|
import { getDegree } from '../util/math';
|
||||||
|
|
||||||
type Node = NodeConfig & {
|
type Node = NodeConfig & {
|
||||||
degree: number;
|
degree: number;
|
||||||
@ -13,34 +14,34 @@ type Node = NodeConfig & {
|
|||||||
};
|
};
|
||||||
type Edge = EdgeConfig;
|
type Edge = EdgeConfig;
|
||||||
|
|
||||||
function getDegree(n: number, nodeMap: object, edges: Edge[]) {
|
function initHierarchy(nodes: Node[], edges: Edge[], nodeMap: NodeIdxMap, directed: boolean) {
|
||||||
const degrees = [];
|
|
||||||
for (let i = 0; i < n; i++) {
|
|
||||||
degrees[i] = 0;
|
|
||||||
}
|
|
||||||
edges.forEach((e) => {
|
|
||||||
degrees[nodeMap[e.source]] += 1;
|
|
||||||
degrees[nodeMap[e.target]] += 1;
|
|
||||||
});
|
|
||||||
return degrees;
|
|
||||||
}
|
|
||||||
|
|
||||||
function initHierarchy(nodes: Node[], edges: Edge[], nodeMap: object, directed: boolean) {
|
|
||||||
nodes.forEach((_, i: number) => {
|
nodes.forEach((_, i: number) => {
|
||||||
nodes[i].children = [];
|
nodes[i].children = [];
|
||||||
nodes[i].parent = [];
|
nodes[i].parent = [];
|
||||||
});
|
});
|
||||||
if (directed) {
|
if (directed) {
|
||||||
edges.forEach((e) => {
|
edges.forEach((e) => {
|
||||||
const sourceIdx = nodeMap[e.source];
|
let sourceIdx = 0;
|
||||||
const targetIdx = nodeMap[e.target];
|
if (e.source) {
|
||||||
|
sourceIdx = nodeMap[e.source];
|
||||||
|
}
|
||||||
|
let targetIdx = 0;
|
||||||
|
if (e.target) {
|
||||||
|
targetIdx = nodeMap[e.target];
|
||||||
|
}
|
||||||
nodes[sourceIdx].children.push(nodes[targetIdx]);
|
nodes[sourceIdx].children.push(nodes[targetIdx]);
|
||||||
nodes[targetIdx].parent.push(nodes[sourceIdx]);
|
nodes[targetIdx].parent.push(nodes[sourceIdx]);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
edges.forEach((e) => {
|
edges.forEach((e) => {
|
||||||
const sourceIdx = nodeMap[e.source];
|
let sourceIdx = 0;
|
||||||
const targetIdx = nodeMap[e.target];
|
if (e.source) {
|
||||||
|
sourceIdx = nodeMap[e.source];
|
||||||
|
}
|
||||||
|
let targetIdx = 0;
|
||||||
|
if (e.target) {
|
||||||
|
targetIdx = nodeMap[e.target];
|
||||||
|
}
|
||||||
nodes[sourceIdx].children.push(nodes[targetIdx]);
|
nodes[sourceIdx].children.push(nodes[targetIdx]);
|
||||||
nodes[targetIdx].children.push(nodes[sourceIdx]);
|
nodes[targetIdx].children.push(nodes[sourceIdx]);
|
||||||
});
|
});
|
||||||
@ -75,35 +76,35 @@ function compareDegree(a: Node, b: Node) {
|
|||||||
*/
|
*/
|
||||||
export default class CircularLayout extends BaseLayout {
|
export default class CircularLayout extends BaseLayout {
|
||||||
/** 布局中心 */
|
/** 布局中心 */
|
||||||
public center: IPointTuple;
|
public center: IPointTuple = [0, 0];
|
||||||
/** 固定半径,若设置了 radius,则 startRadius 与 endRadius 不起效 */
|
/** 固定半径,若设置了 radius,则 startRadius 与 endRadius 不起效 */
|
||||||
public radius: number;
|
public radius: number | null = null;
|
||||||
/** 起始半径 */
|
/** 起始半径 */
|
||||||
public startRadius: number;
|
public startRadius: number | null = null;
|
||||||
/** 终止半径 */
|
/** 终止半径 */
|
||||||
public endRadius: number;
|
public endRadius: number | null = null;
|
||||||
/** 起始角度 */
|
/** 起始角度 */
|
||||||
public startAngle: number;
|
public startAngle: number = 0;
|
||||||
/** 终止角度 */
|
/** 终止角度 */
|
||||||
public endAngle: number;
|
public endAngle: number = 2 * Math.PI;
|
||||||
/** 是否顺时针 */
|
/** 是否顺时针 */
|
||||||
public clockwise: boolean;
|
public clockwise: boolean = true;
|
||||||
/** 节点在环上分成段数(几个段将均匀分布),在 endRadius - startRadius != 0 时生效 */
|
/** 节点在环上分成段数(几个段将均匀分布),在 endRadius - startRadius != 0 时生效 */
|
||||||
public divisions: number;
|
public divisions: number = 1;
|
||||||
/** 节点在环上排序的依据,可选: 'topology', 'degree', 'null' */
|
/** 节点在环上排序的依据,可选: 'topology', 'degree', 'null' */
|
||||||
public ordering: 'topology' | 'topology-directed' | 'degree' | 'null';
|
public ordering: 'topology' | 'topology-directed' | 'degree' | null = null;
|
||||||
/** how many 2*pi from first to last nodes */
|
/** how many 2*pi from first to last nodes */
|
||||||
public angleRatio: 1;
|
public angleRatio = 1;
|
||||||
|
|
||||||
public nodes: Node[];
|
public nodes: Node[] = [];
|
||||||
public edges: Edge[];
|
public edges: Edge[] = [];
|
||||||
|
|
||||||
private nodeMap: object;
|
private nodeMap: NodeIdxMap = {};
|
||||||
private degrees;
|
private degrees: number[] = [];
|
||||||
private astep;
|
private astep: number | undefined;
|
||||||
|
|
||||||
public width: number;
|
public width: number = 300;
|
||||||
public height: number;
|
public height: number = 300;
|
||||||
|
|
||||||
public getDefaultCfg() {
|
public getDefaultCfg() {
|
||||||
return {
|
return {
|
||||||
@ -144,7 +145,7 @@ export default class CircularLayout extends BaseLayout {
|
|||||||
const endAngle = self.endAngle;
|
const endAngle = self.endAngle;
|
||||||
const angleStep = (endAngle - startAngle) / n;
|
const angleStep = (endAngle - startAngle) / n;
|
||||||
// layout
|
// layout
|
||||||
const nodeMap = {};
|
const nodeMap: NodeIdxMap = {};
|
||||||
nodes.forEach((node, i) => {
|
nodes.forEach((node, i) => {
|
||||||
nodeMap[node.id] = i;
|
nodeMap[node.id] = i;
|
||||||
});
|
});
|
||||||
@ -190,9 +191,12 @@ export default class CircularLayout extends BaseLayout {
|
|||||||
const divN = Math.ceil(n / divisions); // node number in each division
|
const divN = Math.ceil(n / divisions); // node number in each division
|
||||||
for (let i = 0; i < n; ++i) {
|
for (let i = 0; i < n; ++i) {
|
||||||
let r = radius;
|
let r = radius;
|
||||||
if (!r) {
|
if (!r && startRadius !== null && endRadius !== null) {
|
||||||
r = startRadius + (i * (endRadius - startRadius)) / (n - 1);
|
r = startRadius + (i * (endRadius - startRadius)) / (n - 1);
|
||||||
}
|
}
|
||||||
|
if (!r) {
|
||||||
|
r = 10 + (i * 100) / (n - 1);
|
||||||
|
}
|
||||||
let angle = startAngle + (i % divN) * astep + ((2 * Math.PI) / divisions) * Math.floor(i / divN);
|
let angle = startAngle + (i % divN) * astep + ((2 * Math.PI) / divisions) * Math.floor(i / divN);
|
||||||
if (!clockwise) {
|
if (!clockwise) {
|
||||||
angle = endAngle - (i % divN) * astep - ((2 * Math.PI) / divisions) * Math.floor(i / divN);
|
angle = endAngle - (i % divN) * astep - ((2 * Math.PI) / divisions) * Math.floor(i / divN);
|
||||||
@ -213,7 +217,7 @@ export default class CircularLayout extends BaseLayout {
|
|||||||
const nodes = self.nodes;
|
const nodes = self.nodes;
|
||||||
const nodeMap = self.nodeMap;
|
const nodeMap = self.nodeMap;
|
||||||
const orderedNodes = [nodes[0]];
|
const orderedNodes = [nodes[0]];
|
||||||
const pickFlags = [];
|
const pickFlags: boolean[] = [];
|
||||||
const n = nodes.length;
|
const n = nodes.length;
|
||||||
pickFlags[0] = true;
|
pickFlags[0] = true;
|
||||||
initHierarchy(nodes, edges, nodeMap, directed);
|
initHierarchy(nodes, edges, nodeMap, directed);
|
||||||
@ -260,10 +264,10 @@ export default class CircularLayout extends BaseLayout {
|
|||||||
* 根据节点度数大小排序
|
* 根据节点度数大小排序
|
||||||
* @return {array} orderedNodes 排序后的结果
|
* @return {array} orderedNodes 排序后的结果
|
||||||
*/
|
*/
|
||||||
public degreeOrdering() {
|
public degreeOrdering(): Node[] {
|
||||||
const self = this;
|
const self = this;
|
||||||
const nodes = self.nodes;
|
const nodes = self.nodes;
|
||||||
const orderedNodes = [];
|
const orderedNodes: Node[] = [];
|
||||||
const degrees = self.degrees;
|
const degrees = self.degrees;
|
||||||
nodes.forEach((node, i) => {
|
nodes.forEach((node, i) => {
|
||||||
node.degree = degrees[i];
|
node.degree = degrees[i];
|
||||||
|
@ -4,28 +4,21 @@
|
|||||||
* this algorithm refers to <cytoscape.js> - https://github.com/cytoscape/cytoscape.js/
|
* this algorithm refers to <cytoscape.js> - https://github.com/cytoscape/cytoscape.js/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EdgeConfig, IPointTuple, NodeConfig } from '../types';
|
import { EdgeConfig, IPointTuple, NodeConfig, NodeIdxMap } from '../types';
|
||||||
|
|
||||||
import isArray from '@antv/util/lib/is-array';
|
import isArray from '@antv/util/lib/is-array';
|
||||||
import isString from '@antv/util/lib/is-string';
|
import isString from '@antv/util/lib/is-string';
|
||||||
import { BaseLayout } from './layout';
|
import { BaseLayout } from './layout';
|
||||||
|
import { getDegree } from '../util/math';
|
||||||
|
import { isNumber } from '@antv/util';
|
||||||
|
|
||||||
type Node = NodeConfig & {
|
type Node = NodeConfig & {
|
||||||
degree: number;
|
[key: string]: number;
|
||||||
size: number | number[];
|
|
||||||
};
|
};
|
||||||
type Edge = EdgeConfig;
|
type Edge = EdgeConfig;
|
||||||
|
|
||||||
function getDegree(n: number, nodeIdxMap: object, edges: Edge[]) {
|
type NodeMap = {
|
||||||
const degrees = [];
|
[key: string]: Node;
|
||||||
for (let i = 0; i < n; i++) {
|
|
||||||
degrees[i] = 0;
|
|
||||||
}
|
|
||||||
edges.forEach((e) => {
|
|
||||||
degrees[nodeIdxMap[e.source]] += 1;
|
|
||||||
degrees[nodeIdxMap[e.target]] += 1;
|
|
||||||
});
|
|
||||||
return degrees;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,33 +26,33 @@ function getDegree(n: number, nodeIdxMap: object, edges: Edge[]) {
|
|||||||
*/
|
*/
|
||||||
export default class ConcentricLayout extends BaseLayout {
|
export default class ConcentricLayout extends BaseLayout {
|
||||||
/** 布局中心 */
|
/** 布局中心 */
|
||||||
public center: IPointTuple;
|
public center: IPointTuple = [0, 0];
|
||||||
public nodeSize: number;
|
public nodeSize: number | IPointTuple = 30;
|
||||||
/** min spacing between outside of nodes (used for radius adjustment) */
|
/** min spacing between outside of nodes (used for radius adjustment) */
|
||||||
public minNodeSpacing: number;
|
public minNodeSpacing: number = 10;
|
||||||
/** prevents node overlap, may overflow boundingBox if not enough space */
|
/** prevents node overlap, may overflow boundingBox if not enough space */
|
||||||
public preventOverlap: boolean;
|
public preventOverlap: boolean = false;
|
||||||
/** how many radians should be between the first and last node (defaults to full circle) */
|
/** how many radians should be between the first and last node (defaults to full circle) */
|
||||||
public sweep: undefined;
|
public sweep: number | undefined;
|
||||||
/** whether levels have an equal radial distance betwen them, may cause bounding box overflow */
|
/** whether levels have an equal radial distance betwen them, may cause bounding box overflow */
|
||||||
public equidistant: boolean;
|
public equidistant: boolean = false;
|
||||||
/** where nodes start in radians */
|
/** where nodes start in radians */
|
||||||
public startAngle: number;
|
public startAngle: number = (3 / 2) * Math.PI;
|
||||||
/** whether the layout should go clockwise (true) or counterclockwise/anticlockwise (false) */
|
/** whether the layout should go clockwise (true) or counterclockwise/anticlockwise (false) */
|
||||||
public clockwise: boolean;
|
public clockwise: boolean = true;
|
||||||
/** the letiation of concentric values in each level */
|
/** the letiation of concentric values in each level */
|
||||||
public maxLevelDiff: undefined | number;
|
public maxLevelDiff: undefined | number;
|
||||||
/** 根据 sortBy 指定的属性进行排布,数值高的放在中心,如果是 sortBy 则会计算节点度数,度数最高的放在中心 */
|
/** 根据 sortBy 指定的属性进行排布,数值高的放在中心,如果是 sortBy 则会计算节点度数,度数最高的放在中心 */
|
||||||
public sortBy: string;
|
public sortBy: string = 'degree';
|
||||||
|
|
||||||
public nodes: Node[];
|
public nodes: Node[] = [];
|
||||||
public edges: Edge[];
|
public edges: Edge[] = [];
|
||||||
|
|
||||||
public width: number;
|
public width: number = 300;
|
||||||
public height: number;
|
public height: number = 300;
|
||||||
|
|
||||||
private maxValueNode: number;
|
private maxValueNode: Node | undefined;
|
||||||
private counterclockwise: boolean;
|
private counterclockwise: boolean | undefined;
|
||||||
|
|
||||||
public getDefaultCfg() {
|
public getDefaultCfg() {
|
||||||
return {
|
return {
|
||||||
@ -92,19 +85,19 @@ export default class ConcentricLayout extends BaseLayout {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const layoutNodes = [];
|
const layoutNodes: Node[] = [];
|
||||||
let maxNodeSize: number;
|
let maxNodeSize: number;
|
||||||
if (isNaN(self.nodeSize)) {
|
if (isArray(self.nodeSize)) {
|
||||||
maxNodeSize = Math.max(self.nodeSize[0], self.nodeSize[1]);
|
maxNodeSize = Math.max(self.nodeSize[0], self.nodeSize[1]);
|
||||||
} else {
|
} else {
|
||||||
maxNodeSize = self.nodeSize;
|
maxNodeSize = self.nodeSize;
|
||||||
}
|
}
|
||||||
nodes.forEach((node) => {
|
nodes.forEach((node) => {
|
||||||
layoutNodes.push(node);
|
layoutNodes.push(node);
|
||||||
let nodeSize: number;
|
let nodeSize: number = maxNodeSize;
|
||||||
if (isArray(node.size)) {
|
if (isArray(node.size)) {
|
||||||
nodeSize = Math.max(node.size[0], node.size[1]);
|
nodeSize = Math.max(node.size[0], node.size[1]);
|
||||||
} else {
|
} else if (isNumber(node.size)){
|
||||||
nodeSize = node.size;
|
nodeSize = node.size;
|
||||||
}
|
}
|
||||||
maxNodeSize = Math.max(maxNodeSize, nodeSize);
|
maxNodeSize = Math.max(maxNodeSize, nodeSize);
|
||||||
@ -119,8 +112,8 @@ export default class ConcentricLayout extends BaseLayout {
|
|||||||
self.clockwise = self.counterclockwise !== undefined ? !self.counterclockwise : self.clockwise;
|
self.clockwise = self.counterclockwise !== undefined ? !self.counterclockwise : self.clockwise;
|
||||||
|
|
||||||
// layout
|
// layout
|
||||||
const nodeMap = {};
|
const nodeMap: NodeMap = {};
|
||||||
const nodeIdxMap = {};
|
const nodeIdxMap: NodeIdxMap = {};
|
||||||
layoutNodes.forEach((node, i) => {
|
layoutNodes.forEach((node, i) => {
|
||||||
nodeMap[node.id] = node;
|
nodeMap[node.id] = node;
|
||||||
nodeIdxMap[node.id] = i;
|
nodeIdxMap[node.id] = i;
|
||||||
@ -129,7 +122,7 @@ export default class ConcentricLayout extends BaseLayout {
|
|||||||
// get the node degrees
|
// get the node degrees
|
||||||
if (self.sortBy === 'degree' || !isString(self.sortBy) || layoutNodes[0][self.sortBy] === undefined) {
|
if (self.sortBy === 'degree' || !isString(self.sortBy) || layoutNodes[0][self.sortBy] === undefined) {
|
||||||
self.sortBy = 'degree';
|
self.sortBy = 'degree';
|
||||||
if (isNaN(nodes[0].degree)) {
|
if (!isNumber(nodes[0].degree)) {
|
||||||
const values = getDegree(nodes.length, nodeIdxMap, edges);
|
const values = getDegree(nodes.length, nodeIdxMap, edges);
|
||||||
layoutNodes.forEach((node, i) => {
|
layoutNodes.forEach((node, i) => {
|
||||||
node.degree = values[i];
|
node.degree = values[i];
|
||||||
@ -137,13 +130,13 @@ export default class ConcentricLayout extends BaseLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// sort nodes by value
|
// sort nodes by value
|
||||||
layoutNodes.sort((n1, n2) => {
|
layoutNodes.sort((n1: Node, n2: Node) => {
|
||||||
return n2[self.sortBy] - n1[self.sortBy];
|
return n2[self.sortBy] - n1[self.sortBy];
|
||||||
});
|
});
|
||||||
|
|
||||||
self.maxValueNode = layoutNodes[0];
|
self.maxValueNode = layoutNodes[0];
|
||||||
|
|
||||||
self.maxLevelDiff = self.maxLevelDiff || self.maxValueNode[self.sortBy] / 4; // 0.5;
|
self.maxLevelDiff = self.maxLevelDiff || self.maxValueNode[self.sortBy] / 4;
|
||||||
|
|
||||||
// put the values into levels
|
// put the values into levels
|
||||||
const levels: any[] = [[]];
|
const levels: any[] = [[]];
|
||||||
@ -151,7 +144,7 @@ export default class ConcentricLayout extends BaseLayout {
|
|||||||
layoutNodes.forEach((node) => {
|
layoutNodes.forEach((node) => {
|
||||||
if (currentLevel.length > 0) {
|
if (currentLevel.length > 0) {
|
||||||
const diff = Math.abs(currentLevel[0][self.sortBy] - node[self.sortBy]);
|
const diff = Math.abs(currentLevel[0][self.sortBy] - node[self.sortBy]);
|
||||||
if (diff >= self.maxLevelDiff) {
|
if (self.maxLevelDiff && diff >= self.maxLevelDiff) {
|
||||||
currentLevel = [];
|
currentLevel = [];
|
||||||
levels.push(currentLevel);
|
levels.push(currentLevel);
|
||||||
}
|
}
|
||||||
@ -173,7 +166,10 @@ export default class ConcentricLayout extends BaseLayout {
|
|||||||
// find the metrics for each level
|
// find the metrics for each level
|
||||||
let r = 0;
|
let r = 0;
|
||||||
levels.forEach((level) => {
|
levels.forEach((level) => {
|
||||||
const sweep = self.sweep === undefined ? 2 * Math.PI - (2 * Math.PI) / level.length : self.sweep;
|
let sweep = self.sweep;
|
||||||
|
if (sweep === undefined) {
|
||||||
|
sweep = 2 * Math.PI - (2 * Math.PI) / level.length;
|
||||||
|
}
|
||||||
const dTheta = (level.dTheta = sweep / Math.max(1, level.length - 1));
|
const dTheta = (level.dTheta = sweep / Math.max(1, level.length - 1));
|
||||||
|
|
||||||
// calculate the radius
|
// calculate the radius
|
||||||
|
@ -14,21 +14,21 @@ import { isNumber } from '@antv/util';
|
|||||||
*/
|
*/
|
||||||
export default class DagreLayout extends BaseLayout {
|
export default class DagreLayout extends BaseLayout {
|
||||||
/** layout 方向, 可选 TB, BT, LR, RL */
|
/** layout 方向, 可选 TB, BT, LR, RL */
|
||||||
public rankdir: 'TB' | 'BT' | 'LR' | 'RL';
|
public rankdir: 'TB' | 'BT' | 'LR' | 'RL' = 'TB';
|
||||||
/** 节点对齐方式,可选 UL, UR, DL, DR */
|
/** 节点对齐方式,可选 UL, UR, DL, DR */
|
||||||
public align: undefined | 'UL' | 'UR' | 'DL' | 'DR';
|
public align: undefined | 'UL' | 'UR' | 'DL' | 'DR';
|
||||||
/** 节点大小 */
|
/** 节点大小 */
|
||||||
public nodeSize: number | number[];
|
public nodeSize: number | number[] | undefined;
|
||||||
/** 节点水平间距(px) */
|
/** 节点水平间距(px) */
|
||||||
public nodesepFunc: () => number;
|
public nodesepFunc: ((d?: any) => number) | undefined;
|
||||||
/** 每一层节点之间间距 */
|
/** 每一层节点之间间距 */
|
||||||
public ranksepFunc: () => number;
|
public ranksepFunc: ((d?: any) => number) | undefined;
|
||||||
/** 节点水平间距(px) */
|
/** 节点水平间距(px) */
|
||||||
public nodesep: number;
|
public nodesep: number = 50;
|
||||||
/** 每一层节点之间间距 */
|
/** 每一层节点之间间距 */
|
||||||
public ranksep: number;
|
public ranksep: number = 50;
|
||||||
/** 是否保留布局连线的控制点 */
|
/** 是否保留布局连线的控制点 */
|
||||||
public controlPoints: boolean;
|
public controlPoints: boolean = true;
|
||||||
|
|
||||||
public getDefaultCfg() {
|
public getDefaultCfg() {
|
||||||
return {
|
return {
|
||||||
@ -49,12 +49,13 @@ export default class DagreLayout extends BaseLayout {
|
|||||||
public execute() {
|
public execute() {
|
||||||
const self = this;
|
const self = this;
|
||||||
const nodes = self.nodes;
|
const nodes = self.nodes;
|
||||||
const edges = self.edges;
|
if (!nodes) return;
|
||||||
|
const edges = self.edges || [];
|
||||||
const g = new dagre.graphlib.Graph();
|
const g = new dagre.graphlib.Graph();
|
||||||
const nodeSize = self.nodeSize;
|
const nodeSize = self.nodeSize;
|
||||||
let nodeSizeFunc;
|
let nodeSizeFunc: ((d?: any) => number[]);
|
||||||
if (!nodeSize) {
|
if (!nodeSize) {
|
||||||
nodeSizeFunc = (d) => {
|
nodeSizeFunc = (d: any) => {
|
||||||
if (d.size) {
|
if (d.size) {
|
||||||
if (isArray(d.size)) {
|
if (isArray(d.size)) {
|
||||||
return d.size;
|
return d.size;
|
||||||
@ -95,13 +96,13 @@ export default class DagreLayout extends BaseLayout {
|
|||||||
});
|
});
|
||||||
dagre.layout(g);
|
dagre.layout(g);
|
||||||
let coord;
|
let coord;
|
||||||
g.nodes().forEach(node => {
|
g.nodes().forEach((node: any) => {
|
||||||
coord = g.node(node);
|
coord = g.node(node);
|
||||||
const i = nodes.findIndex(it => it.id === node);
|
const i = nodes.findIndex(it => it.id === node);
|
||||||
nodes[i].x = coord.x;
|
nodes[i].x = coord.x;
|
||||||
nodes[i].y = coord.y;
|
nodes[i].y = coord.y;
|
||||||
});
|
});
|
||||||
g.edges().forEach(edge => {
|
g.edges().forEach((edge: any) => {
|
||||||
coord = g.edge(edge);
|
coord = g.edge(edge);
|
||||||
const i = edges.findIndex(it => it.source === edge.v && it.target === edge.w);
|
const i = edges.findIndex(it => it.source === edge.v && it.target === edge.w);
|
||||||
edges[i].startPoint = coord.points[0];
|
edges[i].startPoint = coord.points[0];
|
||||||
@ -113,7 +114,7 @@ export default class DagreLayout extends BaseLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFunc(func: Function, value: number, defaultValue: number ): Function {
|
function getFunc(func: ((d?: any) => number) | undefined, value: number, defaultValue: number ): Function {
|
||||||
let resultFunc;
|
let resultFunc;
|
||||||
if (func) {
|
if (func) {
|
||||||
resultFunc = func;
|
resultFunc = func;
|
||||||
|
@ -18,42 +18,42 @@ import { LAYOUT_MESSAGE } from './worker/layoutConst';
|
|||||||
/**
|
/**
|
||||||
* 经典力导布局 force-directed
|
* 经典力导布局 force-directed
|
||||||
*/
|
*/
|
||||||
export default class ForceLayout extends BaseLayout {
|
export default class ForceLayout<Cfg = any> extends BaseLayout {
|
||||||
/** 向心力作用点 */
|
/** 向心力作用点 */
|
||||||
public center: IPointTuple;
|
public center: IPointTuple = [0, 0];
|
||||||
/** 节点作用力 */
|
/** 节点作用力 */
|
||||||
public nodeStrength: any;
|
public nodeStrength: number | null = null;
|
||||||
/** 边的作用力, 默认为根据节点的入度出度自适应 */
|
/** 边的作用力, 默认为根据节点的入度出度自适应 */
|
||||||
public edgeStrength: any;
|
public edgeStrength: number | null = null;
|
||||||
/** 是否防止节点相互覆盖 */
|
/** 是否防止节点相互覆盖 */
|
||||||
public preventOverlap: boolean;
|
public preventOverlap: boolean = false;
|
||||||
/** 节点大小 / 直径,用于防止重叠时的碰撞检测 */
|
/** 节点大小 / 直径,用于防止重叠时的碰撞检测 */
|
||||||
public nodeSize: number | number[] | ((d?: unknown) => number);
|
public nodeSize: number | number[] | ((d?: unknown) => number) | undefined;
|
||||||
/** 节点间距,防止节点重叠时节点之间的最小距离(两节点边缘最短距离) */
|
/** 节点间距,防止节点重叠时节点之间的最小距离(两节点边缘最短距离) */
|
||||||
public nodeSpacing: number;
|
public nodeSpacing: ((d?: unknown) => number) | undefined;
|
||||||
/** 默认边长度 */
|
/** 默认边长度 */
|
||||||
public linkDistance: number;
|
public linkDistance: number = 50;
|
||||||
/** 自定义 force 方法 */
|
/** 自定义 force 方法 */
|
||||||
public forceSimulation: any;
|
public forceSimulation: any;
|
||||||
/** 迭代阈值的衰减率 [0, 1],0.028 对应最大迭代数为 300 */
|
/** 迭代阈值的衰减率 [0, 1],0.028 对应最大迭代数为 300 */
|
||||||
public alphaDecay: number;
|
public alphaDecay: number = 0.028;
|
||||||
/** 停止迭代的阈值 */
|
/** 停止迭代的阈值 */
|
||||||
public alphaMin: number;
|
public alphaMin: number = 0.001;
|
||||||
/** 当前阈值 */
|
/** 当前阈值 */
|
||||||
public alpha: number;
|
public alpha: number = 0.3;
|
||||||
/** 防止重叠的力强度 */
|
/** 防止重叠的力强度 */
|
||||||
public collideStrength: number;
|
public collideStrength: number = 1;
|
||||||
/** 是否启用web worker。前提是在web worker里执行布局,否则无效 */
|
/** 是否启用web worker。前提是在web worker里执行布局,否则无效 */
|
||||||
public workerEnabled: boolean;
|
public workerEnabled: boolean = false;
|
||||||
|
|
||||||
public tick: () => void;
|
public tick: () => void = () => {};
|
||||||
|
|
||||||
public onLayoutEnd: () => void;
|
public onLayoutEnd: () => void = () => {};
|
||||||
/** 布局完成回调 */
|
/** 布局完成回调 */
|
||||||
public onTick: () => void;
|
public onTick: () => void = () => {};
|
||||||
|
|
||||||
/** 是否正在布局 */
|
/** 是否正在布局 */
|
||||||
private ticking: boolean;
|
private ticking: boolean | undefined = undefined;
|
||||||
|
|
||||||
public getDefaultCfg() {
|
public getDefaultCfg() {
|
||||||
return {
|
return {
|
||||||
@ -83,8 +83,8 @@ export default class ForceLayout extends BaseLayout {
|
|||||||
*/
|
*/
|
||||||
public init(data: GraphData) {
|
public init(data: GraphData) {
|
||||||
const self = this;
|
const self = this;
|
||||||
self.nodes = data.nodes;
|
self.nodes = data.nodes || [];
|
||||||
self.edges = data.edges;
|
self.edges = data.edges || [];
|
||||||
self.ticking = false;
|
self.ticking = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +133,7 @@ export default class ForceLayout extends BaseLayout {
|
|||||||
});
|
});
|
||||||
const edgeForce = d3Force
|
const edgeForce = d3Force
|
||||||
.forceLink()
|
.forceLink()
|
||||||
.id((d) => d.id)
|
.id((d: any) => d.id)
|
||||||
.links(d3Edges);
|
.links(d3Edges);
|
||||||
if (self.edgeStrength) {
|
if (self.edgeStrength) {
|
||||||
edgeForce.strength(self.edgeStrength);
|
edgeForce.strength(self.edgeStrength);
|
||||||
@ -164,7 +164,7 @@ export default class ForceLayout extends BaseLayout {
|
|||||||
for (let currentTick = 1; currentTick <= totalTicks; currentTick++) {
|
for (let currentTick = 1; currentTick <= totalTicks; currentTick++) {
|
||||||
simulation.tick();
|
simulation.tick();
|
||||||
// currentTick starts from 1.
|
// currentTick starts from 1.
|
||||||
postMessage({ type: LAYOUT_MESSAGE.TICK, currentTick, totalTicks, nodes }, undefined);
|
postMessage({ type: LAYOUT_MESSAGE.TICK, currentTick, totalTicks, nodes }, undefined as any);
|
||||||
}
|
}
|
||||||
self.ticking = false;
|
self.ticking = false;
|
||||||
}
|
}
|
||||||
@ -188,7 +188,7 @@ export default class ForceLayout extends BaseLayout {
|
|||||||
* 防止重叠
|
* 防止重叠
|
||||||
* @param {object} simulation 力模拟模型
|
* @param {object} simulation 力模拟模型
|
||||||
*/
|
*/
|
||||||
public overlapProcess(simulation) {
|
public overlapProcess(simulation: any) {
|
||||||
const self = this;
|
const self = this;
|
||||||
const nodeSize = self.nodeSize;
|
const nodeSize = self.nodeSize;
|
||||||
const nodeSpacing = self.nodeSpacing;
|
const nodeSpacing = self.nodeSpacing;
|
||||||
@ -209,7 +209,7 @@ export default class ForceLayout extends BaseLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!nodeSize) {
|
if (!nodeSize) {
|
||||||
nodeSizeFunc = (d) => {
|
nodeSizeFunc = d => {
|
||||||
if (d.size) {
|
if (d.size) {
|
||||||
if (isArray(d.size)) {
|
if (isArray(d.size)) {
|
||||||
const res = d.size[0] > d.size[1] ? d.size[0] : d.size[1];
|
const res = d.size[0] > d.size[1] ? d.size[0] : d.size[1];
|
||||||
@ -230,11 +230,15 @@ export default class ForceLayout extends BaseLayout {
|
|||||||
nodeSizeFunc = (d) => {
|
nodeSizeFunc = (d) => {
|
||||||
return radius + nodeSpacingFunc(d);
|
return radius + nodeSpacingFunc(d);
|
||||||
};
|
};
|
||||||
} else if (!isNaN(nodeSize)) {
|
} else if (isNumber(nodeSize)) {
|
||||||
const radius = nodeSize / 2;
|
const radius = nodeSize / 2;
|
||||||
nodeSizeFunc = (d) => {
|
nodeSizeFunc = (d) => {
|
||||||
return radius + nodeSpacingFunc(d);
|
return radius + nodeSpacingFunc(d);
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
nodeSizeFunc = () => {
|
||||||
|
return 10;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// forceCollide's parameter is a radius
|
// forceCollide's parameter is a radius
|
||||||
@ -245,14 +249,14 @@ export default class ForceLayout extends BaseLayout {
|
|||||||
* 更新布局配置,但不执行布局
|
* 更新布局配置,但不执行布局
|
||||||
* @param {object} cfg 需要更新的配置项
|
* @param {object} cfg 需要更新的配置项
|
||||||
*/
|
*/
|
||||||
public updateCfg(cfg) {
|
public updateCfg(cfg: Partial<Cfg>) {
|
||||||
const self = this;
|
const self = this;
|
||||||
if (self.ticking) {
|
if (self.ticking) {
|
||||||
self.forceSimulation.stop();
|
self.forceSimulation.stop();
|
||||||
self.ticking = false;
|
self.ticking = false;
|
||||||
}
|
}
|
||||||
self.forceSimulation = null;
|
self.forceSimulation = null;
|
||||||
mix(self, cfg);
|
mix(self as any, cfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy() {
|
public destroy() {
|
||||||
@ -268,7 +272,7 @@ export default class ForceLayout extends BaseLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return total ticks of d3-force simulation
|
// Return total ticks of d3-force simulation
|
||||||
function getSimulationTicks(simulation): number {
|
function getSimulationTicks(simulation: any): number {
|
||||||
const alphaMin = simulation.alphaMin();
|
const alphaMin = simulation.alphaMin();
|
||||||
const alphaTarget = simulation.alphaTarget();
|
const alphaTarget = simulation.alphaTarget();
|
||||||
const alpha = simulation.alpha();
|
const alpha = simulation.alpha();
|
||||||
@ -276,7 +280,7 @@ function getSimulationTicks(simulation): number {
|
|||||||
const totalTicks = Math.ceil(totalTicksFloat);
|
const totalTicks = Math.ceil(totalTicksFloat);
|
||||||
return totalTicks;
|
return totalTicks;
|
||||||
}
|
}
|
||||||
declare const WorkerGlobalScope;
|
declare const WorkerGlobalScope: any;
|
||||||
|
|
||||||
// 判断是否运行在web worker里
|
// 判断是否运行在web worker里
|
||||||
function isInWorker(): boolean {
|
function isInWorker(): boolean {
|
||||||
|
@ -3,16 +3,20 @@
|
|||||||
* @author shiwu.wyy@antfin.com
|
* @author shiwu.wyy@antfin.com
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EdgeConfig, IPointTuple, NodeConfig } from '../types';
|
import { EdgeConfig, IPointTuple, NodeConfig, NodeIdxMap } from '../types';
|
||||||
import { BaseLayout } from './layout';
|
import { BaseLayout } from './layout';
|
||||||
|
import { isNumber } from '@antv/util';
|
||||||
|
import { Point } from '@antv/g-base';
|
||||||
|
|
||||||
type Node = NodeConfig & {
|
type Node = NodeConfig & {
|
||||||
cluster: string;
|
cluster: string | number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Edge = EdgeConfig;
|
type Edge = EdgeConfig;
|
||||||
|
|
||||||
type NodeMap = Map<string, Node>;
|
type NodeMap = {
|
||||||
type NodeIndexMap = Map<string, string>;
|
[key: string]: Node;
|
||||||
|
}
|
||||||
|
|
||||||
const SPEED_DIVISOR = 800;
|
const SPEED_DIVISOR = 800;
|
||||||
|
|
||||||
@ -21,26 +25,26 @@ const SPEED_DIVISOR = 800;
|
|||||||
*/
|
*/
|
||||||
export default class FruchtermanLayout extends BaseLayout {
|
export default class FruchtermanLayout extends BaseLayout {
|
||||||
/** 布局中心 */
|
/** 布局中心 */
|
||||||
public center: IPointTuple;
|
public center: IPointTuple = [0, 0];
|
||||||
/** 停止迭代的最大迭代数 */
|
/** 停止迭代的最大迭代数 */
|
||||||
public maxIteration: number;
|
public maxIteration: number = 1000;
|
||||||
/** 重力大小,影响图的紧凑程度 */
|
/** 重力大小,影响图的紧凑程度 */
|
||||||
public gravity: number;
|
public gravity: number = 10;
|
||||||
/** 速度 */
|
/** 速度 */
|
||||||
public speed: number;
|
public speed: number = 1;
|
||||||
/** 是否产生聚类力 */
|
/** 是否产生聚类力 */
|
||||||
public clustering: boolean;
|
public clustering: boolean = false;
|
||||||
/** 聚类力大小 */
|
/** 聚类力大小 */
|
||||||
public clusterGravity: number;
|
public clusterGravity: number = 10;
|
||||||
|
|
||||||
public width: number;
|
public nodes: Node[] = [];
|
||||||
public height: number;
|
public edges: Edge[] = [];
|
||||||
|
|
||||||
public nodes: Node[];
|
public width: number = 300;
|
||||||
public edges: Edge[];
|
public height: number = 300;
|
||||||
|
|
||||||
public nodeMap: object;
|
public nodeMap: NodeMap = {};
|
||||||
public nodeIndexMap: object;
|
public nodeIdxMap: NodeIdxMap = {};
|
||||||
|
|
||||||
public getDefaultCfg() {
|
public getDefaultCfg() {
|
||||||
return {
|
return {
|
||||||
@ -60,21 +64,21 @@ export default class FruchtermanLayout extends BaseLayout {
|
|||||||
const nodes = self.nodes;
|
const nodes = self.nodes;
|
||||||
const center = self.center;
|
const center = self.center;
|
||||||
|
|
||||||
if (nodes.length === 0) {
|
if (!nodes || nodes.length === 0) {
|
||||||
return;
|
return;
|
||||||
} else if (nodes.length === 1) {
|
} else if (nodes.length === 1) {
|
||||||
nodes[0].x = center[0];
|
nodes[0].x = center[0];
|
||||||
nodes[0].y = center[1];
|
nodes[0].y = center[1];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const nodeMap = {};
|
const nodeMap: NodeMap = {};
|
||||||
const nodeIndexMap = {};
|
const nodeIdxMap: NodeIdxMap = {};
|
||||||
nodes.forEach((node, i) => {
|
nodes.forEach((node, i) => {
|
||||||
nodeMap[node.id] = node;
|
nodeMap[node.id] = node;
|
||||||
nodeIndexMap[node.id] = i;
|
nodeIdxMap[node.id] = i;
|
||||||
});
|
});
|
||||||
self.nodeMap = nodeMap;
|
self.nodeMap = nodeMap;
|
||||||
self.nodeIndexMap = nodeIndexMap;
|
self.nodeIdxMap = nodeIdxMap;
|
||||||
// layout
|
// layout
|
||||||
self.run();
|
self.run();
|
||||||
}
|
}
|
||||||
@ -82,6 +86,7 @@ export default class FruchtermanLayout extends BaseLayout {
|
|||||||
public run() {
|
public run() {
|
||||||
const self = this;
|
const self = this;
|
||||||
const nodes = self.nodes;
|
const nodes = self.nodes;
|
||||||
|
if (!nodes) return;
|
||||||
const edges = self.edges;
|
const edges = self.edges;
|
||||||
const maxIteration = self.maxIteration;
|
const maxIteration = self.maxIteration;
|
||||||
if (!self.width && typeof window !== 'undefined') {
|
if (!self.width && typeof window !== 'undefined') {
|
||||||
@ -92,15 +97,20 @@ export default class FruchtermanLayout extends BaseLayout {
|
|||||||
}
|
}
|
||||||
const center = self.center;
|
const center = self.center;
|
||||||
const nodeMap = self.nodeMap;
|
const nodeMap = self.nodeMap;
|
||||||
const nodeIndexMap = self.nodeIndexMap;
|
const nodeIdxMap = self.nodeIdxMap;
|
||||||
const maxDisplace = self.width / 10;
|
const maxDisplace = self.width / 10;
|
||||||
const k = Math.sqrt((self.width * self.height) / (nodes.length + 1));
|
const k = Math.sqrt((self.width * self.height) / (nodes.length + 1));
|
||||||
const gravity = self.gravity;
|
const gravity = self.gravity;
|
||||||
const speed = self.speed;
|
const speed = self.speed;
|
||||||
const clustering = self.clustering;
|
const clustering = self.clustering;
|
||||||
const clusterMap = {}
|
const clusterMap: {[key: string]: {
|
||||||
|
name: string | number,
|
||||||
|
cx: number,
|
||||||
|
cy: number,
|
||||||
|
count: number
|
||||||
|
}} = {}
|
||||||
if (clustering) {
|
if (clustering) {
|
||||||
nodes.forEach((n) => {
|
nodes.forEach(n => {
|
||||||
if (clusterMap[n.cluster] === undefined) {
|
if (clusterMap[n.cluster] === undefined) {
|
||||||
const cluster = {
|
const cluster = {
|
||||||
name: n.cluster,
|
name: n.cluster,
|
||||||
@ -111,8 +121,12 @@ export default class FruchtermanLayout extends BaseLayout {
|
|||||||
clusterMap[n.cluster] = cluster;
|
clusterMap[n.cluster] = cluster;
|
||||||
}
|
}
|
||||||
const c = clusterMap[n.cluster];
|
const c = clusterMap[n.cluster];
|
||||||
c.cx += n.x;
|
if (isNumber(n.x)) {
|
||||||
c.cy += n.y;
|
c.cx += n.x;
|
||||||
|
}
|
||||||
|
if (isNumber(n.y)) {
|
||||||
|
c.cy += n.y;
|
||||||
|
}
|
||||||
c.count++;
|
c.count++;
|
||||||
});
|
});
|
||||||
for (let key in clusterMap) {
|
for (let key in clusterMap) {
|
||||||
@ -121,16 +135,17 @@ export default class FruchtermanLayout extends BaseLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let i = 0; i < maxIteration; i++) {
|
for (let i = 0; i < maxIteration; i++) {
|
||||||
const disp = [];
|
const disp: Point[] = [];
|
||||||
nodes.forEach((_, j) => {
|
nodes.forEach((_, j) => {
|
||||||
disp[j] = { x: 0, y: 0 };
|
disp[j] = { x: 0, y: 0 };
|
||||||
});
|
});
|
||||||
self.getDisp(nodes, edges, nodeMap, nodeIndexMap, disp, k);
|
self.getDisp(nodes, edges, nodeMap, nodeIdxMap, disp, k);
|
||||||
|
|
||||||
// gravity for clusters
|
// gravity for clusters
|
||||||
if (clustering) {
|
if (clustering) {
|
||||||
const clusterGravity = self.clusterGravity || gravity;
|
const clusterGravity = self.clusterGravity || gravity;
|
||||||
nodes.forEach((n, j) => {
|
nodes.forEach((n, j) => {
|
||||||
|
if (!isNumber(n.x) || !isNumber(n.y)) return;
|
||||||
const c = clusterMap[n.cluster];
|
const c = clusterMap[n.cluster];
|
||||||
const distLength = Math.sqrt((n.x - c.cx) * (n.x - c.cx) + (n.y - c.cy) * (n.y - c.cy));
|
const distLength = Math.sqrt((n.x - c.cx) * (n.x - c.cx) + (n.y - c.cy) * (n.y - c.cy));
|
||||||
const gravityForce = k * clusterGravity;
|
const gravityForce = k * clusterGravity;
|
||||||
@ -147,8 +162,12 @@ export default class FruchtermanLayout extends BaseLayout {
|
|||||||
|
|
||||||
nodes.forEach((n) => {
|
nodes.forEach((n) => {
|
||||||
const c = clusterMap[n.cluster];
|
const c = clusterMap[n.cluster];
|
||||||
c.cx += n.x;
|
if (isNumber(n.x)) {
|
||||||
c.cy += n.y;
|
c.cx += n.x;
|
||||||
|
}
|
||||||
|
if (isNumber(n.y)) {
|
||||||
|
c.cy += n.y;
|
||||||
|
}
|
||||||
c.count++;
|
c.count++;
|
||||||
});
|
});
|
||||||
for (let key in clusterMap) {
|
for (let key in clusterMap) {
|
||||||
@ -159,18 +178,15 @@ export default class FruchtermanLayout extends BaseLayout {
|
|||||||
|
|
||||||
// gravity
|
// gravity
|
||||||
nodes.forEach((n, j) => {
|
nodes.forEach((n, j) => {
|
||||||
|
if (!isNumber(n.x) || !isNumber(n.y)) return;
|
||||||
const gravityForce = 0.01 * k * gravity;
|
const gravityForce = 0.01 * k * gravity;
|
||||||
disp[j].x -= gravityForce * (n.x - center[0]);
|
disp[j].x -= gravityForce * (n.x - center[0]);
|
||||||
disp[j].y -= gravityForce * (n.y - center[1]);
|
disp[j].y -= gravityForce * (n.y - center[1]);
|
||||||
});
|
});
|
||||||
// speed
|
|
||||||
nodes.forEach((_, j) => {
|
|
||||||
disp[j].dx *= speed / SPEED_DIVISOR;
|
|
||||||
disp[j].dy *= speed / SPEED_DIVISOR;
|
|
||||||
});
|
|
||||||
|
|
||||||
// move
|
// move
|
||||||
nodes.forEach((n, j) => {
|
nodes.forEach((n, j) => {
|
||||||
|
if (!isNumber(n.x) || !isNumber(n.y)) return;
|
||||||
const distLength = Math.sqrt(disp[j].x * disp[j].x + disp[j].y * disp[j].y);
|
const distLength = Math.sqrt(disp[j].x * disp[j].x + disp[j].y * disp[j].y);
|
||||||
if (distLength > 0) {
|
if (distLength > 0) {
|
||||||
// && !n.isFixed()
|
// && !n.isFixed()
|
||||||
@ -183,19 +199,20 @@ export default class FruchtermanLayout extends BaseLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: nodeMap、nodeIndexMap 等根本不需要依靠参数传递
|
// TODO: nodeMap、nodeIndexMap 等根本不需要依靠参数传递
|
||||||
private getDisp(nodes: Node[], edges: Edge[], nodeMap: object, nodeIndexMap: object, disp, k) {
|
private getDisp(nodes: Node[], edges: Edge[], nodeMap: NodeMap, nodeIdxMap: NodeIdxMap, disp: Point[], k: number) {
|
||||||
const self = this;
|
const self = this;
|
||||||
self.calRepulsive(nodes, disp, k);
|
self.calRepulsive(nodes, disp, k);
|
||||||
self.calAttractive(edges, nodeMap, nodeIndexMap, disp, k);
|
self.calAttractive(edges, nodeMap, nodeIdxMap, disp, k);
|
||||||
}
|
}
|
||||||
|
|
||||||
private calRepulsive(nodes: Node[], disp, k) {
|
private calRepulsive(nodes: Node[], disp: Point[], k: number) {
|
||||||
nodes.forEach((v, i) => {
|
nodes.forEach((v, i) => {
|
||||||
disp[i] = { x: 0, y: 0 };
|
disp[i] = { x: 0, y: 0 };
|
||||||
nodes.forEach((u, j) => {
|
nodes.forEach((u, j) => {
|
||||||
if (i === j) {
|
if (i === j) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!isNumber(v.x) || !isNumber(u.x) || !isNumber(v.y) || !isNumber(u.y)) return;
|
||||||
let vecx = v.x - u.x;
|
let vecx = v.x - u.x;
|
||||||
let vecy = v.y - u.y;
|
let vecy = v.y - u.y;
|
||||||
let vecLengthSqr = vecx * vecx + vecy * vecy;
|
let vecLengthSqr = vecx * vecx + vecy * vecy;
|
||||||
@ -212,15 +229,17 @@ export default class FruchtermanLayout extends BaseLayout {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private calAttractive(edges: Edge[], nodeMap: object, nodeIndexMap: object, disp, k) {
|
private calAttractive(edges: Edge[], nodeMap: NodeMap, nodeIdxMap: NodeIdxMap, disp: Point[], k: number) {
|
||||||
edges.forEach((e) => {
|
edges.forEach((e) => {
|
||||||
const uIndex = nodeIndexMap[e.source];
|
if (!e.source || !e.target) return;
|
||||||
const vIndex = nodeIndexMap[e.target];
|
const uIndex = nodeIdxMap[e.source];
|
||||||
|
const vIndex = nodeIdxMap[e.target];
|
||||||
if (uIndex === vIndex) {
|
if (uIndex === vIndex) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const u = nodeMap[e.source];
|
const u = nodeMap[e.source];
|
||||||
const v = nodeMap[e.target];
|
const v = nodeMap[e.target];
|
||||||
|
if (!isNumber(v.x) || !isNumber(u.x) || !isNumber(v.y) || !isNumber(u.y)) return;
|
||||||
const vecx = v.x - u.x;
|
const vecx = v.x - u.x;
|
||||||
const vecy = v.y - u.y;
|
const vecy = v.y - u.y;
|
||||||
const vecLength = Math.sqrt(vecx * vecx + vecy * vecy);
|
const vecLength = Math.sqrt(vecx * vecx + vecy * vecy);
|
||||||
|
@ -4,76 +4,64 @@
|
|||||||
* this algorithm refers to <cytoscape.js> - https://github.com/cytoscape/cytoscape.js/
|
* this algorithm refers to <cytoscape.js> - https://github.com/cytoscape/cytoscape.js/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EdgeConfig, IPointTuple, NodeConfig } from '../types';
|
import { EdgeConfig, IPointTuple, NodeConfig, NodeIdxMap } from '../types';
|
||||||
|
|
||||||
import isString from '@antv/util/lib/is-string';
|
import isString from '@antv/util/lib/is-string';
|
||||||
import { BaseLayout } from './layout';
|
import { BaseLayout } from './layout';
|
||||||
import { isArray, isNumber } from '@antv/util';
|
import { isArray, isNumber } from '@antv/util';
|
||||||
|
import { getDegree } from '../util/math';
|
||||||
|
|
||||||
type Node = NodeConfig & {
|
type Node = NodeConfig & {
|
||||||
degree: number;
|
degree: number;
|
||||||
size: number;
|
|
||||||
};
|
};
|
||||||
type Edge = EdgeConfig;
|
type Edge = EdgeConfig;
|
||||||
|
|
||||||
function getDegree(n: number, nodeIdxMap: object, edges: Edge[]) {
|
|
||||||
const degrees = [];
|
|
||||||
for (let i = 0; i < n; i++) {
|
|
||||||
degrees[i] = 0;
|
|
||||||
}
|
|
||||||
edges.forEach((e) => {
|
|
||||||
degrees[nodeIdxMap[e.source]] += 1;
|
|
||||||
degrees[nodeIdxMap[e.target]] += 1;
|
|
||||||
});
|
|
||||||
return degrees;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 网格布局
|
* 网格布局
|
||||||
*/
|
*/
|
||||||
export default class GridLayout extends BaseLayout {
|
export default class GridLayout extends BaseLayout {
|
||||||
/** 布局起始点 */
|
/** 布局起始点 */
|
||||||
public begin: IPointTuple;
|
public begin: IPointTuple = [0, 0];
|
||||||
/** prevents node overlap, may overflow boundingBox if not enough space */
|
/** prevents node overlap, may overflow boundingBox if not enough space */
|
||||||
public preventOverlap: boolean;
|
public preventOverlap: boolean = true;
|
||||||
/** extra spacing around nodes when preventOverlap: true */
|
/** extra spacing around nodes when preventOverlap: true */
|
||||||
public preventOverlapPadding: 10;
|
public preventOverlapPadding: number = 10;
|
||||||
/** uses all available space on false, uses minimal space on true */
|
/** uses all available space on false, uses minimal space on true */
|
||||||
public condense: boolean;
|
public condense: boolean = false;
|
||||||
/** force num of rows in the grid */
|
/** force num of rows in the grid */
|
||||||
public rows: number;
|
public rows: number | undefined;
|
||||||
/** force num of columns in the grid */
|
/** force num of columns in the grid */
|
||||||
public cols: number;
|
public cols: number | undefined;
|
||||||
/** returns { row, col } for element */
|
/** returns { row, col } for element */
|
||||||
public position: (node: Node) => { row: number; col: number };
|
public position: ((node: Node) => { row: number; col: number }) | undefined;
|
||||||
/** a sorting function to order the nodes; e.g. function(a, b){ return a.datapublic ('weight') - b.data('weight') } */
|
/** a sorting function to order the nodes; e.g. function(a, b){ return a.datapublic ('weight') - b.data('weight') } */
|
||||||
public sortBy: string;
|
public sortBy: string = 'degree';
|
||||||
public nodeSize: number | number[];
|
public nodeSize: number | number[] = 30;
|
||||||
|
|
||||||
public nodes: Node[];
|
public nodes: Node[] = [];
|
||||||
public edges: Edge[];
|
public edges: Edge[] = [];
|
||||||
|
|
||||||
/** 布局中心 */
|
/** 布局中心 */
|
||||||
public center: IPointTuple;
|
public center: IPointTuple = [0, 0];
|
||||||
public width: number;
|
public width: number = 300;
|
||||||
public height: number;
|
public height: number = 300;
|
||||||
|
|
||||||
private cells: number;
|
private cells: number | undefined;
|
||||||
private row: number;
|
private row: number = 0;
|
||||||
private col: number;
|
private col: number = 0;
|
||||||
private splits: number;
|
private splits: number | undefined;
|
||||||
private columns: number;
|
private columns: number | undefined;
|
||||||
private cellWidth: number;
|
private cellWidth: number = 0;
|
||||||
private cellHeight: number;
|
private cellHeight: number = 0;
|
||||||
private cellUsed: {
|
private cellUsed: {
|
||||||
[key: string]: boolean;
|
[key: string]: boolean;
|
||||||
};
|
} = {};
|
||||||
private id2manPos: {
|
private id2manPos: {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
row: number;
|
row: number;
|
||||||
col: number;
|
col: number;
|
||||||
};
|
};
|
||||||
};
|
} = {};
|
||||||
|
|
||||||
public getDefaultCfg() {
|
public getDefaultCfg() {
|
||||||
return {
|
return {
|
||||||
@ -83,7 +71,7 @@ export default class GridLayout extends BaseLayout {
|
|||||||
condense: false,
|
condense: false,
|
||||||
rows: undefined,
|
rows: undefined,
|
||||||
cols: undefined,
|
cols: undefined,
|
||||||
position() {},
|
position: undefined,
|
||||||
sortBy: 'degree',
|
sortBy: 'degree',
|
||||||
nodeSize: 30
|
nodeSize: 30
|
||||||
};
|
};
|
||||||
@ -109,7 +97,7 @@ export default class GridLayout extends BaseLayout {
|
|||||||
nodes.forEach((node) => {
|
nodes.forEach((node) => {
|
||||||
layoutNodes.push(node);
|
layoutNodes.push(node);
|
||||||
});
|
});
|
||||||
const nodeIdxMap = {};
|
const nodeIdxMap: NodeIdxMap = {};
|
||||||
layoutNodes.forEach((node, i) => {
|
layoutNodes.forEach((node, i) => {
|
||||||
nodeIdxMap[node.id] = i;
|
nodeIdxMap[node.id] = i;
|
||||||
});
|
});
|
||||||
@ -156,8 +144,8 @@ export default class GridLayout extends BaseLayout {
|
|||||||
if (self.cols * self.rows > self.cells) {
|
if (self.cols * self.rows > self.cells) {
|
||||||
// otherwise use the automatic values and adjust accordingly
|
// otherwise use the automatic values and adjust accordingly
|
||||||
// if rounding was up, see if we can reduce rows or columns
|
// if rounding was up, see if we can reduce rows or columns
|
||||||
const sm = self.small();
|
const sm = self.small() as number;
|
||||||
const lg = self.large();
|
const lg = self.large() as number;
|
||||||
|
|
||||||
// reducing the small side takes away the most cells, so try it first
|
// reducing the small side takes away the most cells, so try it first
|
||||||
if ((sm - 1) * lg >= self.cells) {
|
if ((sm - 1) * lg >= self.cells) {
|
||||||
@ -168,8 +156,8 @@ export default class GridLayout extends BaseLayout {
|
|||||||
} else {
|
} else {
|
||||||
// if rounding was too low, add rows or columns
|
// if rounding was too low, add rows or columns
|
||||||
while (self.cols * self.rows < self.cells) {
|
while (self.cols * self.rows < self.cells) {
|
||||||
const sm = self.small();
|
const sm = self.small() as number;
|
||||||
const lg = self.large();
|
const lg = self.large() as number;
|
||||||
|
|
||||||
// try to add to larger side first (adds less in multiplication)
|
// try to add to larger side first (adds less in multiplication)
|
||||||
if ((lg + 1) * sm >= self.cells) {
|
if ((lg + 1) * sm >= self.cells) {
|
||||||
@ -196,16 +184,16 @@ export default class GridLayout extends BaseLayout {
|
|||||||
node.y = 0;
|
node.y = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let nodew: number;
|
let nodew: number | undefined;
|
||||||
let nodeh: number;
|
let nodeh: number | undefined;
|
||||||
if (isArray(node.size)) {
|
if (isArray(node.size)) {
|
||||||
nodew = node.size[0];
|
nodew = node.size[0];
|
||||||
nodeh = node.size[1];
|
nodeh = node.size[1];
|
||||||
} else {
|
} else if(isNumber(node.size)){
|
||||||
nodew = node.size;
|
nodew = node.size;
|
||||||
nodeh = node.size;
|
nodeh = node.size;
|
||||||
}
|
}
|
||||||
if (isNaN(nodew) || isNaN(nodeh)) {
|
if (nodew === undefined || nodeh === undefined) {
|
||||||
if (isArray(self.nodeSize)) {
|
if (isArray(self.nodeSize)) {
|
||||||
nodew = self.nodeSize[0];
|
nodew = self.nodeSize[0];
|
||||||
nodeh = self.nodeSize[1];
|
nodeh = self.nodeSize[1];
|
||||||
@ -238,7 +226,10 @@ export default class GridLayout extends BaseLayout {
|
|||||||
self.id2manPos = {};
|
self.id2manPos = {};
|
||||||
for (let i = 0; i < layoutNodes.length; i++) {
|
for (let i = 0; i < layoutNodes.length; i++) {
|
||||||
const node = layoutNodes[i];
|
const node = layoutNodes[i];
|
||||||
const rcPos = self.position(node);
|
let rcPos;
|
||||||
|
if (self.position) {
|
||||||
|
rcPos = self.position(node);
|
||||||
|
}
|
||||||
|
|
||||||
if (rcPos && (rcPos.row !== undefined || rcPos.col !== undefined)) {
|
if (rcPos && (rcPos.row !== undefined || rcPos.col !== undefined)) {
|
||||||
// must have at least row or col def'd
|
// must have at least row or col def'd
|
||||||
@ -269,13 +260,15 @@ export default class GridLayout extends BaseLayout {
|
|||||||
self.getPos(node);
|
self.getPos(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private small(val?: number) {
|
private small(val?: number): number | undefined {
|
||||||
const self = this;
|
const self = this;
|
||||||
let res: number;
|
let res: number | undefined;
|
||||||
|
const rows = self.rows || 5;
|
||||||
|
const cols = self.cols || 5;
|
||||||
if (val == null) {
|
if (val == null) {
|
||||||
res = Math.min(self.rows, self.cols);
|
res = Math.min(rows, cols);
|
||||||
} else {
|
} else {
|
||||||
const min = Math.min(self.rows, self.cols);
|
const min = Math.min(rows, cols);
|
||||||
if (min === self.rows) {
|
if (min === self.rows) {
|
||||||
self.rows = val;
|
self.rows = val;
|
||||||
} else {
|
} else {
|
||||||
@ -285,13 +278,15 @@ export default class GridLayout extends BaseLayout {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
private large(val?: number) {
|
private large(val?: number): number | undefined {
|
||||||
const self = this;
|
const self = this;
|
||||||
let res: number;
|
let res: number | undefined;
|
||||||
|
const rows = self.rows || 5;
|
||||||
|
const cols = self.cols || 5;
|
||||||
if (val == null) {
|
if (val == null) {
|
||||||
res = Math.max(self.rows, self.cols);
|
res = Math.max(rows, cols);
|
||||||
} else {
|
} else {
|
||||||
const max = Math.max(self.rows, self.cols);
|
const max = Math.max(rows, cols);
|
||||||
if (max === self.rows) {
|
if (max === self.rows) {
|
||||||
self.rows = val;
|
self.rows = val;
|
||||||
} else {
|
} else {
|
||||||
@ -313,8 +308,9 @@ export default class GridLayout extends BaseLayout {
|
|||||||
|
|
||||||
private moveToNextCell() {
|
private moveToNextCell() {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
const cols = self.cols || 5;
|
||||||
self.col++;
|
self.col++;
|
||||||
if (self.col >= self.cols) {
|
if (self.col >= cols) {
|
||||||
self.col = 0;
|
self.col = 0;
|
||||||
self.row++;
|
self.row++;
|
||||||
}
|
}
|
||||||
|
@ -17,15 +17,15 @@ type LayoutConstructor<Cfg = any> = new () => BaseLayout<Cfg>;
|
|||||||
* 基础布局,将被自定义布局所继承
|
* 基础布局,将被自定义布局所继承
|
||||||
*/
|
*/
|
||||||
export class BaseLayout<Cfg = any> implements ILayout<Cfg> {
|
export class BaseLayout<Cfg = any> implements ILayout<Cfg> {
|
||||||
public nodes: NodeConfig[];
|
public nodes: NodeConfig[] | null = [];
|
||||||
public edges: EdgeConfig[];
|
public edges: EdgeConfig[] | null = [];
|
||||||
public positions: IPointTuple[];
|
public positions: IPointTuple[] | null = [];
|
||||||
public destroyed: boolean;
|
public destroyed: boolean = false;
|
||||||
|
|
||||||
public init(data: GraphData) {
|
public init(data: GraphData) {
|
||||||
const self = this;
|
const self = this;
|
||||||
self.nodes = data.nodes;
|
self.nodes = data.nodes || [];
|
||||||
self.edges = data.edges;
|
self.edges = data.edges || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public execute() {}
|
public execute() {}
|
||||||
@ -63,7 +63,7 @@ const Layout: {
|
|||||||
* @param {string} type 布局类型,外部引用指定必须,不要与已有布局类型重名
|
* @param {string} type 布局类型,外部引用指定必须,不要与已有布局类型重名
|
||||||
* @param {object} layout 布局方法
|
* @param {object} layout 布局方法
|
||||||
*/
|
*/
|
||||||
registerLayout<Cfg>(type, layout, layoutCons = BaseLayout) {
|
registerLayout<Cfg>(type: string, layout: LayoutOption<Cfg>, layoutCons = BaseLayout) {
|
||||||
if (!layout) {
|
if (!layout) {
|
||||||
throw new Error('please specify handler for this layout:' + type);
|
throw new Error('please specify handler for this layout:' + type);
|
||||||
}
|
}
|
||||||
@ -72,11 +72,11 @@ const Layout: {
|
|||||||
class GLayout extends layoutCons {
|
class GLayout extends layoutCons {
|
||||||
constructor(cfg: Cfg) {
|
constructor(cfg: Cfg) {
|
||||||
super();
|
super();
|
||||||
const self = this;
|
const self = this as any;
|
||||||
const props = {};
|
const props: object = {};
|
||||||
const defaultCfg = self.getDefaultCfg();
|
const defaultCfg = self.getDefaultCfg();
|
||||||
mix(props, defaultCfg, layout, cfg);
|
mix(props, defaultCfg, layout, cfg as unknown);
|
||||||
each(props, (value, key) => {
|
each(props, (value, key: string) => {
|
||||||
self[key] = value;
|
self[key] = value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
* @author shiwu.wyy@antfin.com
|
* @author shiwu.wyy@antfin.com
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IPointTuple } from '../types';
|
import { IPointTuple, Matrix } from '../types';
|
||||||
|
|
||||||
import Numeric from 'numericjs';
|
import Numeric from 'numericjs';
|
||||||
import { floydWarshall, getAdjMatrix, scaleMatrix } from '../util/math';
|
import { floydWarshall, getAdjMatrix, scaleMatrix } from '../util/math';
|
||||||
@ -14,11 +14,11 @@ import { BaseLayout } from './layout';
|
|||||||
*/
|
*/
|
||||||
export default class MDSLayout extends BaseLayout {
|
export default class MDSLayout extends BaseLayout {
|
||||||
/** 布局中心 */
|
/** 布局中心 */
|
||||||
public center: IPointTuple;
|
public center: IPointTuple = [0, 0];
|
||||||
/** 边长度 */
|
/** 边长度 */
|
||||||
public linkDistance: number;
|
public linkDistance: number = 50;
|
||||||
|
|
||||||
private scaledDistances;
|
private scaledDistances: Matrix[] | null = null;
|
||||||
|
|
||||||
public getDefaultCfg() {
|
public getDefaultCfg() {
|
||||||
return {
|
return {
|
||||||
@ -32,9 +32,9 @@ export default class MDSLayout extends BaseLayout {
|
|||||||
public execute() {
|
public execute() {
|
||||||
const self = this;
|
const self = this;
|
||||||
const nodes = self.nodes;
|
const nodes = self.nodes;
|
||||||
const edges = self.edges;
|
const edges = self.edges || [];
|
||||||
const center = self.center;
|
const center = self.center;
|
||||||
if (nodes.length === 0) {
|
if (!nodes || nodes.length === 0) {
|
||||||
return;
|
return;
|
||||||
} else if (nodes.length === 1) {
|
} else if (nodes.length === 1) {
|
||||||
nodes[0].x = center[0];
|
nodes[0].x = center[0];
|
||||||
@ -54,7 +54,7 @@ export default class MDSLayout extends BaseLayout {
|
|||||||
// get positions by MDS
|
// get positions by MDS
|
||||||
const positions = self.runMDS();
|
const positions = self.runMDS();
|
||||||
self.positions = positions;
|
self.positions = positions;
|
||||||
positions.forEach((p, i) => {
|
positions.forEach((p: number[], i: number) => {
|
||||||
nodes[i].x = p[0] + center[0];
|
nodes[i].x = p[0] + center[0];
|
||||||
nodes[i].y = p[1] + center[1];
|
nodes[i].y = p[1] + center[1];
|
||||||
});
|
});
|
||||||
@ -70,7 +70,7 @@ export default class MDSLayout extends BaseLayout {
|
|||||||
// square distances
|
// square distances
|
||||||
const M = Numeric.mul(-0.5, Numeric.pow(distances, 2));
|
const M = Numeric.mul(-0.5, Numeric.pow(distances, 2));
|
||||||
// double centre the rows/columns
|
// double centre the rows/columns
|
||||||
function mean(A) {
|
function mean(A: any) {
|
||||||
return Numeric.div(Numeric.add.apply(null, A), A.length);
|
return Numeric.div(Numeric.add.apply(null, A), A.length);
|
||||||
}
|
}
|
||||||
const rowMeans = mean(M);
|
const rowMeans = mean(M);
|
||||||
@ -85,12 +85,12 @@ export default class MDSLayout extends BaseLayout {
|
|||||||
// points from it
|
// points from it
|
||||||
const ret = Numeric.svd(M);
|
const ret = Numeric.svd(M);
|
||||||
const eigenValues = Numeric.sqrt(ret.S);
|
const eigenValues = Numeric.sqrt(ret.S);
|
||||||
return ret.U.map(function(row) {
|
return ret.U.map(function(row: any) {
|
||||||
return Numeric.mul(row, eigenValues).splice(0, dimension);
|
return Numeric.mul(row, eigenValues).splice(0, dimension);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public handleInfinity(distances: number[][]) {
|
public handleInfinity(distances: Matrix[]) {
|
||||||
let maxDistance = -999999;
|
let maxDistance = -999999;
|
||||||
distances.forEach((row) => {
|
distances.forEach((row) => {
|
||||||
row.forEach((value) => {
|
row.forEach((value) => {
|
||||||
|
@ -1,20 +1,25 @@
|
|||||||
import Numeric from 'numericjs';
|
import Numeric from 'numericjs';
|
||||||
|
import { IPointTuple, Matrix } from '../../types';
|
||||||
|
|
||||||
export default class MDS {
|
export default class MDS {
|
||||||
/** distance matrix */
|
/** distance matrix */
|
||||||
public distances: number[][];
|
public distances: Matrix[];
|
||||||
/** dimensions */
|
/** dimensions */
|
||||||
public dimension: number;
|
public dimension: number;
|
||||||
/** link distance */
|
/** link distance */
|
||||||
public linkDistance: number;
|
public linkDistance: number;
|
||||||
|
|
||||||
constructor(params) {
|
constructor(params: {
|
||||||
|
distances: Matrix[],
|
||||||
|
dimension?: number,
|
||||||
|
linkDistance: number
|
||||||
|
}) {
|
||||||
this.distances = params.distances;
|
this.distances = params.distances;
|
||||||
this.dimension = params.dimension || 2;
|
this.dimension = params.dimension || 2;
|
||||||
this.linkDistance = params.linkDistance;
|
this.linkDistance = params.linkDistance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public layout() {
|
public layout(): IPointTuple[] {
|
||||||
const self = this;
|
const self = this;
|
||||||
const dimension = self.dimension;
|
const dimension = self.dimension;
|
||||||
const distances = self.distances;
|
const distances = self.distances;
|
||||||
@ -24,7 +29,7 @@ export default class MDS {
|
|||||||
const M = Numeric.mul(-0.5, Numeric.pow(distances, 2));
|
const M = Numeric.mul(-0.5, Numeric.pow(distances, 2));
|
||||||
|
|
||||||
// double centre the rows/columns
|
// double centre the rows/columns
|
||||||
function mean(A) {
|
function mean(A: any) {
|
||||||
return Numeric.div(Numeric.add.apply(null, A), A.length);
|
return Numeric.div(Numeric.add.apply(null, A), A.length);
|
||||||
}
|
}
|
||||||
const rowMeans = mean(M);
|
const rowMeans = mean(M);
|
||||||
@ -40,7 +45,7 @@ export default class MDS {
|
|||||||
// take the SVD of the double centred matrix, and return the
|
// take the SVD of the double centred matrix, and return the
|
||||||
// points from it
|
// points from it
|
||||||
let ret;
|
let ret;
|
||||||
let res = [];
|
let res: IPointTuple[] = [];
|
||||||
try {
|
try {
|
||||||
ret = Numeric.svd(M);
|
ret = Numeric.svd(M);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -53,7 +58,7 @@ export default class MDS {
|
|||||||
}
|
}
|
||||||
if (res.length === 0) {
|
if (res.length === 0) {
|
||||||
const eigenValues = Numeric.sqrt(ret.S);
|
const eigenValues = Numeric.sqrt(ret.S);
|
||||||
res = ret.U.map(function(row) {
|
res = ret.U.map(function(row: any) {
|
||||||
return Numeric.mul(row, eigenValues).splice(0, dimension);
|
return Numeric.mul(row, eigenValues).splice(0, dimension);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
* @author shiwu.wyy@antfin.com
|
* @author shiwu.wyy@antfin.com
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IPointTuple, NodeConfig } from '../../types';
|
import { IPointTuple, NodeConfig, Matrix } from '../../types';
|
||||||
|
|
||||||
import isArray from '@antv/util/lib/is-array';
|
import isArray from '@antv/util/lib/is-array';
|
||||||
import isNumber from '@antv/util/lib/is-number';
|
import isNumber from '@antv/util/lib/is-number';
|
||||||
@ -14,11 +14,11 @@ import { floydWarshall, getAdjMatrix } from '../../util/math';
|
|||||||
import { BaseLayout } from '../layout';
|
import { BaseLayout } from '../layout';
|
||||||
|
|
||||||
import MDS from './mds';
|
import MDS from './mds';
|
||||||
import RadialNonoverlapForce from './radialNonoverlapForce';
|
import RadialNonoverlapForce, { RadialNonoverlapForceParam } from './radialNonoverlapForce';
|
||||||
|
|
||||||
type Node = NodeConfig;
|
type Node = NodeConfig;
|
||||||
|
|
||||||
function getWeightMatrix(M: number[][]) {
|
function getWeightMatrix(M: Matrix[]) {
|
||||||
const rows = M.length;
|
const rows = M.length;
|
||||||
const cols = M[0].length;
|
const cols = M[0].length;
|
||||||
const result = [];
|
const result = [];
|
||||||
@ -56,37 +56,37 @@ function getEDistance(p1: IPointTuple, p2: IPointTuple) {
|
|||||||
*/
|
*/
|
||||||
export default class RadialLayout extends BaseLayout {
|
export default class RadialLayout extends BaseLayout {
|
||||||
/** 布局中心 */
|
/** 布局中心 */
|
||||||
public center: IPointTuple;
|
public center: IPointTuple = [0, 0];
|
||||||
/** 停止迭代的最大迭代数 */
|
/** 停止迭代的最大迭代数 */
|
||||||
public maxIteration: number;
|
public maxIteration: number = 1000;
|
||||||
/** 中心点,默认为数据中第一个点 */
|
/** 中心点,默认为数据中第一个点 */
|
||||||
public focusNode: String | Node;
|
public focusNode: String | Node | null = null;
|
||||||
/** 每一圈半径 */
|
/** 每一圈半径 */
|
||||||
public unitRadius: number;
|
public unitRadius: number | null = null;
|
||||||
/** 默认边长度 */
|
/** 默认边长度 */
|
||||||
public linkDistance: number;
|
public linkDistance: number = 50;
|
||||||
/** 是否防止重叠 */
|
/** 是否防止重叠 */
|
||||||
public preventOverlap: boolean;
|
public preventOverlap: boolean = false;
|
||||||
/** 节点直径 */
|
/** 节点直径 */
|
||||||
public nodeSize: number;
|
public nodeSize: number | undefined;
|
||||||
/** 节点间距,防止节点重叠时节点之间的最小距离(两节点边缘最短距离) */
|
/** 节点间距,防止节点重叠时节点之间的最小距离(两节点边缘最短距离) */
|
||||||
public nodeSpacing: number;
|
public nodeSpacing: number | Function | undefined;
|
||||||
/** 是否必须是严格的 radial 布局,即每一层的节点严格布局在一个环上。preventOverlap 为 true 时生效 */
|
/** 是否必须是严格的 radial 布局,即每一层的节点严格布局在一个环上。preventOverlap 为 true 时生效 */
|
||||||
public strictRadial: boolean;
|
public strictRadial: boolean = true;
|
||||||
/** 防止重叠步骤的最大迭代次数 */
|
/** 防止重叠步骤的最大迭代次数 */
|
||||||
public maxPreventOverlapIteration: number;
|
public maxPreventOverlapIteration: number = 200;
|
||||||
|
|
||||||
public sortBy: string;
|
public sortBy: string | undefined;
|
||||||
public sortStrength: number;
|
public sortStrength: number = 10;
|
||||||
|
|
||||||
public width: number;
|
public width: number | undefined;
|
||||||
public height: number;
|
public height: number | undefined;
|
||||||
|
|
||||||
private focusIndex;
|
private focusIndex: number | undefined;
|
||||||
private distances;
|
private distances: Matrix[] | undefined;
|
||||||
private eIdealDistances;
|
private eIdealDistances: Matrix[] | undefined;
|
||||||
private weights;
|
private weights: Matrix[] | undefined;
|
||||||
private radii;
|
private radii: number[] | undefined;
|
||||||
|
|
||||||
public getDefaultCfg() {
|
public getDefaultCfg() {
|
||||||
return {
|
return {
|
||||||
@ -110,9 +110,9 @@ export default class RadialLayout extends BaseLayout {
|
|||||||
public execute() {
|
public execute() {
|
||||||
const self = this;
|
const self = this;
|
||||||
const nodes = self.nodes;
|
const nodes = self.nodes;
|
||||||
const edges = self.edges;
|
const edges = self.edges || [];
|
||||||
const center = self.center;
|
const center = self.center;
|
||||||
if (nodes.length === 0) {
|
if (!nodes || nodes.length === 0) {
|
||||||
return;
|
return;
|
||||||
} else if (nodes.length === 1) {
|
} else if (nodes.length === 1) {
|
||||||
nodes[0].x = center[0];
|
nodes[0].x = center[0];
|
||||||
@ -121,7 +121,7 @@ export default class RadialLayout extends BaseLayout {
|
|||||||
}
|
}
|
||||||
const linkDistance = self.linkDistance;
|
const linkDistance = self.linkDistance;
|
||||||
// layout
|
// layout
|
||||||
let focusNode: Node;
|
let focusNode: Node | null = null;
|
||||||
if (isString(self.focusNode)) {
|
if (isString(self.focusNode)) {
|
||||||
let found = false;
|
let found = false;
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
@ -163,8 +163,8 @@ export default class RadialLayout extends BaseLayout {
|
|||||||
if (!self.height && typeof window !== 'undefined') {
|
if (!self.height && typeof window !== 'undefined') {
|
||||||
self.height = window.innerHeight;
|
self.height = window.innerHeight;
|
||||||
}
|
}
|
||||||
const width = self.width;
|
const width = self.width || 500;
|
||||||
const height = self.height;
|
const height = self.height || 500;
|
||||||
let semiWidth = width - center[0] > center[0] ? center[0] : width - center[0];
|
let semiWidth = width - center[0] > center[0] ? center[0] : width - center[0];
|
||||||
let semiHeight = height - center[1] > center[1] ? center[1] : height - center[1];
|
let semiHeight = height - center[1] > center[1] ? center[1] : height - center[1];
|
||||||
if (semiWidth === 0) {
|
if (semiWidth === 0) {
|
||||||
@ -177,7 +177,7 @@ export default class RadialLayout extends BaseLayout {
|
|||||||
const maxRadius = semiHeight > semiWidth ? semiWidth : semiHeight;
|
const maxRadius = semiHeight > semiWidth ? semiWidth : semiHeight;
|
||||||
const maxD = Math.max(...focusNodeD);
|
const maxD = Math.max(...focusNodeD);
|
||||||
// the radius for each nodes away from focusNode
|
// the radius for each nodes away from focusNode
|
||||||
const radii = [];
|
const radii: number[] = [];
|
||||||
focusNodeD.forEach((value, i) => {
|
focusNodeD.forEach((value, i) => {
|
||||||
if (!self.unitRadius) {
|
if (!self.unitRadius) {
|
||||||
self.unitRadius = maxRadius / maxD;
|
self.unitRadius = maxRadius / maxD;
|
||||||
@ -196,7 +196,7 @@ export default class RadialLayout extends BaseLayout {
|
|||||||
// the initial positions from mds
|
// the initial positions from mds
|
||||||
const mds = new MDS({ distances: eIdealD, linkDistance });
|
const mds = new MDS({ distances: eIdealD, linkDistance });
|
||||||
let positions = mds.layout();
|
let positions = mds.layout();
|
||||||
positions.forEach((p) => {
|
positions.forEach((p: IPointTuple) => {
|
||||||
if (isNaN(p[0])) {
|
if (isNaN(p[0])) {
|
||||||
p[0] = Math.random() * linkDistance;
|
p[0] = Math.random() * linkDistance;
|
||||||
}
|
}
|
||||||
@ -205,12 +205,12 @@ export default class RadialLayout extends BaseLayout {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
self.positions = positions;
|
self.positions = positions;
|
||||||
positions.forEach((p, i) => {
|
positions.forEach((p: IPointTuple, i: number) => {
|
||||||
nodes[i].x = p[0] + center[0];
|
nodes[i].x = p[0] + center[0];
|
||||||
nodes[i].y = p[1] + center[1];
|
nodes[i].y = p[1] + center[1];
|
||||||
});
|
});
|
||||||
// move the graph to origin, centered at focusNode
|
// move the graph to origin, centered at focusNode
|
||||||
positions.forEach((p) => {
|
positions.forEach((p: IPointTuple) => {
|
||||||
p[0] -= positions[focusIndex][0];
|
p[0] -= positions[focusIndex][0];
|
||||||
p[1] -= positions[focusIndex][1];
|
p[1] -= positions[focusIndex][1];
|
||||||
});
|
});
|
||||||
@ -222,7 +222,7 @@ export default class RadialLayout extends BaseLayout {
|
|||||||
// stagger the overlapped nodes
|
// stagger the overlapped nodes
|
||||||
if (preventOverlap) {
|
if (preventOverlap) {
|
||||||
const nodeSpacing = self.nodeSpacing;
|
const nodeSpacing = self.nodeSpacing;
|
||||||
let nodeSpacingFunc;
|
let nodeSpacingFunc: Function;
|
||||||
if (isNumber(nodeSpacing)) {
|
if (isNumber(nodeSpacing)) {
|
||||||
nodeSpacingFunc = () => {
|
nodeSpacingFunc = () => {
|
||||||
return nodeSpacing;
|
return nodeSpacing;
|
||||||
@ -235,7 +235,7 @@ export default class RadialLayout extends BaseLayout {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (!nodeSize) {
|
if (!nodeSize) {
|
||||||
nodeSizeFunc = (d) => {
|
nodeSizeFunc = (d: NodeConfig) => {
|
||||||
if (d.size) {
|
if (d.size) {
|
||||||
if (isArray(d.size)) {
|
if (isArray(d.size)) {
|
||||||
const res = d.size[0] > d.size[1] ? d.size[0] : d.size[1];
|
const res = d.size[0] > d.size[1] ? d.size[0] : d.size[1];
|
||||||
@ -247,17 +247,17 @@ export default class RadialLayout extends BaseLayout {
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
if (isArray(nodeSize)) {
|
if (isArray(nodeSize)) {
|
||||||
nodeSizeFunc = (d) => {
|
nodeSizeFunc = (d: NodeConfig) => {
|
||||||
const res = nodeSize[0] > nodeSize[1] ? nodeSize[0] : nodeSize[1];
|
const res = nodeSize[0] > nodeSize[1] ? nodeSize[0] : nodeSize[1];
|
||||||
return res + nodeSpacingFunc(d);
|
return res + nodeSpacingFunc(d);
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
nodeSizeFunc = (d) => {
|
nodeSizeFunc = (d: NodeConfig) => {
|
||||||
return nodeSize + nodeSpacingFunc(d);
|
return nodeSize + nodeSpacingFunc(d);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const nonoverlapForce = new RadialNonoverlapForce({
|
const nonoverlapForceParams: RadialNonoverlapForceParam = {
|
||||||
nodeSizeFunc,
|
nodeSizeFunc,
|
||||||
adjMatrix,
|
adjMatrix,
|
||||||
positions,
|
positions,
|
||||||
@ -269,11 +269,12 @@ export default class RadialLayout extends BaseLayout {
|
|||||||
iterations: self.maxPreventOverlapIteration || 200,
|
iterations: self.maxPreventOverlapIteration || 200,
|
||||||
k: positions.length / 4.5,
|
k: positions.length / 4.5,
|
||||||
nodes,
|
nodes,
|
||||||
});
|
};
|
||||||
|
const nonoverlapForce = new RadialNonoverlapForce(nonoverlapForceParams);
|
||||||
positions = nonoverlapForce.layout();
|
positions = nonoverlapForce.layout();
|
||||||
}
|
}
|
||||||
// move the graph to center
|
// move the graph to center
|
||||||
positions.forEach((p, i) => {
|
positions.forEach((p: IPointTuple, i: number) => {
|
||||||
nodes[i].x = p[0] + center[0];
|
nodes[i].x = p[0] + center[0];
|
||||||
nodes[i].y = p[1] + center[1];
|
nodes[i].y = p[1] + center[1];
|
||||||
});
|
});
|
||||||
@ -281,21 +282,21 @@ export default class RadialLayout extends BaseLayout {
|
|||||||
public run() {
|
public run() {
|
||||||
const self = this;
|
const self = this;
|
||||||
const maxIteration = self.maxIteration;
|
const maxIteration = self.maxIteration;
|
||||||
const positions = self.positions;
|
const positions = self.positions || [];
|
||||||
const W = self.weights;
|
const W = self.weights|| [];
|
||||||
const eIdealDis = self.eIdealDistances;
|
const eIdealDis = self.eIdealDistances || [];
|
||||||
const radii = self.radii;
|
const radii = self.radii || [];
|
||||||
for (let i = 0; i <= maxIteration; i++) {
|
for (let i = 0; i <= maxIteration; i++) {
|
||||||
const param = i / maxIteration;
|
const param = i / maxIteration;
|
||||||
self.oneIteration(param, positions, radii, eIdealDis, W);
|
self.oneIteration(param, positions, radii, eIdealDis, W);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private oneIteration(param, positions, radii, D, W) {
|
private oneIteration(param: number, positions: IPointTuple[], radii: number[], D: Matrix[], W: Matrix[]) {
|
||||||
const self = this;
|
const self = this;
|
||||||
const vparam = 1 - param;
|
const vparam = 1 - param;
|
||||||
const focusIndex = self.focusIndex;
|
const focusIndex = self.focusIndex;
|
||||||
positions.forEach((v, i) => {
|
positions.forEach((v: IPointTuple, i: number) => {
|
||||||
// v
|
// v
|
||||||
const originDis = getEDistance(v, [0, 0]);
|
const originDis = getEDistance(v, [0, 0]);
|
||||||
const reciODis = originDis === 0 ? 0 : 1 / originDis;
|
const reciODis = originDis === 0 ? 0 : 1 / originDis;
|
||||||
@ -335,16 +336,17 @@ export default class RadialLayout extends BaseLayout {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private eIdealDisMatrix() {
|
private eIdealDisMatrix(): Matrix[] {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
const nodes = self.nodes;
|
||||||
|
if (!nodes) return [];
|
||||||
const D = self.distances;
|
const D = self.distances;
|
||||||
const linkDis = self.linkDistance;
|
const linkDis = self.linkDistance;
|
||||||
const radii = self.radii;
|
const radii = self.radii || [];
|
||||||
const unitRadius = self.unitRadius;
|
const unitRadius = self.unitRadius || 50;
|
||||||
const result = [];
|
const result: Matrix[] = [];
|
||||||
const nodes = self.nodes;
|
D && D.forEach((row, i) => {
|
||||||
D.forEach((row, i) => {
|
const newRow: Matrix = [];
|
||||||
const newRow: number[] = [];
|
|
||||||
row.forEach((v, j) => {
|
row.forEach((v, j) => {
|
||||||
if (i === j) {
|
if (i === j) {
|
||||||
newRow.push(0);
|
newRow.push(0);
|
||||||
@ -375,7 +377,7 @@ export default class RadialLayout extends BaseLayout {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleInfinity(matrix, focusIndex, step) {
|
private handleInfinity(matrix: Matrix[], focusIndex: number, step: number) {
|
||||||
const length = matrix.length;
|
const length = matrix.length;
|
||||||
// 遍历 matrix 中遍历 focus 对应行
|
// 遍历 matrix 中遍历 focus 对应行
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
@ -407,7 +409,7 @@ export default class RadialLayout extends BaseLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private maxToFocus(matrix, focusIndex) {
|
private maxToFocus(matrix: Matrix[], focusIndex: number): number {
|
||||||
let max = 0;
|
let max = 0;
|
||||||
for (let i = 0; i < matrix[focusIndex].length; i++) {
|
for (let i = 0; i < matrix[focusIndex].length; i++) {
|
||||||
if (matrix[focusIndex][i] === Infinity) {
|
if (matrix[focusIndex][i] === Infinity) {
|
||||||
|
@ -1,14 +1,33 @@
|
|||||||
|
import { Matrix, IPointTuple } from '../../types';
|
||||||
|
import { Point } from '@antv/g-base';
|
||||||
|
|
||||||
const SPEED_DIVISOR = 800;
|
const SPEED_DIVISOR = 800;
|
||||||
|
|
||||||
|
export type RadialNonoverlapForceParam = {
|
||||||
|
positions: IPointTuple[];
|
||||||
|
adjMatrix: Matrix[];
|
||||||
|
focusID: number;
|
||||||
|
radii: number[];
|
||||||
|
iterations?: number;
|
||||||
|
height?: number;
|
||||||
|
width?: number;
|
||||||
|
speed?: number;
|
||||||
|
gravity?: number;
|
||||||
|
nodeSizeFunc: (node: any) => number;
|
||||||
|
k: number;
|
||||||
|
strictRadial: boolean;
|
||||||
|
nodes: any[];
|
||||||
|
};
|
||||||
|
|
||||||
export default class RadialNonoverlapForce {
|
export default class RadialNonoverlapForce {
|
||||||
/** node positions */
|
/** node positions */
|
||||||
public positions: any[];
|
public positions: IPointTuple[];
|
||||||
/** adjacency matrix */
|
/** adjacency matrix */
|
||||||
public adjMatrix: any[];
|
public adjMatrix: Matrix[];
|
||||||
/** focus node */
|
/** focus node */
|
||||||
public focusID: number;
|
public focusID: number;
|
||||||
/** radii */
|
/** radii */
|
||||||
public radii: number;
|
public radii: number[];
|
||||||
/** the number of iterations */
|
/** the number of iterations */
|
||||||
public iterations: number;
|
public iterations: number;
|
||||||
/** the height of the canvas */
|
/** the height of the canvas */
|
||||||
@ -24,19 +43,14 @@ export default class RadialNonoverlapForce {
|
|||||||
/** the strength of forces */
|
/** the strength of forces */
|
||||||
public k: number;
|
public k: number;
|
||||||
/** if each circle can be separated into subcircles to avoid overlappings */
|
/** if each circle can be separated into subcircles to avoid overlappings */
|
||||||
public strictRadial: number;
|
public strictRadial: boolean;
|
||||||
/** the nodes data */
|
/** the nodes data */
|
||||||
public nodes: any[];
|
public nodes: any[];
|
||||||
|
|
||||||
private maxDisplace: number;
|
private maxDisplace: number | undefined;
|
||||||
private disp: Array<{
|
private disp: Point[] = [];
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
dx?: number;
|
|
||||||
dy?: number;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
constructor(params) {
|
constructor(params: RadialNonoverlapForceParam) {
|
||||||
this.positions = params.positions;
|
this.positions = params.positions;
|
||||||
this.adjMatrix = params.adjMatrix;
|
this.adjMatrix = params.adjMatrix;
|
||||||
this.focusID = params.focusID;
|
this.focusID = params.focusID;
|
||||||
@ -52,10 +66,10 @@ export default class RadialNonoverlapForce {
|
|||||||
this.nodes = params.nodes;
|
this.nodes = params.nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public layout() {
|
public layout(): IPointTuple[] {
|
||||||
const self = this;
|
const self = this;
|
||||||
const positions = self.positions;
|
const positions = self.positions;
|
||||||
const disp = [];
|
const disp: Point[] = [];
|
||||||
const iterations = self.iterations;
|
const iterations = self.iterations;
|
||||||
const maxDisplace = self.width / 10;
|
const maxDisplace = self.width / 10;
|
||||||
self.maxDisplace = maxDisplace;
|
self.maxDisplace = maxDisplace;
|
||||||
@ -77,11 +91,11 @@ export default class RadialNonoverlapForce {
|
|||||||
const nodes = self.nodes;
|
const nodes = self.nodes;
|
||||||
const disp = self.disp;
|
const disp = self.disp;
|
||||||
const k = self.k;
|
const k = self.k;
|
||||||
const radii = self.radii;
|
const radii = self.radii || [];
|
||||||
|
|
||||||
positions.forEach((v, i) => {
|
positions.forEach((v: IPointTuple, i: number) => {
|
||||||
disp[i] = { x: 0, y: 0 };
|
disp[i] = { x: 0, y: 0 };
|
||||||
positions.forEach((u, j) => {
|
positions.forEach((u: IPointTuple, j: number) => {
|
||||||
if (i === j) {
|
if (i === j) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -115,6 +129,7 @@ export default class RadialNonoverlapForce {
|
|||||||
const speed = self.speed;
|
const speed = self.speed;
|
||||||
const strictRadial = self.strictRadial;
|
const strictRadial = self.strictRadial;
|
||||||
const f = self.focusID;
|
const f = self.focusID;
|
||||||
|
const maxDisplace = self.maxDisplace || self.width / 10;
|
||||||
|
|
||||||
if (strictRadial) {
|
if (strictRadial) {
|
||||||
disp.forEach((di, i) => {
|
disp.forEach((di, i) => {
|
||||||
@ -136,12 +151,6 @@ export default class RadialNonoverlapForce {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// speed
|
|
||||||
positions.forEach((_, i) => {
|
|
||||||
disp[i].dx *= speed / SPEED_DIVISOR;
|
|
||||||
disp[i].dy *= speed / SPEED_DIVISOR;
|
|
||||||
});
|
|
||||||
|
|
||||||
// move
|
// move
|
||||||
const radii = self.radii;
|
const radii = self.radii;
|
||||||
positions.forEach((n, i) => {
|
positions.forEach((n, i) => {
|
||||||
@ -150,7 +159,7 @@ export default class RadialNonoverlapForce {
|
|||||||
}
|
}
|
||||||
const distLength = Math.sqrt(disp[i].x * disp[i].x + disp[i].y * disp[i].y);
|
const distLength = Math.sqrt(disp[i].x * disp[i].x + disp[i].y * disp[i].y);
|
||||||
if (distLength > 0 && i !== f) {
|
if (distLength > 0 && i !== f) {
|
||||||
const limitedDist = Math.min(self.maxDisplace * (speed / SPEED_DIVISOR), distLength);
|
const limitedDist = Math.min(maxDisplace * (speed / SPEED_DIVISOR), distLength);
|
||||||
n[0] += (disp[i].x / distLength) * limitedDist;
|
n[0] += (disp[i].x / distLength) * limitedDist;
|
||||||
n[1] += (disp[i].y / distLength) * limitedDist;
|
n[1] += (disp[i].y / distLength) * limitedDist;
|
||||||
if (strictRadial) {
|
if (strictRadial) {
|
||||||
|
@ -11,11 +11,11 @@ import { BaseLayout } from './layout';
|
|||||||
*/
|
*/
|
||||||
export default class RandomLayout extends BaseLayout {
|
export default class RandomLayout extends BaseLayout {
|
||||||
/** 布局中心 */
|
/** 布局中心 */
|
||||||
public center: IPointTuple;
|
public center: IPointTuple = [0, 0];
|
||||||
/** 宽度 */
|
/** 宽度 */
|
||||||
public width: number;
|
public width: number = 300;
|
||||||
/** 高度 */
|
/** 高度 */
|
||||||
public height: number;
|
public height: number = 300;
|
||||||
|
|
||||||
public getDefaultCfg() {
|
public getDefaultCfg() {
|
||||||
return {
|
return {
|
||||||
@ -38,7 +38,7 @@ export default class RandomLayout extends BaseLayout {
|
|||||||
if (!self.height && typeof window !== 'undefined') {
|
if (!self.height && typeof window !== 'undefined') {
|
||||||
self.height = window.innerHeight;
|
self.height = window.innerHeight;
|
||||||
}
|
}
|
||||||
nodes.forEach((node) => {
|
nodes && nodes.forEach((node) => {
|
||||||
node.x = (Math.random() - 0.5) * layoutScale * self.width + center[0];
|
node.x = (Math.random() - 0.5) * layoutScale * self.width + center[0];
|
||||||
node.y = (Math.random() - 0.5) * layoutScale * self.height + center[1];
|
node.y = (Math.random() - 0.5) * layoutScale * self.height + center[1];
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Base, { IPluginBaseConfig } from '../base'
|
import Base, { IPluginBaseConfig } from '../base'
|
||||||
import Edge from '../../item/edge';
|
import Edge from '../../item/edge';
|
||||||
import Graph from '../../graph/graph';
|
import Graph from '../../graph/graph';
|
||||||
import { GraphData, NodeConfig, NodeMapConfig, EdgeConfig } from '../../types';
|
import { GraphData, NodeConfig, NodeConfigMap, EdgeConfig } from '../../types';
|
||||||
import { Point } from '@antv/g-base/lib/types';
|
import { Point } from '@antv/g-base/lib/types';
|
||||||
|
|
||||||
interface BundlingConfig extends IPluginBaseConfig {
|
interface BundlingConfig extends IPluginBaseConfig {
|
||||||
@ -90,7 +90,7 @@ export default class Bundling extends Base {
|
|||||||
|
|
||||||
const edges = data.edges;
|
const edges = data.edges;
|
||||||
const nodes = data.nodes;
|
const nodes = data.nodes;
|
||||||
const nodeIdMap: NodeMapConfig = {};
|
const nodeIdMap: NodeConfigMap = {};
|
||||||
let error = false;
|
let error = false;
|
||||||
|
|
||||||
nodes.forEach(node => {
|
nodes.forEach(node => {
|
||||||
@ -186,7 +186,7 @@ export default class Bundling extends Base {
|
|||||||
public divideEdges(divisions: number): Point[][] {
|
public divideEdges(divisions: number): Point[][] {
|
||||||
const self = this;
|
const self = this;
|
||||||
const edges: EdgeConfig[] = self.get('data').edges;
|
const edges: EdgeConfig[] = self.get('data').edges;
|
||||||
const nodeIdMap: NodeMapConfig = self.get('nodeIdMap');
|
const nodeIdMap: NodeConfigMap = self.get('nodeIdMap');
|
||||||
let edgePoints = self.get('edgePoints');
|
let edgePoints = self.get('edgePoints');
|
||||||
|
|
||||||
if (!edgePoints || edgePoints === undefined) edgePoints = [];
|
if (!edgePoints || edgePoints === undefined) edgePoints = [];
|
||||||
@ -264,7 +264,7 @@ export default class Bundling extends Base {
|
|||||||
const edges = data.edges;
|
const edges = data.edges;
|
||||||
|
|
||||||
const bundleThreshold: number = self.get('bundleThreshold');
|
const bundleThreshold: number = self.get('bundleThreshold');
|
||||||
const nodeIdMap: NodeMapConfig = self.get('nodeIdMap');
|
const nodeIdMap: NodeConfigMap = self.get('nodeIdMap');
|
||||||
let edgeBundles = self.get('edgeBundles');
|
let edgeBundles = self.get('edgeBundles');
|
||||||
|
|
||||||
if (!edgeBundles) edgeBundles = [];
|
if (!edgeBundles) edgeBundles = [];
|
||||||
|
@ -5,6 +5,7 @@ import ShapeBase from '@antv/g-canvas/lib/shape/base';
|
|||||||
import Node from '../item/node';
|
import Node from '../item/node';
|
||||||
import { IGraph } from '../interface/graph';
|
import { IGraph } from '../interface/graph';
|
||||||
import { IEdge, INode } from '../interface/item';
|
import { IEdge, INode } from '../interface/item';
|
||||||
|
import { ILabelConfig } from '../interface/shape';
|
||||||
|
|
||||||
// Math types
|
// Math types
|
||||||
export interface IPoint {
|
export interface IPoint {
|
||||||
@ -117,7 +118,7 @@ export type ModelStyle = Partial<{
|
|||||||
};
|
};
|
||||||
// loop edge config
|
// loop edge config
|
||||||
loopCfg: LoopConfig;
|
loopCfg: LoopConfig;
|
||||||
labelCfg?: object;
|
labelCfg?: ILabelConfig;
|
||||||
anchorPoints: number[][];
|
anchorPoints: number[][];
|
||||||
controlPoints: IPoint[];
|
controlPoints: IPoint[];
|
||||||
size: number | number[];
|
size: number | number[];
|
||||||
@ -178,10 +179,7 @@ export interface ModelConfig extends ModelStyle {
|
|||||||
// 节点或边的类型
|
// 节点或边的类型
|
||||||
type?: string;
|
type?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
labelCfg?: {
|
labelCfg?: ILabelConfig;
|
||||||
style?: object;
|
|
||||||
[key: string]: unknown;
|
|
||||||
};
|
|
||||||
descriptionCfg?: {
|
descriptionCfg?: {
|
||||||
style?: object;
|
style?: object;
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
@ -205,8 +203,10 @@ export interface ModelConfig extends ModelStyle {
|
|||||||
endPoint?: IPoint;
|
endPoint?: IPoint;
|
||||||
children?: TreeGraphData[];
|
children?: TreeGraphData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NodeConfig extends ModelConfig {
|
export interface NodeConfig extends ModelConfig {
|
||||||
id: string;
|
id: string;
|
||||||
|
size?: number | number[];
|
||||||
groupId?: string;
|
groupId?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
@ -216,10 +216,7 @@ export interface EdgeConfig extends ModelConfig {
|
|||||||
source?: string;
|
source?: string;
|
||||||
target?: string;
|
target?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
labelCfg?: {
|
labelCfg?: ILabelConfig;
|
||||||
style?: object;
|
|
||||||
[key: string]: unknown;
|
|
||||||
};
|
|
||||||
sourceNode?: Node;
|
sourceNode?: Node;
|
||||||
targetNode?: Node;
|
targetNode?: Node;
|
||||||
startPoint?: IPoint;
|
startPoint?: IPoint;
|
||||||
@ -236,7 +233,11 @@ export type EdgeData = EdgeConfig & {
|
|||||||
endPoint: IPoint;
|
endPoint: IPoint;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface NodeMapConfig {
|
export interface NodeMap {
|
||||||
|
[key: string]: INode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NodeConfigMap {
|
||||||
[key: string]: NodeConfig;
|
[key: string]: NodeConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,7 +348,7 @@ export type BehaviorOpation<U> = {
|
|||||||
export type IEvent = Record<G6Event, string>;
|
export type IEvent = Record<G6Event, string>;
|
||||||
|
|
||||||
export interface IG6GraphEvent extends GraphEvent {
|
export interface IG6GraphEvent extends GraphEvent {
|
||||||
item: Item;
|
item: Item | null;
|
||||||
canvasX: number;
|
canvasX: number;
|
||||||
canvasY: number;
|
canvasY: number;
|
||||||
wheelDelta: number;
|
wheelDelta: number;
|
||||||
@ -361,8 +362,19 @@ export type Item = INode | IEdge;
|
|||||||
|
|
||||||
export type ITEM_TYPE = 'node' | 'edge' | 'group'
|
export type ITEM_TYPE = 'node' | 'edge' | 'group'
|
||||||
|
|
||||||
|
export type NodeIdxMap = {
|
||||||
|
[key: string]: number
|
||||||
|
}
|
||||||
|
|
||||||
// 触发 viewportchange 事件的参数
|
// 触发 viewportchange 事件的参数
|
||||||
export interface ViewPortEventParam {
|
export interface ViewPortEventParam {
|
||||||
action: string;
|
action: string;
|
||||||
matrix: Matrix;
|
matrix: Matrix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Indexable<T> { [key:string]: T}
|
||||||
|
|
||||||
|
export interface LayoutConfig {
|
||||||
|
type?: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { Point } from '@antv/g-base/lib/types';
|
|||||||
import { IGroup } from '@antv/g-canvas/lib/interfaces';
|
import { IGroup } from '@antv/g-canvas/lib/interfaces';
|
||||||
import { mat3, transform, vec3 } from '@antv/matrix-util';
|
import { mat3, transform, vec3 } from '@antv/matrix-util';
|
||||||
import isArray from '@antv/util/lib/is-array'
|
import isArray from '@antv/util/lib/is-array'
|
||||||
import { GraphData, ICircle, IEllipse, IRect, Matrix } from '../types';
|
import { GraphData, ICircle, IEllipse, IRect, Matrix, EdgeConfig, NodeIdxMap } from '../types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否在区间内
|
* 是否在区间内
|
||||||
@ -383,3 +383,19 @@ export const rotate = (group: IGroup, angle: number) => {
|
|||||||
|
|
||||||
group.setMatrix(matrix)
|
group.setMatrix(matrix)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getDegree = (n: number, nodeIdxMap: NodeIdxMap, edges: EdgeConfig[]): number[] => {
|
||||||
|
const degrees: number[] = [];
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
degrees[i] = 0;
|
||||||
|
}
|
||||||
|
edges.forEach((e) => {
|
||||||
|
if (e.source) {
|
||||||
|
degrees[nodeIdxMap[e.source]] += 1;
|
||||||
|
}
|
||||||
|
if (e.target) {
|
||||||
|
degrees[nodeIdxMap[e.target]] += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return degrees;
|
||||||
|
}
|
@ -1,10 +1,11 @@
|
|||||||
import G6 from '../../../src';
|
import G6 from '../../../src';
|
||||||
|
import { NodeConfig, EdgeConfig } from '../../../src/types';
|
||||||
|
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.id = 'graph-spec';
|
div.id = 'graph-spec';
|
||||||
document.body.appendChild(div);
|
document.body.appendChild(div);
|
||||||
|
|
||||||
const data: {nodes: object, edges: object} = {
|
const data: {nodes: NodeConfig[], edges: EdgeConfig[]} = {
|
||||||
nodes: [
|
nodes: [
|
||||||
{
|
{
|
||||||
id: '0',
|
id: '0',
|
||||||
|
@ -32,6 +32,9 @@ describe('layout using web worker', function() {
|
|||||||
callback();
|
callback();
|
||||||
});
|
});
|
||||||
graph.render();
|
graph.render();
|
||||||
|
setTimeout(() => {
|
||||||
|
callback();
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
function callback() {
|
function callback() {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
@ -41,7 +44,6 @@ describe('layout using web worker', function() {
|
|||||||
graph.once('afterlayout', () => {
|
graph.once('afterlayout', () => {
|
||||||
expect(node.x).not.toEqual(undefined);
|
expect(node.x).not.toEqual(undefined);
|
||||||
expect(node.y).not.toEqual(undefined);
|
expect(node.y).not.toEqual(undefined);
|
||||||
// FIXME:
|
|
||||||
expect(count >= 1).toEqual(true);
|
expect(count >= 1).toEqual(true);
|
||||||
expect(ended).toEqual(true);
|
expect(ended).toEqual(true);
|
||||||
graph.destroy();
|
graph.destroy();
|
||||||
|
Loading…
Reference in New Issue
Block a user