mirror of
https://gitee.com/antv/g6.git
synced 2024-12-02 11:48:29 +08:00
Merge pull request #1029 from antvis/g6-dev-20191209
feat: update types file
This commit is contained in:
commit
ccdeb3ae84
20
jest.config.js
Normal file
20
jest.config.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
runner: 'jest-electron/runner',
|
||||||
|
testEnvironment: 'jest-electron/environment',
|
||||||
|
preset: 'ts-jest',
|
||||||
|
collectCoverage: false,
|
||||||
|
collectCoverageFrom: [
|
||||||
|
'src/**/*.{ts,js}',
|
||||||
|
'!**/node_modules/**',
|
||||||
|
'!**/vendor/**'
|
||||||
|
],
|
||||||
|
testRegex: '/tests/.*-spec\\.ts?$',
|
||||||
|
moduleDirectories: [ 'node_modules', 'src' ],
|
||||||
|
moduleFileExtensions: [ 'js', 'ts', 'json' ],
|
||||||
|
moduleNameMapper: {
|
||||||
|
'@g6/(.*)': '<rootDir>/src/$1',
|
||||||
|
'@g6/types': '<rootDir>/types'
|
||||||
|
}
|
||||||
|
};
|
14
package.json
14
package.json
@ -26,7 +26,7 @@
|
|||||||
"clean": "rimraf esm lib dist",
|
"clean": "rimraf esm lib dist",
|
||||||
"lint": "lint-staged",
|
"lint": "lint-staged",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test-live": "DEBUG_MODE=1 jest --watch ./tests/unit/behavior/index-spec.ts",
|
"test-live": "DEBUG_MODE=1 jest --watch ./tests/unit/graph/controller/mode-spec.ts",
|
||||||
"coverage": "jest --coverage",
|
"coverage": "jest --coverage",
|
||||||
"ci": "run-s build coverage",
|
"ci": "run-s build coverage",
|
||||||
"doc": "rimraf apis && typedoc",
|
"doc": "rimraf apis && typedoc",
|
||||||
@ -81,18 +81,6 @@
|
|||||||
"git add"
|
"git add"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"jest": {
|
|
||||||
"runner": "jest-electron/runner",
|
|
||||||
"testEnvironment": "jest-electron/environment",
|
|
||||||
"preset": "ts-jest",
|
|
||||||
"collectCoverage": false,
|
|
||||||
"collectCoverageFrom": [
|
|
||||||
"src/**/*.{ts,js}",
|
|
||||||
"!**/node_modules/**",
|
|
||||||
"!**/vendor/**"
|
|
||||||
],
|
|
||||||
"testRegex": "/tests/.*-spec\\.ts?$"
|
|
||||||
},
|
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/antvis/g6"
|
"url": "https://github.com/antvis/g6"
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import clone from '@antv/util/lib/clone'
|
import clone from '@antv/util/lib/clone'
|
||||||
import { IBehaviorOpation } from '../../types';
|
import { BehaviorOpation } from '@g6/types';
|
||||||
import BehaviorOption from './behaviorOption'
|
import BehaviorOption from './behaviorOption'
|
||||||
|
|
||||||
export default class Behavior {
|
export default class Behavior {
|
||||||
private static types = {}
|
private static types = {}
|
||||||
public static registerBehavior<T, U>(type: string, behavior: IBehaviorOpation<U>) {
|
public static registerBehavior<T, U>(type: string, behavior: BehaviorOpation<U>) {
|
||||||
if(!behavior) {
|
if(!behavior) {
|
||||||
throw new Error(`please specify handler for this behavior: ${type}`)
|
throw new Error(`please specify handler for this behavior: ${type}`)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import each from '@antv/util/lib/each'
|
import each from '@antv/util/lib/each'
|
||||||
import wrapBehavior from '@antv/util/lib/wrap-behavior'
|
import wrapBehavior from '@antv/util/lib/wrap-behavior'
|
||||||
import { G6Event } from '../../types';
|
import { IGraph } from '@g6/interface/graph'
|
||||||
import { IGraph } from '../interface/graph'
|
import { G6Event } from '@g6/types';
|
||||||
|
|
||||||
export default class BehaviorOption {
|
export default class BehaviorOption {
|
||||||
private _events = null
|
private _events = null
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { G6Event, IG6GraphEvent } from "../../types";
|
import { G6Event, IG6GraphEvent } from "@g6/types";
|
||||||
import { cloneEvent } from '../util/base'
|
import { cloneEvent } from '@g6/util/base'
|
||||||
const abs = Math.abs
|
const abs = Math.abs
|
||||||
const DRAG_OFFSET = 10
|
const DRAG_OFFSET = 10
|
||||||
const ALLOW_EVENTS = [ 16, 17, 18 ]
|
const ALLOW_EVENTS = [ 16, 17, 18 ]
|
||||||
|
1
src/graph/controller/index.ts
Normal file
1
src/graph/controller/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default as Mode } from './mode'
|
175
src/graph/controller/mode.ts
Normal file
175
src/graph/controller/mode.ts
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
import each from '@antv/util/lib/each'
|
||||||
|
import isArray from '@antv/util/lib/is-array'
|
||||||
|
import isString from '@antv/util/lib/is-string'
|
||||||
|
import Behavior from '@g6/behavior/behavior'
|
||||||
|
import { IBehavior } from '@g6/interface/behavior';
|
||||||
|
import { IGraph, IMode, IModeType } from '@g6/interface/graph';
|
||||||
|
|
||||||
|
export default class Mode {
|
||||||
|
private graph: IGraph
|
||||||
|
/**
|
||||||
|
* modes = {
|
||||||
|
* default: [ 'drag-node', 'zoom-canvas' ],
|
||||||
|
* edit: [ 'drag-canvas', {
|
||||||
|
* type: 'brush-select',
|
||||||
|
* trigger: 'ctrl'
|
||||||
|
* }]
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type {IMode}
|
||||||
|
* @memberof Mode
|
||||||
|
*/
|
||||||
|
public modes: IMode
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mode = 'drag-node'
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type {string}
|
||||||
|
* @memberof Mode
|
||||||
|
*/
|
||||||
|
public mode: string
|
||||||
|
private currentBehaves: IBehavior[]
|
||||||
|
constructor(graph: IGraph) {
|
||||||
|
this.graph = graph
|
||||||
|
this.modes = graph.get('modes') || {
|
||||||
|
default: []
|
||||||
|
}
|
||||||
|
this.formatModes()
|
||||||
|
|
||||||
|
this.mode = graph.get('defaultMode') || 'default'
|
||||||
|
this.currentBehaves = []
|
||||||
|
|
||||||
|
this.setMode(this.mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatModes() {
|
||||||
|
const modes = this.modes;
|
||||||
|
each(modes, mode => {
|
||||||
|
each(mode, (behavior, i) => {
|
||||||
|
if (isString(behavior)) {
|
||||||
|
mode[i] = { type: behavior };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private setBehaviors(mode: string) {
|
||||||
|
const graph = this.graph;
|
||||||
|
const behaviors = this.modes[mode];
|
||||||
|
const behaves: IBehavior[] = [];
|
||||||
|
let behave: IBehavior;
|
||||||
|
each(behaviors, behavior => {
|
||||||
|
const BehaviorInstance = Behavior.getBehavior(behavior.type)
|
||||||
|
if (!BehaviorInstance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
behave = new BehaviorInstance(behavior);
|
||||||
|
if(behave) {
|
||||||
|
behave.bind(graph)
|
||||||
|
behaves.push(behave);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.currentBehaves = behaves;
|
||||||
|
}
|
||||||
|
|
||||||
|
private mergeBehaviors(modeBehaviors: IModeType[], behaviors: IModeType[]): IModeType[] {
|
||||||
|
each(behaviors, behavior => {
|
||||||
|
if (modeBehaviors.indexOf(behavior) < 0) {
|
||||||
|
if (isString(behavior)) {
|
||||||
|
behavior = { type: behavior };
|
||||||
|
}
|
||||||
|
modeBehaviors.push(behavior);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return modeBehaviors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private filterBehaviors(modeBehaviors: IModeType[], behaviors: IModeType[]): IModeType[] {
|
||||||
|
const result: IModeType[] = [];
|
||||||
|
modeBehaviors.forEach(behavior => {
|
||||||
|
let type: string = ''
|
||||||
|
if(isString(behavior)) {
|
||||||
|
type = behavior
|
||||||
|
} else {
|
||||||
|
type = behavior.type
|
||||||
|
}
|
||||||
|
if (behaviors.indexOf(type) < 0) {
|
||||||
|
result.push(behavior);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setMode(mode: string): Mode {
|
||||||
|
const modes = this.modes;
|
||||||
|
const graph = this.graph;
|
||||||
|
const behaviors = modes[mode];
|
||||||
|
if (!behaviors) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
graph.emit('beforemodechange', { mode });
|
||||||
|
|
||||||
|
each(this.currentBehaves, behave => {
|
||||||
|
behave.unbind(graph);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setBehaviors(mode);
|
||||||
|
|
||||||
|
graph.emit('aftermodechange', { mode });
|
||||||
|
this.mode = mode;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动态增加或删除 Behavior
|
||||||
|
*
|
||||||
|
* @param {IModeType[]} behaviors
|
||||||
|
* @param {(IModeType[] | IModeType)} modes
|
||||||
|
* @param {boolean} isAdd
|
||||||
|
* @returns {Mode}
|
||||||
|
* @memberof Mode
|
||||||
|
*/
|
||||||
|
public manipulateBehaviors(behaviors: IModeType[], modes: IModeType[] | IModeType, isAdd: boolean): Mode {
|
||||||
|
const self = this
|
||||||
|
if(!isArray(behaviors)) {
|
||||||
|
behaviors = [ behaviors ]
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isArray(modes)) {
|
||||||
|
each(modes, mode => {
|
||||||
|
if (!self.modes[mode]) {
|
||||||
|
if (isAdd) {
|
||||||
|
self.modes[mode] = [].concat(behaviors);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isAdd) {
|
||||||
|
self.modes[mode] = this.mergeBehaviors(self.modes[mode], behaviors);
|
||||||
|
} else {
|
||||||
|
self.modes[mode] = this.filterBehaviors(self.modes[mode], behaviors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentMode: string = ''
|
||||||
|
if(!modes) {
|
||||||
|
currentMode = this.mode
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAdd) {
|
||||||
|
self.modes[currentMode] = this.mergeBehaviors(self.modes[currentMode], behaviors);
|
||||||
|
} else {
|
||||||
|
self.modes[currentMode] = this.filterBehaviors(self.modes[currentMode], behaviors);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.setMode(this.mode)
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
13
src/graph/graph.ts
Normal file
13
src/graph/graph.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import EventEmitter from '@antv/event-emitter'
|
||||||
|
import { GraphOptions, IGraph } from '@g6/interface/graph';
|
||||||
|
|
||||||
|
export default class Graph extends EventEmitter implements IGraph {
|
||||||
|
private _cfg: GraphOptions
|
||||||
|
constructor(cfg: GraphOptions) {
|
||||||
|
super()
|
||||||
|
this._cfg = cfg
|
||||||
|
}
|
||||||
|
public get(key: string) {
|
||||||
|
return this._cfg[key]
|
||||||
|
}
|
||||||
|
}
|
0
src/graph/tree-graph.ts
Normal file
0
src/graph/tree-graph.ts
Normal file
@ -1,9 +1,19 @@
|
|||||||
|
|
||||||
import GraphEvent from '@antv/g-base/lib/event/graph-event';
|
import GraphEvent from '@antv/g-base/lib/event/graph-event';
|
||||||
import { IG6GraphEvent } from "../../types";
|
import { DefaultBehaviorType, G6Event, IG6GraphEvent } from '@g6/types';
|
||||||
|
import { IGraph } from './graph';
|
||||||
import { IItem } from './item';
|
import { IItem } from './item';
|
||||||
|
|
||||||
|
export interface IBehavior {
|
||||||
|
constructor: (cfg?: object) => void;
|
||||||
|
getEvents: () => { [key in G6Event]?: string };
|
||||||
|
shouldBegin: () => boolean;
|
||||||
|
shouldUpdate: () => boolean;
|
||||||
|
shouldEnd: () => boolean;
|
||||||
|
bind: (graph: IGraph) => void;
|
||||||
|
unbind: (graph: IGraph) => void;
|
||||||
|
[key: string]: (...args: DefaultBehaviorType[]) => unknown;
|
||||||
|
}
|
||||||
|
|
||||||
export class G6GraphEvent extends GraphEvent implements IG6GraphEvent {
|
export class G6GraphEvent extends GraphEvent implements IG6GraphEvent {
|
||||||
public item: IItem
|
public item: IItem
|
||||||
|
@ -1,7 +1,147 @@
|
|||||||
import { G6Event } from '../../types'
|
import EventEmitter from '@antv/event-emitter';
|
||||||
|
import { Easeing, ModelStyle, ShapeStyle } from '@g6/types'
|
||||||
|
|
||||||
export interface IGraph {
|
export interface IModeOption {
|
||||||
on: (event: G6Event, handler: () => void) => void;
|
type: string;
|
||||||
off: (event: G6Event, handler: () => void) => void;
|
}
|
||||||
|
|
||||||
|
export type IModeType = string | IModeOption
|
||||||
|
|
||||||
|
export interface IMode {
|
||||||
|
default: IModeType[]
|
||||||
|
[key: string]: IModeType[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ILayoutOptions {
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GraphOptions {
|
||||||
|
/**
|
||||||
|
* 图的 DOM 容器,可以传入该 DOM 的 id 或者直接传入容器的 HTML 节点对象
|
||||||
|
*/
|
||||||
|
container: string | HTMLElement;
|
||||||
|
/**
|
||||||
|
* 指定画布宽度,单位为 'px'
|
||||||
|
*/
|
||||||
|
width: number;
|
||||||
|
/**
|
||||||
|
* 指定画布高度,单位为 'px'
|
||||||
|
*/
|
||||||
|
height: number;
|
||||||
|
/**
|
||||||
|
* 渲染引擎,支持canvas和svg。
|
||||||
|
*/
|
||||||
|
renderer?: 'canvas' | 'svg';
|
||||||
|
|
||||||
|
fitView?: boolean;
|
||||||
|
|
||||||
|
layout?: ILayoutOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图适应画布时,指定四周的留白。
|
||||||
|
* 可以是一个值, 例如:fitViewPadding: 20
|
||||||
|
* 也可以是一个数组,例如:fitViewPadding: [20, 40, 50,20]
|
||||||
|
* 当指定一个值时,四边的边距都相等,当指定数组时,数组内数值依次对应 上,右,下,左四边的边距。
|
||||||
|
*/
|
||||||
|
fitViewPadding?: number[] | number;
|
||||||
|
/**
|
||||||
|
* 各种元素是否在一个分组内,决定节点和边的层级问题,默认情况下所有的节点在一个分组中,所有的边在一个分组中,当这个参数为 false 时,节点和边的层级根据生成的顺序确定。
|
||||||
|
* 默认值:true
|
||||||
|
*/
|
||||||
|
groupByTypes?: boolean;
|
||||||
|
|
||||||
|
groupStyle?: {
|
||||||
|
style: {
|
||||||
|
[key: string]: ShapeStyle
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当图中元素更新,或视口变换时,是否自动重绘。建议在批量操作节点时关闭,以提高性能,完成批量操作后再打开,参见后面的 setAutoPaint() 方法。
|
||||||
|
* 默认值:true
|
||||||
|
*/
|
||||||
|
autoPaint?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置画布的模式。详情可见G6中的Mode文档。
|
||||||
|
*/
|
||||||
|
modes?: IMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认状态下节点的配置,比如 shape, size, color。会被写入的 data 覆盖。
|
||||||
|
*/
|
||||||
|
defaultNode?: {
|
||||||
|
shape?: string,
|
||||||
|
size?: string,
|
||||||
|
color?: string,
|
||||||
|
} & ModelStyle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认状态下边的配置,比如 shape, size, color。会被写入的 data 覆盖。
|
||||||
|
*/
|
||||||
|
defaultEdge?: {
|
||||||
|
shape?: string,
|
||||||
|
size?: string,
|
||||||
|
color?: string,
|
||||||
|
} & ModelStyle;
|
||||||
|
|
||||||
|
nodeStateStyles?: ModelStyle;
|
||||||
|
|
||||||
|
edgeStateStyles?: ModelStyle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向 graph 注册插件。插件机制请见:plugin
|
||||||
|
*/
|
||||||
|
plugins?: any[];
|
||||||
|
/**
|
||||||
|
* 是否启用全局动画。
|
||||||
|
*/
|
||||||
|
animate?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动画配置项,仅在animate为true时有效。
|
||||||
|
*/
|
||||||
|
animateCfg?: {
|
||||||
|
/**
|
||||||
|
* 回调函数,用于自定义节点运动路径。
|
||||||
|
*/
|
||||||
|
onFrame?: () => unknown;
|
||||||
|
/**
|
||||||
|
* 动画时长,单位为毫秒。
|
||||||
|
*/
|
||||||
|
duration?: number;
|
||||||
|
/**
|
||||||
|
* 动画动效。
|
||||||
|
* 默认值:easeLinear
|
||||||
|
*/
|
||||||
|
easing?: Easeing;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 最小缩放比例
|
||||||
|
* 默认值 0.2
|
||||||
|
*/
|
||||||
|
minZoom?: number;
|
||||||
|
/**
|
||||||
|
* 最大缩放比例
|
||||||
|
* 默认值 10
|
||||||
|
*/
|
||||||
|
maxZoom?: number;
|
||||||
|
/**
|
||||||
|
* 像素比率
|
||||||
|
* 默认值 1.0
|
||||||
|
*/
|
||||||
|
pixelRatio?: number;
|
||||||
|
|
||||||
|
groupType?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edge 是否连接到节点中间
|
||||||
|
*/
|
||||||
|
linkCenter?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGraph extends EventEmitter {
|
||||||
|
get<T>(key: string): T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import Group from "@antv/g-canvas/lib/group";
|
import Group from "@antv/g-canvas/lib/group";
|
||||||
import ShapeBase from "@antv/g-canvas/lib/shape/base";
|
import { IBBox, IPoint, IShapeBase, ModelConfig, NodeConfig, ShapeStyle } from '@g6/types'
|
||||||
import { BBox } from "@antv/g-canvas/lib/types";
|
|
||||||
import { IModelConfig, INodeConfig, IPoint, IShapeStyle } from '../../types'
|
|
||||||
|
|
||||||
// item 的配置项
|
// item 的配置项
|
||||||
interface IItemConfig {
|
export type IItemConfig = Partial<{
|
||||||
/**
|
/**
|
||||||
* id
|
* id
|
||||||
*/
|
*/
|
||||||
@ -18,7 +16,7 @@ interface IItemConfig {
|
|||||||
/**
|
/**
|
||||||
* data model
|
* data model
|
||||||
*/
|
*/
|
||||||
model: IModelConfig;
|
model: ModelConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* G Group
|
* G Group
|
||||||
@ -46,23 +44,23 @@ interface IItemConfig {
|
|||||||
/**
|
/**
|
||||||
* key shape to calculate item's bbox
|
* key shape to calculate item's bbox
|
||||||
*/
|
*/
|
||||||
keyShape: ShapeBase,
|
keyShape: IShapeBase,
|
||||||
/**
|
/**
|
||||||
* item's states, such as selected or active
|
* item's states, such as selected or active
|
||||||
* @type Array
|
* @type Array
|
||||||
*/
|
*/
|
||||||
states: string[];
|
states: string[];
|
||||||
|
|
||||||
}
|
}>
|
||||||
|
|
||||||
export interface IItem {
|
export interface IItem {
|
||||||
_cfg: IItemConfig;
|
_cfg: IItemConfig;
|
||||||
|
|
||||||
|
destroyed: boolean;
|
||||||
|
|
||||||
isItem(): boolean;
|
isItem(): boolean;
|
||||||
|
|
||||||
getDefaultCfg(): IItemConfig;
|
getKeyShapeStyle(): ShapeStyle;
|
||||||
|
|
||||||
getKeyShapeStyle(): IShapeStyle;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前元素的所有状态
|
* 获取当前元素的所有状态
|
||||||
@ -77,11 +75,11 @@ export interface IItem {
|
|||||||
*/
|
*/
|
||||||
hasState(state: string): boolean;
|
hasState(state: string): boolean;
|
||||||
|
|
||||||
getStateStyle(state: string): IShapeStyle;
|
getStateStyle(state: string): ShapeStyle;
|
||||||
|
|
||||||
getOriginStyle(): IShapeStyle;
|
getOriginStyle(): ShapeStyle;
|
||||||
|
|
||||||
getCurrentStatesStyle(): IShapeStyle;
|
getCurrentStatesStyle(): ShapeStyle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更改元素状态, visible 不属于这个范畴
|
* 更改元素状态, visible 不属于这个范畴
|
||||||
@ -103,13 +101,13 @@ export interface IItem {
|
|||||||
* 节点的关键形状,用于计算节点大小,连线截距等
|
* 节点的关键形状,用于计算节点大小,连线截距等
|
||||||
* @return {G.Shape} 关键形状
|
* @return {G.Shape} 关键形状
|
||||||
*/
|
*/
|
||||||
getKeyShape(): ShapeBase;
|
getKeyShape(): IShapeBase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 节点数据模型
|
* 节点数据模型
|
||||||
* @return {Object} 数据模型
|
* @return {Object} 数据模型
|
||||||
*/
|
*/
|
||||||
getModel(): IModelConfig;
|
getModel(): ModelConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 节点类型
|
* 节点类型
|
||||||
@ -117,7 +115,7 @@ export interface IItem {
|
|||||||
*/
|
*/
|
||||||
getType(): string;
|
getType(): string;
|
||||||
|
|
||||||
getShapeCfg(model: IModelConfig): IModelConfig;
|
getShapeCfg(model: ModelConfig): ModelConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 刷新一般用于处理几种情况
|
* 刷新一般用于处理几种情况
|
||||||
@ -133,7 +131,7 @@ export interface IItem {
|
|||||||
* @internal 仅提供给 Graph 使用,外部直接调用 graph.update 接口
|
* @internal 仅提供给 Graph 使用,外部直接调用 graph.update 接口
|
||||||
* @param {Object} cfg 配置项,可以是增量信息
|
* @param {Object} cfg 配置项,可以是增量信息
|
||||||
*/
|
*/
|
||||||
update(cfg: IModelConfig): void;
|
update(cfg: ModelConfig): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新元素内容,样式
|
* 更新元素内容,样式
|
||||||
@ -144,12 +142,7 @@ export interface IItem {
|
|||||||
* 更新位置,避免整体重绘
|
* 更新位置,避免整体重绘
|
||||||
* @param {object} cfg 待更新数据
|
* @param {object} cfg 待更新数据
|
||||||
*/
|
*/
|
||||||
updatePosition(cfg: INodeConfig): void;
|
updatePosition(cfg: NodeConfig): void;
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新/刷新等操作后,清除 cache
|
|
||||||
*/
|
|
||||||
clearCache(): void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 绘制元素
|
* 绘制元素
|
||||||
@ -159,7 +152,7 @@ export interface IItem {
|
|||||||
/**
|
/**
|
||||||
* 获取元素的包围盒
|
* 获取元素的包围盒
|
||||||
*/
|
*/
|
||||||
getBBox(): BBox;
|
getBBox(): IBBox;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将元素放到最前面
|
* 将元素放到最前面
|
||||||
@ -195,6 +188,8 @@ export interface IItem {
|
|||||||
|
|
||||||
isVisible(): boolean;
|
isVisible(): boolean;
|
||||||
|
|
||||||
|
isOnlyMove(cfg: ModelConfig): boolean;
|
||||||
|
|
||||||
get<T>(key: string): T;
|
get<T>(key: string): T;
|
||||||
set<T>(key: string, value: T): void;
|
set<T>(key: string, value: T): void;
|
||||||
}
|
}
|
||||||
@ -252,11 +247,15 @@ export interface INode extends IItem {
|
|||||||
*/
|
*/
|
||||||
removeEdge(edge: IEdge): void;
|
removeEdge(edge: IEdge): void;
|
||||||
|
|
||||||
clearCache(): void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取锚点的定义
|
* 获取锚点的定义
|
||||||
* @return {array} anchorPoints, {x,y,...cfg}
|
* @return {array} anchorPoints, {x,y,...cfg}
|
||||||
*/
|
*/
|
||||||
getAnchorPoints(): IPoint[];
|
getAnchorPoints(): IPoint[];
|
||||||
|
|
||||||
|
hasLocked(): boolean;
|
||||||
|
|
||||||
|
lock(): void;
|
||||||
|
|
||||||
|
unlock(): void;
|
||||||
}
|
}
|
217
src/item/edge.ts
Normal file
217
src/item/edge.ts
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
import isNil from '@antv/util/lib/is-nil';
|
||||||
|
import isPlainObject from '@antv/util/lib/is-plain-object'
|
||||||
|
import { IEdge, INode } from "@g6/interface/item";
|
||||||
|
import { EdgeConfig, IPoint, NodeConfig, SourceTarget } from '@g6/types';
|
||||||
|
import Item from "./item";
|
||||||
|
|
||||||
|
const END_MAP = { source: 'start', target: 'end' };
|
||||||
|
const ITEM_NAME_SUFFIX = 'Node'; // 端点的后缀,如 sourceNode, targetNode
|
||||||
|
const POINT_NAME_SUFFIX = 'Point'; // 起点或者结束点的后缀,如 startPoint, endPoint
|
||||||
|
const ANCHOR_NAME_SUFFIX = 'Anchor';
|
||||||
|
|
||||||
|
export default class Edge extends Item implements IEdge {
|
||||||
|
protected getDefaultCfg() {
|
||||||
|
return {
|
||||||
|
type: 'edge',
|
||||||
|
sourceNode: null,
|
||||||
|
targetNode: null,
|
||||||
|
startPoint: null,
|
||||||
|
endPoint: null,
|
||||||
|
linkCenter: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setEnd(name: string, value: INode) {
|
||||||
|
const pointName = END_MAP[name] + POINT_NAME_SUFFIX;
|
||||||
|
const itemName = name + ITEM_NAME_SUFFIX;
|
||||||
|
const preItem = this.get(itemName);
|
||||||
|
if(preItem) {
|
||||||
|
// 如果之前存在节点,则移除掉边
|
||||||
|
preItem.removeEdge(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPlainObject(value)) { // 如果设置成具体的点,则清理节点
|
||||||
|
this.set(pointName, value);
|
||||||
|
this.set(itemName, null);
|
||||||
|
} else {
|
||||||
|
value!.addEdge(this);
|
||||||
|
this.set(itemName, value);
|
||||||
|
this.set(pointName, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取连接点的坐标
|
||||||
|
* @param name source | target
|
||||||
|
* @param model 边的数据模型
|
||||||
|
* @param controlPoints 控制点
|
||||||
|
*/
|
||||||
|
private getLinkPoint(name: SourceTarget, model: EdgeConfig, controlPoints: IPoint[]): IPoint {
|
||||||
|
const pointName = END_MAP[name] + POINT_NAME_SUFFIX;
|
||||||
|
const itemName = name + ITEM_NAME_SUFFIX;
|
||||||
|
let point = this.get(pointName);
|
||||||
|
if (!point) {
|
||||||
|
const item = this.get(itemName);
|
||||||
|
const anchorName = name + ANCHOR_NAME_SUFFIX;
|
||||||
|
const prePoint = this.getPrePoint(name, controlPoints);
|
||||||
|
const anchorIndex = model[anchorName];
|
||||||
|
if (isNil(anchorIndex)) { // 如果有锚点,则使用锚点索引获取连接点
|
||||||
|
point = item.getLinkPointByAnchor(anchorIndex);
|
||||||
|
}
|
||||||
|
// 如果锚点没有对应的点或者没有锚点,则直接计算连接点
|
||||||
|
point = point || item.getLinkPoint(prePoint);
|
||||||
|
if (isNil(point.index)) {
|
||||||
|
this.set(name + 'AnchorIndex', point.index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return point;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取同端点进行连接的点,计算交汇点
|
||||||
|
* @param name
|
||||||
|
* @param controlPoints
|
||||||
|
*/
|
||||||
|
private getPrePoint(name: SourceTarget, controlPoints: IPoint[]): NodeConfig | IPoint {
|
||||||
|
if (controlPoints && controlPoints.length) {
|
||||||
|
const index = name === 'source' ? 0 : controlPoints.length - 1;
|
||||||
|
return controlPoints[index];
|
||||||
|
}
|
||||||
|
const oppositeName = name === 'source' ? 'target' : 'source'; // 取另一个节点的位置
|
||||||
|
return this.getEndPoint(oppositeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取端点的位置
|
||||||
|
* @param name
|
||||||
|
*/
|
||||||
|
private getEndPoint(name: string): NodeConfig | IPoint {
|
||||||
|
const itemName = name + ITEM_NAME_SUFFIX;
|
||||||
|
const pointName = END_MAP[name] + POINT_NAME_SUFFIX;
|
||||||
|
const item = this.get(itemName);
|
||||||
|
// 如果有端点,直接使用 model
|
||||||
|
if (item) {
|
||||||
|
return item.get('model');
|
||||||
|
} // 否则直接使用点
|
||||||
|
return this.get(pointName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过端点的中心获取控制点
|
||||||
|
* @param model
|
||||||
|
*/
|
||||||
|
private getControlPointsByCenter(model) {
|
||||||
|
const sourcePoint = this.getEndPoint('source');
|
||||||
|
const targetPoint = this.getEndPoint('target');
|
||||||
|
const shapeFactory = this.get('shapeFactory');
|
||||||
|
return shapeFactory.getControlPoints(model.shape, {
|
||||||
|
startPoint: sourcePoint,
|
||||||
|
endPoint: targetPoint
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getEndCenter(name: string): IPoint {
|
||||||
|
const itemName = name + ITEM_NAME_SUFFIX;
|
||||||
|
const pointName = END_MAP[name] + POINT_NAME_SUFFIX;
|
||||||
|
const item = this.get(itemName);
|
||||||
|
// 如果有端点,直接使用 model
|
||||||
|
if (item) {
|
||||||
|
const bbox = item.getBBox();
|
||||||
|
return {
|
||||||
|
x: bbox.centerX,
|
||||||
|
y: bbox.centerY
|
||||||
|
};
|
||||||
|
} // 否则直接使用点
|
||||||
|
return this.get(pointName);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected init() {
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
public getShapeCfg(model: EdgeConfig): EdgeConfig {
|
||||||
|
const self = this;
|
||||||
|
const linkCenter: boolean = self.get('linkCenter'); // 如果连接到中心,忽视锚点、忽视控制点
|
||||||
|
const cfg: any = super.getShapeCfg(model);
|
||||||
|
if (linkCenter) {
|
||||||
|
cfg.startPoint = self.getEndCenter('source');
|
||||||
|
cfg.endPoint = self.getEndCenter('target');
|
||||||
|
} else {
|
||||||
|
const controlPoints = cfg.controlPoints || self.getControlPointsByCenter(cfg);
|
||||||
|
cfg.startPoint = self.getLinkPoint('source', model, controlPoints);
|
||||||
|
cfg.endPoint = self.getLinkPoint('target', model, controlPoints);
|
||||||
|
}
|
||||||
|
cfg.sourceNode = self.get('sourceNode');
|
||||||
|
cfg.targetNode = self.get('targetNode');
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取边的数据模型
|
||||||
|
*/
|
||||||
|
public getModel(): EdgeConfig {
|
||||||
|
const model: EdgeConfig = this.get('model');
|
||||||
|
const out = Object.assign({}, model);
|
||||||
|
const sourceItem = this.get('source' + ITEM_NAME_SUFFIX);
|
||||||
|
const targetItem = this.get('target' + ITEM_NAME_SUFFIX);
|
||||||
|
if (sourceItem) {
|
||||||
|
out.source = sourceItem.get('id');
|
||||||
|
delete out['source' + ITEM_NAME_SUFFIX];
|
||||||
|
} else {
|
||||||
|
out.source = this.get('start' + POINT_NAME_SUFFIX);
|
||||||
|
}
|
||||||
|
if (targetItem) {
|
||||||
|
out.target = targetItem.get('id');
|
||||||
|
delete out['target' + ITEM_NAME_SUFFIX];
|
||||||
|
} else {
|
||||||
|
out.target = this.get('end' + POINT_NAME_SUFFIX);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setSource(source: INode) {
|
||||||
|
this.setEnd('source', source)
|
||||||
|
this.set('source', source)
|
||||||
|
}
|
||||||
|
|
||||||
|
public setTarget(target: INode) {
|
||||||
|
this.setEnd('target', target)
|
||||||
|
this.set('target', target)
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSource(): INode {
|
||||||
|
return this.get('source')
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTarget(): INode {
|
||||||
|
return this.get('target')
|
||||||
|
}
|
||||||
|
|
||||||
|
public updatePosition() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 边不需要重计算容器位置,直接重新计算 path 位置
|
||||||
|
* @param {object} cfg 待更新数据
|
||||||
|
*/
|
||||||
|
public update(cfg: EdgeConfig) {
|
||||||
|
const model: EdgeConfig = this.get('model');
|
||||||
|
Object.assign(model, cfg);
|
||||||
|
this.updateShape();
|
||||||
|
this.afterUpdate();
|
||||||
|
this.clearCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy() {
|
||||||
|
const sourceItem: INode = this.get('source' + ITEM_NAME_SUFFIX);
|
||||||
|
const targetItem: INode = this.get('target' + ITEM_NAME_SUFFIX);
|
||||||
|
if(sourceItem && !sourceItem.destroyed) {
|
||||||
|
sourceItem.removeEdge(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(targetItem && !targetItem.destroyed) {
|
||||||
|
targetItem.removeEdge(this);
|
||||||
|
}
|
||||||
|
super.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
568
src/item/item.ts
Normal file
568
src/item/item.ts
Normal file
@ -0,0 +1,568 @@
|
|||||||
|
import Group from '@antv/g-canvas/lib/group';
|
||||||
|
import each from '@antv/util/lib/each'
|
||||||
|
import isNil from '@antv/util/lib/is-nil';
|
||||||
|
import isPlainObject from '@antv/util/lib/is-plain-object'
|
||||||
|
import isString from '@antv/util/lib/is-string'
|
||||||
|
import uniqueId from '@antv/util/lib/unique-id'
|
||||||
|
import { IItem, IItemConfig } from "@g6/interface/item";
|
||||||
|
import { IBBox, IPoint, IShapeBase, ModelConfig, ModelStyle } from '@g6/types';
|
||||||
|
import { getBBox } from '@g6/util/graphic';
|
||||||
|
import { translate } from '@g6/util/math';
|
||||||
|
|
||||||
|
const CACHE_BBOX = 'bboxCache';
|
||||||
|
|
||||||
|
const RESERVED_STYLES = [ 'fillStyle', 'strokeStyle',
|
||||||
|
'path', 'points', 'img', 'symbol' ];
|
||||||
|
|
||||||
|
export default class Item implements IItem {
|
||||||
|
public _cfg: IItemConfig = {}
|
||||||
|
private defaultCfg: IItemConfig = {
|
||||||
|
/**
|
||||||
|
* id
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
id: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类型
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
type: 'item',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* data model
|
||||||
|
* @type {object}
|
||||||
|
*/
|
||||||
|
model: {} as ModelConfig,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* g group
|
||||||
|
* @type {G.Group}
|
||||||
|
*/
|
||||||
|
group: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* is open animate
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
animate: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* visible - not group visible
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
visible: true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* locked - lock node
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
locked: false,
|
||||||
|
/**
|
||||||
|
* capture event
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
event: true,
|
||||||
|
/**
|
||||||
|
* key shape to calculate item's bbox
|
||||||
|
* @type object
|
||||||
|
*/
|
||||||
|
keyShape: null,
|
||||||
|
/**
|
||||||
|
* item's states, such as selected or active
|
||||||
|
* @type Array
|
||||||
|
*/
|
||||||
|
states: []
|
||||||
|
};
|
||||||
|
|
||||||
|
public destroyed: boolean = false
|
||||||
|
|
||||||
|
constructor(cfg: IItemConfig) {
|
||||||
|
this._cfg = Object.assign(this.defaultCfg, this.getDefaultCfg(), cfg)
|
||||||
|
const group = cfg.group
|
||||||
|
group.set('item', this)
|
||||||
|
|
||||||
|
let id = this.get('model').id
|
||||||
|
|
||||||
|
if(!id) {
|
||||||
|
id = uniqueId(this.get('type'))
|
||||||
|
}
|
||||||
|
|
||||||
|
this.set('id', id)
|
||||||
|
group.set('id', id)
|
||||||
|
|
||||||
|
this.init()
|
||||||
|
this.draw()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 keyshape 计算包围盒
|
||||||
|
*/
|
||||||
|
private calculateBBox(): IBBox {
|
||||||
|
const keyShape: IShapeBase = this.get('keyShape');
|
||||||
|
const group: Group = this.get('group');
|
||||||
|
// 因为 group 可能会移动,所以必须通过父元素计算才能计算出正确的包围盒
|
||||||
|
const bbox = getBBox(keyShape, group);
|
||||||
|
bbox.x = bbox.minX;
|
||||||
|
bbox.y = bbox.minY;
|
||||||
|
bbox.width = bbox.maxX - bbox.minX;
|
||||||
|
bbox.height = bbox.maxY - bbox.minY;
|
||||||
|
bbox.centerX = (bbox.minX + bbox.maxX) / 2;
|
||||||
|
bbox.centerY = (bbox.minY + bbox.maxY) / 2;
|
||||||
|
return bbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* draw shape
|
||||||
|
*/
|
||||||
|
private drawInner() {
|
||||||
|
const self = this;
|
||||||
|
const shapeFactory = self.get('shapeFactory');
|
||||||
|
const group: Group = self.get('group');
|
||||||
|
const model: ModelConfig = self.get('model');
|
||||||
|
group.clear();
|
||||||
|
|
||||||
|
if (!shapeFactory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.updatePosition(model);
|
||||||
|
const cfg = self.getShapeCfg(model); // 可能会附加额外信息
|
||||||
|
const shapeType: string = cfg.shape;
|
||||||
|
const keyShape: IShapeBase = shapeFactory.draw(shapeType, cfg, group);
|
||||||
|
if (keyShape) {
|
||||||
|
keyShape.isKeyShape = true;
|
||||||
|
self.set('keyShape', keyShape);
|
||||||
|
self.set('originStyle', this.getKeyShapeStyle());
|
||||||
|
}
|
||||||
|
// 防止由于用户外部修改 model 中的 shape 导致 shape 不更新
|
||||||
|
this.set('currentShape', shapeType);
|
||||||
|
this.resetStates(shapeFactory, shapeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* reset shape states
|
||||||
|
* @param shapeFactory
|
||||||
|
* @param shapeType
|
||||||
|
*/
|
||||||
|
private resetStates(shapeFactory, shapeType: string) {
|
||||||
|
const self = this;
|
||||||
|
const states: string[] = self.get('states');
|
||||||
|
each(states, state => {
|
||||||
|
shapeFactory.setState(shapeType, state, true, self);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected init() {
|
||||||
|
// TODO 实例化工厂方法,需要等 Shape 重构完成
|
||||||
|
// const shapeFactory = Shape.getFactory(this.get('type'));
|
||||||
|
// this.set('shapeFactory', shapeFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取属性
|
||||||
|
* @internal 仅内部类使用
|
||||||
|
* @param {String} key 属性名
|
||||||
|
* @return {object | string | number} 属性值
|
||||||
|
*/
|
||||||
|
public get(key: string) {
|
||||||
|
return this._cfg[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置属性
|
||||||
|
* @internal 仅内部类使用
|
||||||
|
* @param {String|Object} key 属性名,也可以是对象
|
||||||
|
* @param {object | string | number} val 属性值
|
||||||
|
*/
|
||||||
|
public set(key: string, val): void {
|
||||||
|
if(isPlainObject(key)) {
|
||||||
|
this._cfg = Object.assign({}, this._cfg, key)
|
||||||
|
} else {
|
||||||
|
this._cfg[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getDefaultCfg() {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新/刷新等操作后,清除 cache
|
||||||
|
*/
|
||||||
|
protected clearCache() {
|
||||||
|
this.set(CACHE_BBOX, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染前的逻辑,提供给子类复写
|
||||||
|
*/
|
||||||
|
protected beforeDraw() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染后的逻辑,提供给子类复写
|
||||||
|
*/
|
||||||
|
protected afterDraw() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新后做一些工作
|
||||||
|
*/
|
||||||
|
protected afterUpdate() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* draw shape
|
||||||
|
*/
|
||||||
|
public draw() {
|
||||||
|
this.beforeDraw()
|
||||||
|
this.drawInner()
|
||||||
|
this.afterDraw()
|
||||||
|
}
|
||||||
|
|
||||||
|
public getKeyShapeStyle(): ModelStyle {
|
||||||
|
const keyShape = this.getKeyShape();
|
||||||
|
if (keyShape) {
|
||||||
|
const styles: ModelStyle = {};
|
||||||
|
each(keyShape.attr(), (val, key) => {
|
||||||
|
if (RESERVED_STYLES.indexOf(key) < 0) {
|
||||||
|
styles[key] = val;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return styles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO 确定还是否需要该方法
|
||||||
|
public getShapeCfg(model: ModelConfig): ModelConfig {
|
||||||
|
const styles = this.get('styles');
|
||||||
|
if (styles && styles.default) {
|
||||||
|
// merge graph的item样式与数据模型中的样式
|
||||||
|
const newModel = Object.assign({}, model);
|
||||||
|
newModel.style = Object.assign({}, styles.default, model.style);
|
||||||
|
return newModel;
|
||||||
|
}
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定状态的样式,去除了全局样式
|
||||||
|
* @param state 状态名称
|
||||||
|
*/
|
||||||
|
public getStateStyle(state: string) {
|
||||||
|
const type: string = this.getType()
|
||||||
|
const styles = this.get(`${type}StateStyles`);
|
||||||
|
const stateStyle = styles && styles[state];
|
||||||
|
return stateStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get keyshape style
|
||||||
|
*/
|
||||||
|
public getOriginStyle(): ModelStyle {
|
||||||
|
return this.get('originStyle');
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCurrentStatesStyle(): ModelStyle {
|
||||||
|
const self = this;
|
||||||
|
const originStyle = self.getOriginStyle();
|
||||||
|
each(self.getStates(), state => {
|
||||||
|
Object.assign(originStyle, self.getStateStyle(state));
|
||||||
|
});
|
||||||
|
return originStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更改元素状态, visible 不属于这个范畴
|
||||||
|
* @internal 仅提供内部类 graph 使用
|
||||||
|
* @param {String} state 状态名
|
||||||
|
* @param {Boolean} enable 节点状态值
|
||||||
|
*/
|
||||||
|
public setState(state: string, enable: boolean) {
|
||||||
|
const states: string[] = this.get('states');
|
||||||
|
const shapeFactory = this.get('shapeFactory');
|
||||||
|
const index = states.indexOf(state);
|
||||||
|
if (enable) {
|
||||||
|
if (index > -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
states.push(state);
|
||||||
|
} else if (index > -1) {
|
||||||
|
states.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shapeFactory) {
|
||||||
|
const model: ModelConfig = this.get('model');
|
||||||
|
shapeFactory.setState(model.shape, state, enable, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
public clearStates(states: string[]) {
|
||||||
|
const self = this;
|
||||||
|
const originStates = self.getStates();
|
||||||
|
const shapeFactory = self.get('shapeFactory');
|
||||||
|
const shape: string = self.get('model').shape;
|
||||||
|
if (!states) {
|
||||||
|
self.set('states', []);
|
||||||
|
shapeFactory.setState(shape, originStates[0], false, self);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isString(states)) {
|
||||||
|
states = [ states ];
|
||||||
|
}
|
||||||
|
const newStates = originStates.filter(state => {
|
||||||
|
shapeFactory.setState(shape, state, false, self);
|
||||||
|
if (states.indexOf(state) >= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
self.set('states', newStates);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点的图形容器
|
||||||
|
* @return {G.Group} 图形容器
|
||||||
|
*/
|
||||||
|
public getContainer(): Group {
|
||||||
|
return this.get('group');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点的关键形状,用于计算节点大小,连线截距等
|
||||||
|
* @return {G.Shape} 关键形状
|
||||||
|
*/
|
||||||
|
public getKeyShape(): IShapeBase {
|
||||||
|
return this.get('keyShape');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点数据模型
|
||||||
|
* @return {Object} 数据模型
|
||||||
|
*/
|
||||||
|
public getModel(): ModelConfig {
|
||||||
|
return this.get('model');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点类型
|
||||||
|
* @return {string} 节点的类型
|
||||||
|
*/
|
||||||
|
public getType(): string {
|
||||||
|
return this.get('type');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否是 Item 对象,悬空边情况下进行判定
|
||||||
|
*/
|
||||||
|
public isItem(): boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前元素的所有状态
|
||||||
|
* @return {Array} 元素的所有状态
|
||||||
|
*/
|
||||||
|
public getStates(): string[] {
|
||||||
|
return this.get('states');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前元素是否处于某状态
|
||||||
|
* @param {String} state 状态名
|
||||||
|
* @return {Boolean} 是否处于某状态
|
||||||
|
*/
|
||||||
|
public hasState(state: string): boolean {
|
||||||
|
const states = this.getStates()
|
||||||
|
return states.indexOf(state) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新一般用于处理几种情况
|
||||||
|
* 1. item model 在外部被改变
|
||||||
|
* 2. 边的节点位置发生改变,需要重新计算边
|
||||||
|
*
|
||||||
|
* 因为数据从外部被修改无法判断一些属性是否被修改,直接走位置和 shape 的更新
|
||||||
|
*/
|
||||||
|
public refresh() {
|
||||||
|
const model: ModelConfig = this.get('model');
|
||||||
|
// 更新元素位置
|
||||||
|
this.updatePosition(model);
|
||||||
|
// 更新元素内容,样式
|
||||||
|
this.updateShape();
|
||||||
|
// 做一些更新之后的操作
|
||||||
|
this.afterUpdate();
|
||||||
|
// 清除缓存
|
||||||
|
this.clearCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
public isOnlyMove(cfg?: ModelConfig): boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将更新应用到 model 上,刷新属性
|
||||||
|
* @internal 仅提供给 Graph 使用,外部直接调用 graph.update 接口
|
||||||
|
* @param {Object} cfg 配置项,可以是增量信息
|
||||||
|
*/
|
||||||
|
public update(cfg: ModelConfig) {
|
||||||
|
const model: ModelConfig = this.get('model');
|
||||||
|
const originPosition: IPoint = { x: model.x, y: model.y };
|
||||||
|
|
||||||
|
// 直接将更新合到原数据模型上,可以保证用户在外部修改源数据然后刷新时的样式符合期待。
|
||||||
|
Object.assign(model, cfg);
|
||||||
|
|
||||||
|
// isOnlyMove 仅用于node
|
||||||
|
const onlyMove = this.isOnlyMove(cfg);
|
||||||
|
// 仅仅移动位置时,既不更新,也不重绘
|
||||||
|
if (onlyMove) {
|
||||||
|
this.updatePosition(model);
|
||||||
|
} else {
|
||||||
|
// 如果 x,y 有变化,先重置位置
|
||||||
|
if (originPosition.x !== model.x || originPosition.y !== model.y) {
|
||||||
|
this.updatePosition(model);
|
||||||
|
}
|
||||||
|
this.updateShape();
|
||||||
|
}
|
||||||
|
this.afterUpdate();
|
||||||
|
this.clearCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新元素内容,样式
|
||||||
|
*/
|
||||||
|
public updateShape() {
|
||||||
|
const shapeFactory = this.get('shapeFactory');
|
||||||
|
const model = this.get('model');
|
||||||
|
const shape = model.shape;
|
||||||
|
// 判定是否允许更新
|
||||||
|
// 1. 注册的节点允许更新
|
||||||
|
// 2. 更新后的 shape 等于原先的 shape
|
||||||
|
if (shapeFactory.shouldUpdate(shape) && shape === this.get('currentShape')) {
|
||||||
|
const updateCfg = this.getShapeCfg(model);
|
||||||
|
shapeFactory.update(shape, updateCfg, this);
|
||||||
|
} else {
|
||||||
|
// 如果不满足上面两种状态,重新绘制
|
||||||
|
this.draw();
|
||||||
|
}
|
||||||
|
this.set('originStyle', this.getKeyShapeStyle());
|
||||||
|
// 更新后重置节点状态
|
||||||
|
this.resetStates(shapeFactory, shape);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新位置,避免整体重绘
|
||||||
|
* @param {object} cfg 待更新数据
|
||||||
|
*/
|
||||||
|
public updatePosition(cfg: ModelConfig) {
|
||||||
|
const model: ModelConfig = this.get('model');
|
||||||
|
|
||||||
|
const x = isNil(cfg.x) ? model.x : cfg.x;
|
||||||
|
const y = isNil(cfg.y) ? model.y : cfg.y;
|
||||||
|
|
||||||
|
const group: Group = this.get('group');
|
||||||
|
|
||||||
|
if (isNil(x) || isNil(y)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
group.resetMatrix();
|
||||||
|
// G 4.0 element 中移除了矩阵相关方法,详见https://www.yuque.com/antv/blog/kxzk9g#4rMMV
|
||||||
|
translate(group, { x, y });
|
||||||
|
model.x = x;
|
||||||
|
model.y = y;
|
||||||
|
this.clearCache(); // 位置更新后需要清除缓存
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取元素的包围盒
|
||||||
|
* @return {Object} 包含 x,y,width,height, centerX, centerY
|
||||||
|
*/
|
||||||
|
public getBBox(): IBBox {
|
||||||
|
// 计算 bbox 开销有些大,缓存
|
||||||
|
let bbox: IBBox = this.get(CACHE_BBOX);
|
||||||
|
if (!bbox) {
|
||||||
|
bbox = this.calculateBBox();
|
||||||
|
this.set(CACHE_BBOX, bbox);
|
||||||
|
}
|
||||||
|
return bbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将元素放到最前面
|
||||||
|
*/
|
||||||
|
public toFront() {
|
||||||
|
this.get('group').toFront();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将元素放到最后面
|
||||||
|
*/
|
||||||
|
public toBack() {
|
||||||
|
this.get('group').toBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示元素
|
||||||
|
*/
|
||||||
|
public show() {
|
||||||
|
this.changeVisibility(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 隐藏元素
|
||||||
|
*/
|
||||||
|
public hide() {
|
||||||
|
this.changeVisibility(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更改是否显示
|
||||||
|
* @param {Boolean} visible 是否显示
|
||||||
|
*/
|
||||||
|
public changeVisibility(visible: boolean) {
|
||||||
|
const group: Group = this.get('group');
|
||||||
|
if (visible) {
|
||||||
|
group.show();
|
||||||
|
} else {
|
||||||
|
group.hide();
|
||||||
|
}
|
||||||
|
this.set('visible', visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 元素是否可见
|
||||||
|
*/
|
||||||
|
public isVisible(): boolean {
|
||||||
|
return this.get('visible');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否拾取及出发该元素的交互事件
|
||||||
|
* @param {Boolean} enable 标识位
|
||||||
|
*/
|
||||||
|
public enableCapture(enable: boolean) {
|
||||||
|
const group: Group = this.get('group');
|
||||||
|
if(group) {
|
||||||
|
group.attr('capture', enable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy() {
|
||||||
|
if(!this.destroyed) {
|
||||||
|
const animate = this.get('animate');
|
||||||
|
const group: Group = this.get('group');
|
||||||
|
if (animate) {
|
||||||
|
group.stopAnimate();
|
||||||
|
}
|
||||||
|
group.remove();
|
||||||
|
this._cfg = null;
|
||||||
|
this.destroyed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
205
src/item/node.ts
Normal file
205
src/item/node.ts
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
import each from '@antv/util/lib/each'
|
||||||
|
import isNil from '@antv/util/lib/is-nil';
|
||||||
|
import mix from '@antv/util/lib/mix'
|
||||||
|
import { IEdge, INode } from '@g6/interface/item';
|
||||||
|
import { IPoint, IShapeBase, NodeConfig } from '@g6/types';
|
||||||
|
import { distance, getCircleIntersectByPoint, getEllispeIntersectByPoint, getRectIntersectByPoint } from '@g6/util/math';
|
||||||
|
import Item from './item'
|
||||||
|
|
||||||
|
const CACHE_ANCHOR_POINTS = 'anchorPointsCache'
|
||||||
|
const CACHE_BBOX = 'bboxCache'
|
||||||
|
|
||||||
|
export default class Node extends Item implements INode {
|
||||||
|
private getNearestPoint(points: IPoint[], curPoint: IPoint): IPoint {
|
||||||
|
let index = 0;
|
||||||
|
let nearestPoint = points[0];
|
||||||
|
let minDistance = distance(points[0], curPoint);
|
||||||
|
for (let i = 0; i < points.length; i++) {
|
||||||
|
const point = points[i];
|
||||||
|
const dis = distance(point, curPoint);
|
||||||
|
if (dis < minDistance) {
|
||||||
|
nearestPoint = point;
|
||||||
|
minDistance = dis;
|
||||||
|
index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nearestPoint.anchorIndex = index;
|
||||||
|
return nearestPoint;
|
||||||
|
}
|
||||||
|
public getDefaultCfg() {
|
||||||
|
return {
|
||||||
|
type: 'node',
|
||||||
|
edges: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取从节点关联的所有边
|
||||||
|
*/
|
||||||
|
public getEdges(): IEdge[] {
|
||||||
|
return this.get('edges')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有的入边
|
||||||
|
*/
|
||||||
|
public getInEdges(): IEdge[] {
|
||||||
|
const self = this;
|
||||||
|
return this.get('edges').filter((edge: IEdge) => {
|
||||||
|
return edge.get('target') === self;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有的出边
|
||||||
|
*/
|
||||||
|
public getOutEdges(): IEdge[] {
|
||||||
|
const self = this;
|
||||||
|
return this.get('edges').filter((edge: IEdge) => {
|
||||||
|
return edge.get('source') === self;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据锚点的索引获取连接点
|
||||||
|
* @param {Number} index 索引
|
||||||
|
*/
|
||||||
|
public getLinkPointByAnchor(index): IPoint {
|
||||||
|
const anchorPoints = this.getAnchorPoints();
|
||||||
|
return anchorPoints[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取连接点
|
||||||
|
* @param point
|
||||||
|
*/
|
||||||
|
public getLinkPoint(point: IPoint): IPoint {
|
||||||
|
const keyShape: IShapeBase = this.get('keyShape');
|
||||||
|
const type: string = keyShape.get('type');
|
||||||
|
const bbox = this.getBBox();
|
||||||
|
const { centerX, centerY } = bbox;
|
||||||
|
const anchorPoints = this.getAnchorPoints();
|
||||||
|
let intersectPoint: IPoint;
|
||||||
|
switch (type) {
|
||||||
|
case 'circle':
|
||||||
|
intersectPoint = getCircleIntersectByPoint({
|
||||||
|
x: centerX,
|
||||||
|
y: centerY,
|
||||||
|
r: bbox.width / 2
|
||||||
|
}, point);
|
||||||
|
break;
|
||||||
|
case 'ellipse':
|
||||||
|
intersectPoint = getEllispeIntersectByPoint({
|
||||||
|
x: centerX,
|
||||||
|
y: centerY,
|
||||||
|
rx: bbox.width / 2,
|
||||||
|
ry: bbox.height / 2
|
||||||
|
}, point);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
intersectPoint = getRectIntersectByPoint(bbox, point);
|
||||||
|
}
|
||||||
|
let linkPoint = intersectPoint;
|
||||||
|
// 如果存在锚点,则使用交点计算最近的锚点
|
||||||
|
if (anchorPoints.length) {
|
||||||
|
if (!linkPoint) { // 如果计算不出交点
|
||||||
|
linkPoint = point;
|
||||||
|
}
|
||||||
|
linkPoint = this.getNearestPoint(anchorPoints, linkPoint);
|
||||||
|
}
|
||||||
|
if (!linkPoint) { // 如果最终依然没法找到锚点和连接点,直接返回中心点
|
||||||
|
linkPoint = { x: centerX, y: centerY };
|
||||||
|
}
|
||||||
|
return linkPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取锚点的定义
|
||||||
|
* @return {array} anchorPoints
|
||||||
|
*/
|
||||||
|
public getAnchorPoints(): IPoint[] {
|
||||||
|
let anchorPoints: IPoint[] = this.get(CACHE_ANCHOR_POINTS);
|
||||||
|
if (!anchorPoints) {
|
||||||
|
anchorPoints = [];
|
||||||
|
const shapeFactory = this.get('shapeFactory');
|
||||||
|
const bbox = this.getBBox();
|
||||||
|
const model: NodeConfig = this.get('model');
|
||||||
|
const shapeCfg = this.getShapeCfg(model);
|
||||||
|
const points = shapeFactory.getAnchorPoints(model.shape, shapeCfg) || [];
|
||||||
|
each(points, (pointArr, index) => {
|
||||||
|
const point = mix({
|
||||||
|
x: bbox.minX + pointArr[0] * bbox.width,
|
||||||
|
y: bbox.minY + pointArr[1] * bbox.height
|
||||||
|
}, pointArr[2], {
|
||||||
|
index
|
||||||
|
});
|
||||||
|
anchorPoints.push(point);
|
||||||
|
});
|
||||||
|
this.set(CACHE_ANCHOR_POINTS, anchorPoints);
|
||||||
|
}
|
||||||
|
return anchorPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add edge
|
||||||
|
* @param edge Edge instance
|
||||||
|
*/
|
||||||
|
public addEdge(edge: IEdge) {
|
||||||
|
this.get('edges').push(edge)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 锁定节点
|
||||||
|
*/
|
||||||
|
public lock() {
|
||||||
|
this.set('locked', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解锁锁定的节点
|
||||||
|
*/
|
||||||
|
public unlock() {
|
||||||
|
this.set('locked', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasLocked(): boolean {
|
||||||
|
return this.get('locked');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除边
|
||||||
|
* @param {Edge} edge 边
|
||||||
|
*/
|
||||||
|
public removeEdge(edge: IEdge) {
|
||||||
|
const edges = this.getEdges();
|
||||||
|
const index = edges.indexOf(edge);
|
||||||
|
if (index > -1) {
|
||||||
|
edges.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public clearCache() {
|
||||||
|
this.set(CACHE_BBOX, null); // 清理缓存的 bbox
|
||||||
|
this.set(CACHE_ANCHOR_POINTS, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否仅仅移动节点,其他属性没变化
|
||||||
|
* @param cfg 节点数据模型
|
||||||
|
*/
|
||||||
|
public isOnlyMove(cfg: NodeConfig): boolean {
|
||||||
|
if(!cfg) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const existX = isNil(cfg.x)
|
||||||
|
const existY = isNil(cfg.y)
|
||||||
|
|
||||||
|
const keys = Object.keys(cfg)
|
||||||
|
|
||||||
|
// 仅有一个字段,包含 x 或者 包含 y
|
||||||
|
// 两个字段,同时有 x,同时有 y
|
||||||
|
return (keys.length === 1 && (existX || existY))
|
||||||
|
|| (keys.length === 2 && existX && existY)
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +1,16 @@
|
|||||||
import GraphEvent from '@antv/g-base/lib/event/graph-event'
|
|
||||||
import isArray from '@antv/util/lib/is-array'
|
import isArray from '@antv/util/lib/is-array'
|
||||||
import isNil from '@antv/util/lib/is-nil'
|
import isNil from '@antv/util/lib/is-nil'
|
||||||
import isNumber from "@antv/util/lib/is-number";
|
import isNumber from "@antv/util/lib/is-number";
|
||||||
import isString from '@antv/util/lib/is-string'
|
import isString from '@antv/util/lib/is-string'
|
||||||
import { IG6GraphEvent, IPadding } from "../../types";
|
import { G6GraphEvent } from '@g6/interface/behavior';
|
||||||
import { G6GraphEvent } from '../interface/behavior';
|
import { IG6GraphEvent, Padding } from '@g6/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* turn padding into [top, right, bottom, right]
|
* turn padding into [top, right, bottom, right]
|
||||||
* @param {Number|Array} padding input padding
|
* @param {Number|Array} padding input padding
|
||||||
* @return {array} output
|
* @return {array} output
|
||||||
*/
|
*/
|
||||||
export const formatPadding = (padding: IPadding): number[] => {
|
export const formatPadding = (padding: Padding): number[] => {
|
||||||
let top = 0;
|
let top = 0;
|
||||||
let left = 0;
|
let left = 0;
|
||||||
let right = 0;
|
let right = 0;
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import Group from "@antv/g-canvas/lib/group";
|
import Group from "@antv/g-canvas/lib/group";
|
||||||
import ShapeBase from "@antv/g-canvas/lib/shape/base";
|
|
||||||
import { BBox } from "@antv/g-canvas/lib/types";
|
|
||||||
import { vec2 } from "@antv/matrix-util";
|
import { vec2 } from "@antv/matrix-util";
|
||||||
import each from '@antv/util/lib/each'
|
import each from '@antv/util/lib/each'
|
||||||
import { IEdgeConfig, IPoint, ITreeGraphData } from "../../types";
|
import Global from '@g6/global'
|
||||||
import Global from '../global'
|
import { INode } from "@g6/interface/item";
|
||||||
import { INode } from "../interface/item";
|
import { EdgeConfig, IBBox, IPoint, IShapeBase, TreeGraphData } from '@g6/types';
|
||||||
import { applyMatrix } from "./math";
|
import { applyMatrix } from "./math";
|
||||||
|
|
||||||
const PI: number = Math.PI
|
const PI: number = Math.PI
|
||||||
@ -16,7 +14,7 @@ const cos: (x: number) => number = Math.cos
|
|||||||
const SELF_LINK_SIN: number = sin(PI / 8);
|
const SELF_LINK_SIN: number = sin(PI / 8);
|
||||||
const SELF_LINK_COS: number = cos(PI / 8);
|
const SELF_LINK_COS: number = cos(PI / 8);
|
||||||
|
|
||||||
export const getBBox = (element: ShapeBase, group: Group): BBox => {
|
export const getBBox = (element: IShapeBase, group: Group): IBBox => {
|
||||||
const bbox = element.getBBox();
|
const bbox = element.getBBox();
|
||||||
let leftTop: IPoint = {
|
let leftTop: IPoint = {
|
||||||
x: bbox.minX,
|
x: bbox.minX,
|
||||||
@ -52,13 +50,13 @@ export const getBBox = (element: ShapeBase, group: Group): BBox => {
|
|||||||
* get loop edge config
|
* get loop edge config
|
||||||
* @param cfg edge config
|
* @param cfg edge config
|
||||||
*/
|
*/
|
||||||
export const getLoopCfgs = (cfg: IEdgeConfig): IEdgeConfig => {
|
export const getLoopCfgs = (cfg: EdgeConfig): EdgeConfig => {
|
||||||
const item: INode = cfg.sourceNode || cfg.targetNode
|
const item: INode = cfg.sourceNode || cfg.targetNode
|
||||||
const container: Group = item.get('group')
|
const container: Group = item.get('group')
|
||||||
const containerMatrix = container.getMatrix()
|
const containerMatrix = container.getMatrix()
|
||||||
|
|
||||||
const keyShape: ShapeBase = item.getKeyShape()
|
const keyShape: IShapeBase = item.getKeyShape()
|
||||||
const bbox: BBox = keyShape.getBBox()
|
const bbox: IBBox = keyShape.getBBox()
|
||||||
|
|
||||||
const loopCfg = cfg.loopCfg || {}
|
const loopCfg = cfg.loopCfg || {}
|
||||||
// 距离keyShape边的最高距离
|
// 距离keyShape边的最高距离
|
||||||
@ -269,7 +267,7 @@ export const getLoopCfgs = (cfg: IEdgeConfig): IEdgeConfig => {
|
|||||||
// return result;
|
// return result;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const traverse = (data: ITreeGraphData, fn: (param: ITreeGraphData) => boolean) => {
|
const traverse = (data: TreeGraphData, fn: (param: TreeGraphData) => boolean) => {
|
||||||
if(!fn(data)) {
|
if(!fn(data)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -278,7 +276,7 @@ const traverse = (data: ITreeGraphData, fn: (param: ITreeGraphData) => boolean)
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const traverseTree = (data: ITreeGraphData, fn: (param: ITreeGraphData) => boolean) => {
|
export const traverseTree = (data: TreeGraphData, fn: (param: TreeGraphData) => boolean) => {
|
||||||
if(typeof fn !== 'function') {
|
if(typeof fn !== 'function') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -290,7 +288,7 @@ export const traverseTree = (data: ITreeGraphData, fn: (param: ITreeGraphData) =
|
|||||||
* @param data Tree graph data
|
* @param data Tree graph data
|
||||||
* @param layout
|
* @param layout
|
||||||
*/
|
*/
|
||||||
export const radialLayout = (data: ITreeGraphData, layout?: string): ITreeGraphData => {
|
export const radialLayout = (data: TreeGraphData, layout?: string): TreeGraphData => {
|
||||||
// 布局方式有 H / V / LR / RL / TB / BT
|
// 布局方式有 H / V / LR / RL / TB / BT
|
||||||
const VERTICAL_LAYOUTS: string[] = [ 'V', 'TB', 'BT' ];
|
const VERTICAL_LAYOUTS: string[] = [ 'V', 'TB', 'BT' ];
|
||||||
const min: IPoint = {
|
const min: IPoint = {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import groupBy, { ObjectType } from '@antv/util/lib/group-by'
|
import groupBy, { ObjectType } from '@antv/util/lib/group-by'
|
||||||
import { IGraphData, IGroupConfig, IGroupNodeIds } from '../../types';
|
import { GraphData, GroupConfig, GroupNodeIds } from '@g6/types';
|
||||||
|
|
||||||
export const getAllNodeInGroups = (data: IGraphData): IGroupNodeIds => {
|
export const getAllNodeInGroups = (data: GraphData): GroupNodeIds => {
|
||||||
const groupById: ObjectType<IGroupConfig> = groupBy(data.groups, 'id');
|
const groupById: ObjectType<GroupConfig> = groupBy(data.groups, 'id');
|
||||||
const groupByParentId: ObjectType<IGroupConfig> = groupBy(data.groups, 'parentId');
|
const groupByParentId: ObjectType<GroupConfig> = groupBy(data.groups, 'parentId');
|
||||||
|
|
||||||
const result = {};
|
const result = {};
|
||||||
for (const parentId in groupByParentId) {
|
for (const parentId in groupByParentId) {
|
||||||
@ -39,7 +39,7 @@ export const getAllNodeInGroups = (data: IGraphData): IGroupNodeIds => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 缓存所有groupID对应的Node
|
// 缓存所有groupID对应的Node
|
||||||
const groupNodes: IGroupNodeIds = {} as IGroupNodeIds;
|
const groupNodes: GroupNodeIds = {} as GroupNodeIds;
|
||||||
for (const groupId in groupIds) {
|
for (const groupId in groupIds) {
|
||||||
if (!groupId || groupId === 'undefined') {
|
if (!groupId || groupId === 'undefined') {
|
||||||
continue;
|
continue;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
import Group from '@antv/g-canvas/lib/group';
|
||||||
import { mat3, vec3 } from '@antv/matrix-util'
|
import { mat3, vec3 } from '@antv/matrix-util'
|
||||||
import { ICircle, IEllipse, IGraphData, IMatrix, IPoint, IRect } from '../../types'
|
import { transform } from '@antv/matrix-util'
|
||||||
|
import { GraphData, ICircle, IEllipse, IMatrix, IPoint, IRect } from '@g6/types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否在区间内
|
* 是否在区间内
|
||||||
@ -271,7 +273,7 @@ export const floydWarshall = (adjMatrix: IMatrix[]): IMatrix[] => {
|
|||||||
* @param data graph data
|
* @param data graph data
|
||||||
* @param directed whether it's a directed graph
|
* @param directed whether it's a directed graph
|
||||||
*/
|
*/
|
||||||
export const getAdjMatrix = (data: IGraphData, directed: boolean): IMatrix[] => {
|
export const getAdjMatrix = (data: GraphData, directed: boolean): IMatrix[] => {
|
||||||
const nodes = data.nodes;
|
const nodes = data.nodes;
|
||||||
const edges = data.edges;
|
const edges = data.edges;
|
||||||
const matrix: IMatrix[] = [];
|
const matrix: IMatrix[] = [];
|
||||||
@ -294,4 +296,16 @@ export const getAdjMatrix = (data: IGraphData, directed: boolean): IMatrix[] =>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return matrix;
|
return matrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平移group
|
||||||
|
* @param group Group 实例
|
||||||
|
* @param point 坐标
|
||||||
|
*/
|
||||||
|
export const translate = (group: Group, point: IPoint) => {
|
||||||
|
const matrix: IMatrix = group.getMatrix()
|
||||||
|
transform(matrix, [
|
||||||
|
[ 't', point.x, point.y ]
|
||||||
|
])
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import { vec2 } from '@antv/matrix-util'
|
import { vec2 } from '@antv/matrix-util'
|
||||||
import { catmullRom2Bezier } from '@antv/path-util'
|
import { catmullRom2Bezier } from '@antv/path-util'
|
||||||
import { IPoint } from '../../types'
|
import { IPoint } from '@g6/types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 替换字符串中的字段
|
* 替换字符串中的字段
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import '../../../src/behavior'
|
import '../../../src/behavior'
|
||||||
import Behavior from '../../../src/behavior/behavior'
|
import Behavior from '../../../src/behavior/behavior'
|
||||||
import { IBehavior } from '../../../types';
|
import { IBehavior } from '../../../src/interface/behavior';
|
||||||
|
|
||||||
describe('Behavior', () => {
|
describe('Behavior', () => {
|
||||||
it('register signle behavior', () => {
|
it('register signle behavior', () => {
|
||||||
|
19
tests/unit/graph/controller/mode-spec.ts
Normal file
19
tests/unit/graph/controller/mode-spec.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { Mode } from '../../../../src/graph/controller'
|
||||||
|
import Graph from '../../../../src/graph/graph'
|
||||||
|
import { GraphOptions, IGraph } from '../../../../src/interface/graph';
|
||||||
|
|
||||||
|
describe('Mode Controller', () => {
|
||||||
|
it('signle mode', () => {
|
||||||
|
const cfg: GraphOptions = {
|
||||||
|
container: 'x',
|
||||||
|
width: 200,
|
||||||
|
height: 100
|
||||||
|
}
|
||||||
|
const graph: IGraph = new Graph(cfg)
|
||||||
|
const modeController = new Mode(graph)
|
||||||
|
expect(Object.keys(modeController.modes).length).toBe(1);
|
||||||
|
expect(modeController.modes.default).not.toBe(undefined);
|
||||||
|
expect(modeController.modes.default.length).toBe(0);
|
||||||
|
expect(modeController.mode).toBe('default');
|
||||||
|
})
|
||||||
|
})
|
@ -11,7 +11,12 @@
|
|||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"lib": ["esnext", "dom"],
|
"lib": ["esnext", "dom"],
|
||||||
"types": ["jest"]
|
"types": ["jest"],
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@g6/*": ["./src/*"],
|
||||||
|
"@g6/types": ["./types"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"typedocOptions": {
|
"typedocOptions": {
|
||||||
|
107
types/index.ts
107
types/index.ts
@ -1,17 +1,28 @@
|
|||||||
import GraphEvent from '@antv/g-base/lib/event/graph-event';
|
import GraphEvent from '@antv/g-base/lib/event/graph-event';
|
||||||
|
import { BBox } from '@antv/g-base/lib/types';
|
||||||
|
import ShapeBase from '@antv/g-canvas/lib/shape/base';
|
||||||
import { IGraph } from '../src/interface/graph';
|
import { IGraph } from '../src/interface/graph';
|
||||||
import { IItem, INode } from '../src/interface/item'
|
import { IItem, INode } from '../src/interface/item'
|
||||||
|
|
||||||
|
// Math types
|
||||||
export interface IPoint {
|
export interface IPoint {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
|
// 获取连接点时使用
|
||||||
|
anchorIndex?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IMatrix = number[];
|
export type IMatrix = number[];
|
||||||
|
|
||||||
export type IPadding = number | string | number[];
|
export interface IBBox extends BBox {
|
||||||
|
centerX?: number;
|
||||||
|
centerY?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export type IShapeStyle = Partial<{
|
export type Padding = number | string | number[];
|
||||||
|
|
||||||
|
// Shape types
|
||||||
|
export type ShapeStyle = Partial<{
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
r: number;
|
r: number;
|
||||||
@ -27,6 +38,10 @@ export type IShapeStyle = Partial<{
|
|||||||
[key: string]: string | number | object | object[]
|
[key: string]: string | number | object | object[]
|
||||||
}>
|
}>
|
||||||
|
|
||||||
|
export interface IShapeBase extends ShapeBase {
|
||||||
|
isKeyShape: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IRect extends IPoint {
|
export interface IRect extends IPoint {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
@ -41,12 +56,15 @@ export interface IEllipse extends IPoint {
|
|||||||
ry: number;
|
ry: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type IModelStyle = Partial<{
|
export type SourceTarget = 'source' | 'target'
|
||||||
|
|
||||||
|
// model types (node edge group)
|
||||||
|
export type ModelStyle = Partial<{
|
||||||
style: {
|
style: {
|
||||||
[key: string]: IShapeStyle
|
[key: string]: ShapeStyle
|
||||||
};
|
};
|
||||||
stateStyles: {
|
stateStyles: {
|
||||||
[key: string]: IShapeStyle;
|
[key: string]: ShapeStyle;
|
||||||
};
|
};
|
||||||
// loop edge config
|
// loop edge config
|
||||||
loopCfg: {
|
loopCfg: {
|
||||||
@ -56,37 +74,35 @@ type IModelStyle = Partial<{
|
|||||||
clockwise?: boolean;
|
clockwise?: boolean;
|
||||||
};
|
};
|
||||||
}>
|
}>
|
||||||
interface IModelStyle1 {
|
|
||||||
style?: {
|
|
||||||
[key: string]: IShapeStyle
|
|
||||||
};
|
|
||||||
stateStyles?: {
|
|
||||||
[key: string]: IShapeStyle;
|
|
||||||
};
|
|
||||||
// loop edge config
|
|
||||||
loopCfg?: {
|
|
||||||
dist?: number;
|
|
||||||
position?: string;
|
|
||||||
// 如果逆时针画,交换起点和终点
|
|
||||||
clockwise?: boolean;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type IModelConfig = INodeConfig | IEdgeConfig
|
export type Easeing =
|
||||||
|
'easeLinear'
|
||||||
|
| 'easePolyIn'
|
||||||
|
| 'easePolyOut'
|
||||||
|
| 'easePolyInOut'
|
||||||
|
| 'easeQuad'
|
||||||
|
| 'easeQuadIn'
|
||||||
|
| 'easeQuadOut'
|
||||||
|
| 'easeQuadInOut'
|
||||||
|
| string
|
||||||
|
|
||||||
export interface INodeConfig extends IModelStyle {
|
|
||||||
id: string;
|
export interface ModelConfig extends ModelStyle {
|
||||||
|
shape?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
groupId?: string;
|
|
||||||
description?: string;
|
|
||||||
x?: number;
|
x?: number;
|
||||||
y?: number;
|
y?: number;
|
||||||
}
|
}
|
||||||
|
export interface NodeConfig extends ModelConfig {
|
||||||
|
id: string;
|
||||||
|
groupId?: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IEdgeConfig extends IModelStyle {
|
export interface EdgeConfig extends ModelConfig {
|
||||||
|
id?: string;
|
||||||
source: string;
|
source: string;
|
||||||
target: string;
|
target: string;
|
||||||
label?: string;
|
|
||||||
sourceNode?: INode;
|
sourceNode?: INode;
|
||||||
targetNode?: INode;
|
targetNode?: INode;
|
||||||
startPoint?: IPoint;
|
startPoint?: IPoint;
|
||||||
@ -94,28 +110,28 @@ export interface IEdgeConfig extends IModelStyle {
|
|||||||
controlPoints?: IPoint[];
|
controlPoints?: IPoint[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IGroupConfig {
|
export interface GroupConfig {
|
||||||
id: string;
|
id: string;
|
||||||
parentId?: string;
|
parentId?: string;
|
||||||
[key: string]: string | IModelStyle;
|
[key: string]: string | ModelStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IGroupNodeIds {
|
export interface GroupNodeIds {
|
||||||
[key: string]: string[];
|
[key: string]: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IGraphData {
|
export interface GraphData {
|
||||||
nodes?: INodeConfig[];
|
nodes?: NodeConfig[];
|
||||||
edges?: IEdgeConfig[];
|
edges?: EdgeConfig[];
|
||||||
groups?: IGroupConfig[];
|
groups?: GroupConfig[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITreeGraphData {
|
export interface TreeGraphData {
|
||||||
id: string;
|
id: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
x?: number;
|
x?: number;
|
||||||
y?: number;
|
y?: number;
|
||||||
children?: ITreeGraphData[];
|
children?: TreeGraphData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Behavior type file
|
// Behavior type file
|
||||||
@ -160,12 +176,12 @@ type Unbind = 'unbind'
|
|||||||
|
|
||||||
export type DefaultBehaviorType = IG6GraphEvent | string | number | object
|
export type DefaultBehaviorType = IG6GraphEvent | string | number | object
|
||||||
|
|
||||||
export type IBehaviorOpation<U> = {
|
export type BehaviorOpation<U> = {
|
||||||
[T in keyof U]:
|
[T in keyof U]:
|
||||||
T extends GetEvents ? () => { [key in G6Event]?: string } :
|
T extends GetEvents ? () => { [key in G6Event]?: string } :
|
||||||
T extends ShouldBegin ? (cfg?: IModelConfig) => boolean :
|
T extends ShouldBegin ? (cfg?: ModelConfig) => boolean :
|
||||||
T extends ShouldEnd ? (cfg?: IModelConfig) => boolean :
|
T extends ShouldEnd ? (cfg?: ModelConfig) => boolean :
|
||||||
T extends ShouldUpdate ? (cfg?: IModelConfig) => boolean :
|
T extends ShouldUpdate ? (cfg?: ModelConfig) => boolean :
|
||||||
T extends Bind ? (graph: IGraph) => void :
|
T extends Bind ? (graph: IGraph) => void :
|
||||||
T extends Unbind ? (graph: IGraph) => void :
|
T extends Unbind ? (graph: IGraph) => void :
|
||||||
(...args: DefaultBehaviorType[]) => unknown;
|
(...args: DefaultBehaviorType[]) => unknown;
|
||||||
@ -176,14 +192,3 @@ export type IEvent = Record<G6Event, string>
|
|||||||
export interface IG6GraphEvent extends GraphEvent {
|
export interface IG6GraphEvent extends GraphEvent {
|
||||||
item: IItem;
|
item: IItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IBehavior {
|
|
||||||
constructor: (cfg?: object) => void;
|
|
||||||
getEvents: () => { [key in G6Event]?: string };
|
|
||||||
shouldBegin: () => boolean;
|
|
||||||
shouldUpdate: () => boolean;
|
|
||||||
shouldEnd: () => boolean;
|
|
||||||
bind: (graph: IGraph) => void;
|
|
||||||
unbind: (graph: IGraph) => void;
|
|
||||||
[key: string]: (...args: DefaultBehaviorType[]) => unknown;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user