mirror of
https://gitee.com/antv/g6.git
synced 2024-12-02 03:38:20 +08:00
V5 layout (#4308)
* feat: unified datachange from changeData, addData, updateData, and removeData; feat: item definition and first drawing * feat: update canvas while addData; feat: update canvas while updateData (node); chore: unified additems, removeitems, updateitems to be itemchange hook * feat: init layout controller * feat: node and edge updating and drawing * chore: neaten * feat: draw and update labels for node and edge * feat: icon for edge; feat: custom node and edge and register to lib * feat: init layout controller * fix: use latest g-webgl * feat: layout controller should support & option * fix: registry typo & add custom layout test case * feat: state related API for graph and item * chore: update notes --------- Co-authored-by: Yanyan-Wang <yanyanwang93@gmail.com>
This commit is contained in:
parent
875a9eb39b
commit
0f23a181db
@ -47,6 +47,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@antv/graphlib": "^2.0.0-alpha.0",
|
||||||
"eslint": "^7.11.0",
|
"eslint": "^7.11.0",
|
||||||
"eslint-config-prettier": "^6.7.0",
|
"eslint-config-prettier": "^6.7.0",
|
||||||
"eslint-plugin-import": "^2.22.1",
|
"eslint-plugin-import": "^2.22.1",
|
||||||
@ -65,8 +66,7 @@
|
|||||||
"tslint-config-airbnb": "^5.11.2",
|
"tslint-config-airbnb": "^5.11.2",
|
||||||
"tslint-config-prettier": "^1.18.0",
|
"tslint-config-prettier": "^1.18.0",
|
||||||
"tslint-eslint-rules": "^5.4.0",
|
"tslint-eslint-rules": "^5.4.0",
|
||||||
"typescript": "^4.6.3",
|
"typescript": "^4.6.3"
|
||||||
"@antv/graphlib": "^2.0.0-alpha.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^16.9.35",
|
"@types/react": "^16.9.35",
|
||||||
@ -88,4 +88,4 @@
|
|||||||
"normalize-url": "^4.1.0",
|
"normalize-url": "^4.1.0",
|
||||||
"sharp": "^0.30.4"
|
"sharp": "^0.30.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,10 @@
|
|||||||
"@antv/g": "^5.15.7",
|
"@antv/g": "^5.15.7",
|
||||||
"@antv/g-canvas": "^1.9.28",
|
"@antv/g-canvas": "^1.9.28",
|
||||||
"@antv/g-svg": "^1.8.36",
|
"@antv/g-svg": "^1.8.36",
|
||||||
"@antv/graphlib": "^2.0.0-alpha.0",
|
"@antv/g-webgl": "^1.7.44",
|
||||||
|
"@antv/graphlib": "^2.0.0",
|
||||||
|
"@antv/layout": "^1.0.0-alpha.17",
|
||||||
|
"@antv/layout-gpu": "^1.0.0-alpha.3",
|
||||||
"@antv/util": "~2.0.5",
|
"@antv/util": "~2.0.5",
|
||||||
"typedoc-plugin-markdown": "^3.14.0"
|
"typedoc-plugin-markdown": "^3.14.0"
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Graph as GraphLib, ID } from '@antv/graphlib';
|
import { Graph as GraphLib, ID } from '@antv/graphlib';
|
||||||
import { GraphData, IGraph, ComboModel, ComboUserModel } from '../../types';
|
import { GraphData, IGraph, ComboModel, ComboUserModel } from '../../types';
|
||||||
import { registery } from '../../stdlib';
|
import { registry } from '../../stdlib';
|
||||||
import { getExtension } from '../../util/extension';
|
import { getExtension } from '../../util/extension';
|
||||||
import { clone, isArray, isNumber, isString, isFunction, isObject } from '@antv/util';
|
import { clone, isArray, isNumber, isString, isFunction, isObject } from '@antv/util';
|
||||||
import { NodeModel, NodeModelData, NodeUserModel, NodeUserModelData } from '../../types/node';
|
import { NodeModel, NodeModelData, NodeUserModel, NodeUserModelData } from '../../types/node';
|
||||||
@ -85,7 +85,7 @@ export class DataController {
|
|||||||
return transform
|
return transform
|
||||||
.map((config) => ({
|
.map((config) => ({
|
||||||
config,
|
config,
|
||||||
func: getExtension(config, registery.useLib, 'transform'),
|
func: getExtension(config, registry.useLib, 'transform'),
|
||||||
}))
|
}))
|
||||||
.filter((ext) => !!ext.func);
|
.filter((ext) => !!ext.func);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { IGraph } from "../../types";
|
import { isObject } from '@antv/util';
|
||||||
import { registery } from '../../stdlib';
|
import { registry } from '../../stdlib';
|
||||||
import { getExtension } from "../../util/extension";
|
import { IGraph } from '../../types';
|
||||||
import { isObject } from "@antv/util";
|
import { getExtension } from '../../util/extension';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages the interaction extensions and graph modes;
|
* Manages the interaction extensions and graph modes;
|
||||||
@ -29,14 +29,16 @@ export class InteractionController {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the extensions from useLib, stdLib is a sub set of useLib.
|
* Get the extensions from useLib, stdLib is a sub set of useLib.
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
private getExtensions() {
|
private getExtensions() {
|
||||||
const { modes = {} } = this.graph.getSpecification();
|
const { modes = {} } = this.graph.getSpecification();
|
||||||
const modeBehaviors = {};
|
const modeBehaviors = {};
|
||||||
Object.keys(modes).forEach(mode => {
|
Object.keys(modes).forEach((mode) => {
|
||||||
modeBehaviors[mode] = modes[mode].map(config => getExtension(config, registery.useLib, 'behavior')).filter(behavior => !!behavior);
|
modeBehaviors[mode] = modes[mode]
|
||||||
})
|
.map((config) => getExtension(config, registry.useLib, 'behavior'))
|
||||||
|
.filter((behavior) => !!behavior);
|
||||||
|
});
|
||||||
return modeBehaviors;
|
return modeBehaviors;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,25 +52,35 @@ export class InteractionController {
|
|||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listener of graph's behaviorchange hook. Update, add, or remove behaviors from modes.
|
* Listener of graph's behaviorchange hook. Update, add, or remove behaviors from modes.
|
||||||
* @param param contains action, modes, and behaviors
|
* @param param contains action, modes, and behaviors
|
||||||
*/
|
*/
|
||||||
private onBehaviorChange(self, param: { action: 'update' | 'add' | 'remove', modes: string[], behaviors: (string | { key: string, type: string })[] }) {
|
private onBehaviorChange(
|
||||||
|
self,
|
||||||
|
param: {
|
||||||
|
action: 'update' | 'add' | 'remove';
|
||||||
|
modes: string[];
|
||||||
|
behaviors: (string | { key: string; type: string })[];
|
||||||
|
},
|
||||||
|
) {
|
||||||
const { action, modes, behaviors } = param;
|
const { action, modes, behaviors } = param;
|
||||||
modes.forEach(mode => {
|
modes.forEach((mode) => {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'add':
|
case 'add':
|
||||||
behaviors.forEach(config => self.extensions[mode].push(getExtension(config, registery.useLib, 'behavior')));
|
behaviors.forEach((config) =>
|
||||||
|
self.extensions[mode].push(getExtension(config, registry.useLib, 'behavior')),
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'remove':
|
case 'remove':
|
||||||
behaviors.forEach(key => {
|
behaviors.forEach((key) => {
|
||||||
self.extensions[mode] = self.extensions[mode].filter(behavior => behavior.getKey() === key)
|
self.extensions[mode] = self.extensions[mode].filter(
|
||||||
|
(behavior) => behavior.getKey() === key,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'update':
|
case 'update':
|
||||||
behaviors.forEach(config => {
|
behaviors.forEach((config) => {
|
||||||
if (isObject(config) && config.hasOwnProperty('key')) {
|
if (isObject(config) && config.hasOwnProperty('key')) {
|
||||||
const behaviorItem = self.extensions[mode].find(behavior => behavior.getKey() === config.key);
|
const behaviorItem = self.extensions[mode].find(behavior => behavior.getKey() === config.key);
|
||||||
if (behaviorItem) behaviorItem.updateConfig(config);
|
if (behaviorItem) behaviorItem.updateConfig(config);
|
||||||
@ -80,4 +92,4 @@ export class InteractionController {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { GraphChange, ID } from '@antv/graphlib';
|
import { GraphChange, ID } from '@antv/graphlib';
|
||||||
import { ComboModel, IGraph } from '../../types';
|
import { ComboModel, IGraph } from '../../types';
|
||||||
import { registery } from '../../stdlib';
|
import { registry } from '../../stdlib';
|
||||||
import { getExtension } from '../../util/extension';
|
import { getExtension } from '../../util/extension';
|
||||||
import { GraphCore } from '../../types/data';
|
import { GraphCore } from '../../types/data';
|
||||||
import { NodeDisplayModel, NodeEncode, NodeModel, NodeModelData } from '../../types/node';
|
import { NodeDisplayModel, NodeEncode, NodeModel, NodeModelData } from '../../types/node';
|
||||||
@ -87,13 +87,13 @@ export class ItemController {
|
|||||||
const comboTypes = ['circle-combo', 'rect-combo']; // TODO: WIP
|
const comboTypes = ['circle-combo', 'rect-combo']; // TODO: WIP
|
||||||
return {
|
return {
|
||||||
node: nodeTypes
|
node: nodeTypes
|
||||||
.map((config) => getExtension(config, registery.useLib, 'node'))
|
.map((config) => getExtension(config, registry.useLib, 'node'))
|
||||||
.filter(Boolean),
|
.filter(Boolean),
|
||||||
edge: edgeTypes
|
edge: edgeTypes
|
||||||
.map((config) => getExtension(config, registery.useLib, 'edge'))
|
.map((config) => getExtension(config, registry.useLib, 'edge'))
|
||||||
.filter(Boolean),
|
.filter(Boolean),
|
||||||
combo: comboTypes
|
combo: comboTypes
|
||||||
.map((config) => getExtension(config, registery.useLib, 'combo'))
|
.map((config) => getExtension(config, registry.useLib, 'combo'))
|
||||||
.filter(Boolean),
|
.filter(Boolean),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,129 @@
|
|||||||
import { IGraph } from "../../types";
|
import { isLayoutWithIterations, Layout, LayoutMapping, Supervisor } from '@antv/layout';
|
||||||
|
import { stdLib } from '../../stdlib';
|
||||||
|
import { IGraph } from '../../types';
|
||||||
|
import { GraphCore } from '../../types/data';
|
||||||
|
import { LayoutOptions } from '../../types/layout';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages layout extensions and graph layout.
|
* Manages layout extensions and graph layout.
|
||||||
|
* It will also emit `afterlayout` & `tick` events on Graph.
|
||||||
*/
|
*/
|
||||||
export class LayoutController {
|
export class LayoutController {
|
||||||
public extensions = {};
|
public extensions = {};
|
||||||
public graph: IGraph;
|
public graph: IGraph;
|
||||||
|
|
||||||
|
private currentLayout: Layout<any>;
|
||||||
|
private currentSupervisor: Supervisor;
|
||||||
|
|
||||||
constructor(graph: IGraph<any>) {
|
constructor(graph: IGraph<any>) {
|
||||||
this.graph = graph;
|
this.graph = graph;
|
||||||
// this.tap();
|
this.tap();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* Subscribe the lifecycle of graph.
|
||||||
|
*/
|
||||||
|
private tap() {
|
||||||
|
this.graph.hooks.layout.tap(this.onLayout.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async onLayout(params: { graphCore: GraphCore; options?: LayoutOptions }) {
|
||||||
|
// Stop currentLayout if any.
|
||||||
|
this.stopLayout();
|
||||||
|
|
||||||
|
const { graphCore, options } = params;
|
||||||
|
|
||||||
|
const {
|
||||||
|
type,
|
||||||
|
workerEnabled,
|
||||||
|
animated,
|
||||||
|
iterations = 300,
|
||||||
|
...rest
|
||||||
|
} = {
|
||||||
|
...this.graph.getSpecification().layout,
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find built-in layout algorithms.
|
||||||
|
const layoutCtor = stdLib.layouts[type];
|
||||||
|
if (!layoutCtor) {
|
||||||
|
throw new Error(`Unknown layout algorithm: ${type}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize layout.
|
||||||
|
const layout = new layoutCtor(rest);
|
||||||
|
this.currentLayout = layout;
|
||||||
|
|
||||||
|
let positions: LayoutMapping;
|
||||||
|
|
||||||
|
if (workerEnabled) {
|
||||||
|
/**
|
||||||
|
* Run algorithm in WebWorker, `animated` option will be ignored.
|
||||||
|
*/
|
||||||
|
const supervisor = new Supervisor(graphCore, layout, { iterations });
|
||||||
|
this.currentSupervisor = supervisor;
|
||||||
|
positions = await supervisor.execute();
|
||||||
|
} else {
|
||||||
|
if (isLayoutWithIterations(layout)) {
|
||||||
|
if (animated) {
|
||||||
|
positions = await layout.execute(graphCore, {
|
||||||
|
onTick: (positionsOnTick: LayoutMapping) => {
|
||||||
|
// Display the animated process of layout.
|
||||||
|
this.updateNodesPosition(positionsOnTick);
|
||||||
|
this.graph.emit('tick', positionsOnTick);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* Manually step simulation in a sync way. `onTick` won't get triggered in this case,
|
||||||
|
* there will be no animation either.
|
||||||
|
*/
|
||||||
|
layout.execute(graphCore);
|
||||||
|
layout.stop();
|
||||||
|
positions = layout.tick(iterations);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `onTick` will get triggered in this case.
|
||||||
|
*/
|
||||||
|
} else {
|
||||||
|
positions = await layout.execute(graphCore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update nodes' positions.
|
||||||
|
this.updateNodesPosition(positions);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopLayout() {
|
||||||
|
if (this.currentLayout && isLayoutWithIterations(this.currentLayout)) {
|
||||||
|
this.currentLayout.stop();
|
||||||
|
this.currentLayout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentSupervisor) {
|
||||||
|
this.currentSupervisor.stop();
|
||||||
|
this.currentSupervisor = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.stopLayout();
|
||||||
|
|
||||||
|
if (this.currentSupervisor) {
|
||||||
|
this.currentSupervisor.kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateNodesPosition(positions: LayoutMapping) {
|
||||||
|
positions.nodes.forEach((node) => {
|
||||||
|
this.graph.updateData('node', {
|
||||||
|
id: node.id,
|
||||||
|
data: {
|
||||||
|
x: node.data.x,
|
||||||
|
y: node.data.y,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -18,7 +18,7 @@ import { GraphCore } from '../types/data';
|
|||||||
import { EdgeModel, EdgeModelData } from '../types/edge';
|
import { EdgeModel, EdgeModelData } from '../types/edge';
|
||||||
import { Hooks } from '../types/hook';
|
import { Hooks } from '../types/hook';
|
||||||
import { ITEM_TYPE } from '../types/item';
|
import { ITEM_TYPE } from '../types/item';
|
||||||
import { LayoutCommonConfig } from '../types/layout';
|
import { LayoutOptions } from '../types/layout';
|
||||||
import { NodeModel, NodeModelData } from '../types/node';
|
import { NodeModel, NodeModelData } from '../types/node';
|
||||||
import { FitViewRules, GraphAlignment } from '../types/view';
|
import { FitViewRules, GraphAlignment } from '../types/view';
|
||||||
import { createCanvas } from '../util/canvas';
|
import { createCanvas } from '../util/canvas';
|
||||||
@ -111,6 +111,7 @@ export default class Graph<B extends BehaviorRegistry> extends EventEmitter impl
|
|||||||
graphCore: GraphCore;
|
graphCore: GraphCore;
|
||||||
}>({ name: 'itemchange' }),
|
}>({ name: 'itemchange' }),
|
||||||
render: new Hook<{ graphCore: GraphCore }>({ name: 'render' }),
|
render: new Hook<{ graphCore: GraphCore }>({ name: 'render' }),
|
||||||
|
layout: new Hook<{ graphCore: GraphCore }>({ name: 'layout' }),
|
||||||
modechange: new Hook<{ mode: string }>({ name: 'modechange' }),
|
modechange: new Hook<{ mode: string }>({ name: 'modechange' }),
|
||||||
behaviorchange: new Hook<{
|
behaviorchange: new Hook<{
|
||||||
action: 'update' | 'add' | 'remove';
|
action: 'update' | 'add' | 'remove';
|
||||||
@ -143,20 +144,28 @@ export default class Graph<B extends BehaviorRegistry> extends EventEmitter impl
|
|||||||
* @returns
|
* @returns
|
||||||
* @group Data
|
* @group Data
|
||||||
*/
|
*/
|
||||||
public read(data: GraphData) {
|
public async read(data: GraphData) {
|
||||||
this.hooks.datachange.emit({ data, type: 'replace' });
|
this.hooks.datachange.emit({ data, type: 'replace' });
|
||||||
const emitRender = () => {
|
const emitRender = async () => {
|
||||||
this.hooks.render.emit({
|
this.hooks.render.emit({
|
||||||
graphCore: this.dataController.graphCore,
|
graphCore: this.dataController.graphCore,
|
||||||
});
|
});
|
||||||
this.emit('afterrender');
|
this.emit('afterrender');
|
||||||
|
|
||||||
|
// TODO: make read async?
|
||||||
|
await this.hooks.layout.emitLinearAsync({
|
||||||
|
graphCore: this.dataController.graphCore,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.emit('afterlayout');
|
||||||
};
|
};
|
||||||
if (this.canvasReady) {
|
if (this.canvasReady) {
|
||||||
emitRender();
|
await emitRender();
|
||||||
} else {
|
} else {
|
||||||
Promise.all(
|
await Promise.all(
|
||||||
[this.backgroundCanvas, this.canvas, this.transientCanvas].map((canvas) => canvas.ready),
|
[this.backgroundCanvas, this.canvas, this.transientCanvas].map((canvas) => canvas.ready),
|
||||||
).then(emitRender);
|
);
|
||||||
|
await emitRender();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,12 +176,18 @@ export default class Graph<B extends BehaviorRegistry> extends EventEmitter impl
|
|||||||
* @returns
|
* @returns
|
||||||
* @group Data
|
* @group Data
|
||||||
*/
|
*/
|
||||||
public changeData(data: GraphData, type: 'replace' | 'mergeReplace' = 'mergeReplace') {
|
public async changeData(data: GraphData, type: 'replace' | 'mergeReplace' = 'mergeReplace') {
|
||||||
this.hooks.datachange.emit({ data, type });
|
this.hooks.datachange.emit({ data, type });
|
||||||
this.hooks.render.emit({
|
this.hooks.render.emit({
|
||||||
graphCore: this.dataController.graphCore,
|
graphCore: this.dataController.graphCore,
|
||||||
});
|
});
|
||||||
this.emit('afterrender');
|
this.emit('afterrender');
|
||||||
|
|
||||||
|
await this.hooks.layout.emitLinearAsync({
|
||||||
|
graphCore: this.dataController.graphCore,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.emit('afterlayout');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -551,20 +566,20 @@ export default class Graph<B extends BehaviorRegistry> extends EventEmitter impl
|
|||||||
// ===== layout =====
|
// ===== layout =====
|
||||||
/**
|
/**
|
||||||
* Layout the graph (with current configurations if cfg is not assigned).
|
* Layout the graph (with current configurations if cfg is not assigned).
|
||||||
* @param {LayoutCommonConfig} cfg layout configurations. if assigned, the layout spec of the graph will be updated in the same time
|
|
||||||
* @param {GraphAlignment} align align the result
|
|
||||||
* @param {Point} canvasPoint align the result
|
|
||||||
* @param {boolean} stack push it into stack
|
|
||||||
* @group Layout
|
|
||||||
*/
|
*/
|
||||||
public layout(
|
public async layout(options?: LayoutOptions) {
|
||||||
cfg?: LayoutCommonConfig,
|
await this.hooks.layout.emitLinearAsync({
|
||||||
align?: GraphAlignment,
|
graphCore: this.dataController.graphCore,
|
||||||
canvasPoint?: Point,
|
options,
|
||||||
stack?: boolean,
|
});
|
||||||
) {
|
this.emit('afterlayout');
|
||||||
// TODO: LayoutConfig combination instead of LayoutCommonConfig
|
}
|
||||||
// TODO
|
|
||||||
|
/**
|
||||||
|
* Some layout algorithms has many iterations which can be stopped at any time.
|
||||||
|
*/
|
||||||
|
public stopLayout() {
|
||||||
|
this.layoutController.stopLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { IHook } from "../types/hook";
|
import { IHook } from '../types/hook';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A hook class unified the definitions of tap, untap, and emit.
|
* A hook class unified the definitions of tap, untap, and emit.
|
||||||
@ -16,14 +16,14 @@ export default class Hook<T> implements IHook<T> {
|
|||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Tap a listener to the corresponding lifecycle of this hook.
|
* Tap a listener to the corresponding lifecycle of this hook.
|
||||||
* @param listener
|
* @param listener
|
||||||
*/
|
*/
|
||||||
public tap(listener: (param: T) => void) {
|
public tap(listener: (param: T) => void) {
|
||||||
this.listeners.push(listener);
|
this.listeners.push(listener);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Remove a listener from the corresponding lifecycle of this hook.
|
* Remove a listener from the corresponding lifecycle of this hook.
|
||||||
* @param listener
|
* @param listener
|
||||||
*/
|
*/
|
||||||
public unTap(listener: (param: T) => void) {
|
public unTap(listener: (param: T) => void) {
|
||||||
const idx = this.listeners.indexOf(listener);
|
const idx = this.listeners.indexOf(listener);
|
||||||
@ -31,28 +31,34 @@ export default class Hook<T> implements IHook<T> {
|
|||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Emit the corresponding lifecycle to call the listeners
|
* Emit the corresponding lifecycle to call the listeners
|
||||||
* @param param
|
* @param param
|
||||||
*/
|
*/
|
||||||
public emit(param: T) {
|
public emit(param: T) {
|
||||||
this.listeners.forEach(listener => listener(param));
|
this.listeners.forEach((listener) => listener(param));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Linearly async emit the corresponding lifecycle to call the listeners
|
* Linearly async emit the corresponding lifecycle to call the listeners
|
||||||
* @param param
|
* @param param
|
||||||
*/
|
*/
|
||||||
public async emitLinearAsync(param: T): Promise<void> {
|
public async emitLinearAsync(param: T): Promise<void> {
|
||||||
return new Promise(async () => {
|
for (const listener of this.listeners) {
|
||||||
let start = Promise.resolve();
|
await listener(param);
|
||||||
this.listeners.forEach(listener => {
|
}
|
||||||
start = start.then(async () => new Promise(async (resolve, reject) => {
|
// return new Promise(async () => {
|
||||||
try {
|
// let start = Promise.resolve();
|
||||||
await listener(param);
|
// this.listeners.forEach((listener) => {
|
||||||
resolve();
|
// start = start.then(
|
||||||
} catch (e) {
|
// async () =>
|
||||||
reject();
|
// new Promise(async (resolve, reject) => {
|
||||||
}
|
// try {
|
||||||
}));
|
// await listener(param);
|
||||||
});
|
// resolve();
|
||||||
});
|
// } catch (e) {
|
||||||
|
// reject();
|
||||||
|
// }
|
||||||
|
// }),
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import { comboFromNode } from "./data/comboFromNode"
|
import { registry as layoutRegistry } from '@antv/layout';
|
||||||
import DragCanvas from "./behavior/drag-canvas";
|
import { Lib } from '../types/stdlib';
|
||||||
import { Lib } from "../types/stdlib";
|
import DragCanvas from './behavior/drag-canvas';
|
||||||
import { CircleNode } from "./item/node";
|
import { comboFromNode } from './data/comboFromNode';
|
||||||
import { LineEdge } from "./item/edge";
|
import { LineEdge } from './item/edge';
|
||||||
|
import { CircleNode } from './item/node';
|
||||||
|
|
||||||
const stdLib = {
|
const stdLib = {
|
||||||
transforms: {
|
transforms: {
|
||||||
comboFromNode
|
comboFromNode,
|
||||||
},
|
},
|
||||||
themes: {},
|
themes: {},
|
||||||
layouts: {}, // from @antv/layout
|
layouts: layoutRegistry,
|
||||||
behaviors: {
|
behaviors: {
|
||||||
'drag-canvas': DragCanvas
|
'drag-canvas': DragCanvas
|
||||||
},
|
},
|
||||||
@ -34,6 +35,6 @@ const useLib: Lib = {
|
|||||||
combos: {},
|
combos: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const registery = { useLib };
|
const registry = { useLib };
|
||||||
export default registery;
|
export default registry;
|
||||||
export { stdLib, registery };
|
export { stdLib, registry };
|
||||||
|
@ -9,7 +9,7 @@ import { Padding, Point } from './common';
|
|||||||
import { DataChangeType, GraphData } from './data';
|
import { DataChangeType, GraphData } from './data';
|
||||||
import { EdgeModel, EdgeUserModel } from './edge';
|
import { EdgeModel, EdgeUserModel } from './edge';
|
||||||
import { ITEM_TYPE } from './item';
|
import { ITEM_TYPE } from './item';
|
||||||
import { LayoutCommonConfig } from './layout';
|
import { LayoutOptions } from './layout';
|
||||||
import { NodeModel, NodeUserModel } from './node';
|
import { NodeModel, NodeUserModel } from './node';
|
||||||
import { Specification } from './spec';
|
import { Specification } from './spec';
|
||||||
import { FitViewRules, GraphAlignment } from './view';
|
import { FitViewRules, GraphAlignment } from './view';
|
||||||
@ -295,18 +295,9 @@ export interface IGraph<B extends BehaviorRegistry = BehaviorRegistry> extends E
|
|||||||
// ===== layout =====
|
// ===== layout =====
|
||||||
/**
|
/**
|
||||||
* Layout the graph (with current configurations if cfg is not assigned).
|
* Layout the graph (with current configurations if cfg is not assigned).
|
||||||
* @param {LayoutCommonConfig} cfg layout configurations. if assigned, the layout spec of the graph will be updated in the same time
|
|
||||||
* @param {GraphAlignment} align align the result
|
|
||||||
* @param {Point} canvasPoint align the result
|
|
||||||
* @param {boolean} stack push it into stack
|
|
||||||
* @group Layout
|
|
||||||
*/
|
*/
|
||||||
layout: (
|
layout: (options?: LayoutOptions) => Promise<void>;
|
||||||
cfg?: LayoutCommonConfig,
|
stopLayout: () => void;
|
||||||
align?: GraphAlignment,
|
|
||||||
canvasPoint?: Point,
|
|
||||||
stack?: boolean,
|
|
||||||
) => void;
|
|
||||||
|
|
||||||
// ===== interaction =====
|
// ===== interaction =====
|
||||||
/**
|
/**
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { DataChangeType, GraphCore, GraphData } from "./data";
|
import { DataChangeType, GraphCore, GraphData } from "./data";
|
||||||
import { NodeModel, NodeModelData, NodeUserModel } from "./node";
|
import { NodeModelData } from "./node";
|
||||||
import { EdgeModel, EdgeModelData, EdgeUserModel } from "./edge";
|
import { EdgeModelData } from "./edge";
|
||||||
import { ITEM_TYPE } from "./item";
|
import { ITEM_TYPE } from "./item";
|
||||||
import { GraphChange, ID } from "@antv/graphlib";
|
import { GraphChange, ID } from "@antv/graphlib";
|
||||||
|
import { LayoutOptions } from "./layout";
|
||||||
|
|
||||||
export interface IHook<T> {
|
export interface IHook<T> {
|
||||||
name: string;
|
name: string;
|
||||||
@ -10,10 +11,11 @@ export interface IHook<T> {
|
|||||||
tap: (listener: (param: T) => void) => void;
|
tap: (listener: (param: T) => void) => void;
|
||||||
unTap: (listener: (param: T) => void) => void;
|
unTap: (listener: (param: T) => void) => void;
|
||||||
emit: (param: T) => void;
|
emit: (param: T) => void;
|
||||||
|
emitLinearAsync: (param: T) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Hooks {
|
export interface Hooks {
|
||||||
'init': IHook<void>;
|
init: IHook<void>;
|
||||||
// data
|
// data
|
||||||
'datachange': IHook<{
|
'datachange': IHook<{
|
||||||
type: DataChangeType;
|
type: DataChangeType;
|
||||||
@ -24,14 +26,14 @@ export interface Hooks {
|
|||||||
changes: GraphChange<NodeModelData, EdgeModelData>[];
|
changes: GraphChange<NodeModelData, EdgeModelData>[];
|
||||||
graphCore: GraphCore;
|
graphCore: GraphCore;
|
||||||
}>;
|
}>;
|
||||||
'render': IHook<{ graphCore: GraphCore }>; // TODO: define param template
|
render: IHook<{ graphCore: GraphCore }>; // TODO: define param template
|
||||||
// 'layout': IHook<any>; // TODO: define param template
|
layout: IHook<{ graphCore: GraphCore; options?: LayoutOptions }>; // TODO: define param template
|
||||||
// 'updatelayout': IHook<any>; // TODO: define param template
|
// 'updatelayout': IHook<any>; // TODO: define param template
|
||||||
'modechange': IHook<{ mode: string }>;
|
modechange: IHook<{ mode: string }>;
|
||||||
'behaviorchange': IHook<{
|
behaviorchange: IHook<{
|
||||||
action: 'update' | 'add' | 'remove';
|
action: 'update' | 'add' | 'remove';
|
||||||
modes: string[];
|
modes: string[];
|
||||||
behaviors: (string | { type: string, key: string })[];
|
behaviors: (string | { type: string; key: string })[];
|
||||||
}>;
|
}>;
|
||||||
'itemstatechange': IHook<{
|
'itemstatechange': IHook<{
|
||||||
ids: ID[],
|
ids: ID[],
|
||||||
@ -41,4 +43,4 @@ export interface Hooks {
|
|||||||
// 'viewportchange': IHook<any>; // TODO: define param template
|
// 'viewportchange': IHook<any>; // TODO: define param template
|
||||||
// 'destroy': IHook<any>; // TODO: define param template
|
// 'destroy': IHook<any>; // TODO: define param template
|
||||||
// TODO: more timecycles here
|
// TODO: more timecycles here
|
||||||
};
|
}
|
||||||
|
@ -1,9 +1,69 @@
|
|||||||
|
import {
|
||||||
|
CircularLayoutOptions,
|
||||||
|
ConcentricLayoutOptions,
|
||||||
|
D3ForceLayoutOptions,
|
||||||
|
ForceAtlas2LayoutOptions,
|
||||||
|
ForceLayoutOptions,
|
||||||
|
FruchtermanLayoutOptions,
|
||||||
|
GridLayoutOptions,
|
||||||
|
MDSLayoutOptions,
|
||||||
|
RadialLayoutOptions,
|
||||||
|
RandomLayoutOptions,
|
||||||
|
} from '@antv/layout';
|
||||||
|
|
||||||
|
export type LayoutOptions = (
|
||||||
export interface LayoutCommonConfig {
|
| CircularLayout
|
||||||
type?: string;
|
| RandomLayout
|
||||||
gpuEnabled?: boolean;
|
| ConcentricLayout
|
||||||
|
| GridLayout
|
||||||
|
| MDSLayout
|
||||||
|
| RadialLayout
|
||||||
|
| FruchtermanLayout
|
||||||
|
| D3ForceLayout
|
||||||
|
| ForceLayout
|
||||||
|
| ForceAtlas2
|
||||||
|
) & {
|
||||||
workerEnabled?: boolean;
|
workerEnabled?: boolean;
|
||||||
// Works when workerEnabled is true, config it with a visitable url to avoid visiting online version.
|
animated?: boolean;
|
||||||
workerScriptURL?: string;
|
iterations?: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
interface CircularLayout extends CircularLayoutOptions {
|
||||||
|
type: 'circular';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RandomLayout extends RandomLayoutOptions {
|
||||||
|
type: 'random';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GridLayout extends GridLayoutOptions {
|
||||||
|
type: 'grid';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MDSLayout extends MDSLayoutOptions {
|
||||||
|
type: 'mds';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConcentricLayout extends ConcentricLayoutOptions {
|
||||||
|
type: 'concentric';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RadialLayout extends RadialLayoutOptions {
|
||||||
|
type: 'radial';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FruchtermanLayout extends FruchtermanLayoutOptions {
|
||||||
|
type: 'fruchterman' | 'fruchtermanGPU';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface D3ForceLayout extends D3ForceLayoutOptions {
|
||||||
|
type: 'd3force';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ForceLayout extends ForceLayoutOptions {
|
||||||
|
type: 'force' | 'gforce';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ForceAtlas2 extends ForceAtlas2LayoutOptions {
|
||||||
|
type: 'forceAtlas2';
|
||||||
|
}
|
||||||
|
@ -4,9 +4,9 @@ import { FetchDataConfig, GraphData, InlineDataConfig, TransformerFn } from "./d
|
|||||||
import { EdgeDisplayModel, EdgeEncode, EdgeModel, EdgeShapesEncode } from "./edge";
|
import { EdgeDisplayModel, EdgeEncode, EdgeModel, EdgeShapesEncode } from "./edge";
|
||||||
import { NodeDisplayModel, NodeEncode, NodeModel, NodeShapesEncode } from "./node";
|
import { NodeDisplayModel, NodeEncode, NodeModel, NodeShapesEncode } from "./node";
|
||||||
import { GraphAlignment } from "./view";
|
import { GraphAlignment } from "./view";
|
||||||
import { LayoutCommonConfig } from "./layout";
|
|
||||||
import { ComboDisplayModel, ComboEncode, ComboModel, ComboShapesEncode } from "./combo";
|
import { ComboDisplayModel, ComboEncode, ComboModel, ComboShapesEncode } from "./combo";
|
||||||
import { BehaviorOptionsOf, BehaviorRegistry } from "./behavior";
|
import { BehaviorOptionsOf, BehaviorRegistry } from "./behavior";
|
||||||
|
import { LayoutOptions } from "./layout";
|
||||||
|
|
||||||
type rendererName = 'canvas' | 'svg' | 'webgl';
|
type rendererName = 'canvas' | 'svg' | 'webgl';
|
||||||
|
|
||||||
@ -15,24 +15,32 @@ export interface Specification<B extends BehaviorRegistry> {
|
|||||||
container: string | HTMLElement;
|
container: string | HTMLElement;
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
renderer?: rendererName | {
|
renderer?:
|
||||||
type: rendererName,
|
| rendererName
|
||||||
pixelRatio: number,
|
| {
|
||||||
headless: boolean,
|
type: rendererName;
|
||||||
};
|
pixelRatio: number;
|
||||||
|
headless: boolean;
|
||||||
|
};
|
||||||
zoom?: number;
|
zoom?: number;
|
||||||
autoFit?: 'view' | 'center' | {
|
autoFit?:
|
||||||
position: Point,
|
| 'view'
|
||||||
alignment: GraphAlignment
|
| 'center'
|
||||||
};
|
| {
|
||||||
|
position: Point;
|
||||||
|
alignment: GraphAlignment;
|
||||||
|
};
|
||||||
optimizeThreshold?: number;
|
optimizeThreshold?: number;
|
||||||
|
|
||||||
/** data */
|
/** data */
|
||||||
data: GraphData | InlineDataConfig | FetchDataConfig; // TODO: more
|
data: GraphData | InlineDataConfig | FetchDataConfig; // TODO: more
|
||||||
transform?: string[] | {
|
transform?:
|
||||||
type: string,
|
| string[]
|
||||||
[param: string]: unknown // TODO: generate by plugins
|
| {
|
||||||
}[] | TransformerFn[];
|
type: string;
|
||||||
|
[param: string]: unknown; // TODO: generate by plugins
|
||||||
|
}[]
|
||||||
|
| TransformerFn[];
|
||||||
|
|
||||||
/** item */
|
/** item */
|
||||||
node?: ((data: NodeModel) => NodeDisplayModel) | NodeEncode;
|
node?: ((data: NodeModel) => NodeDisplayModel) | NodeEncode;
|
||||||
@ -51,7 +59,7 @@ export interface Specification<B extends BehaviorRegistry> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** layout */
|
/** layout */
|
||||||
layout?: LayoutCommonConfig | LayoutCommonConfig[]; // TODO: Config comes from @antv/layout
|
layout?: LayoutOptions | LayoutOptions[];
|
||||||
|
|
||||||
/** interaction */
|
/** interaction */
|
||||||
modes?: {
|
modes?: {
|
||||||
@ -65,7 +73,7 @@ export interface Specification<B extends BehaviorRegistry> {
|
|||||||
|
|
||||||
/** free plugins */
|
/** free plugins */
|
||||||
plugins?: {
|
plugins?: {
|
||||||
name: string,
|
name: string;
|
||||||
options: any; // TODO: configs from plugins
|
options: any; // TODO: configs from plugins
|
||||||
}[]
|
}[];
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Canvas } from '@antv/g';
|
import { Canvas } from '@antv/g';
|
||||||
import { Renderer as CanvasRenderer } from '@antv/g-canvas';
|
import { Renderer as CanvasRenderer } from '@antv/g-canvas';
|
||||||
import { Renderer as SVGRenderer } from '@antv/g-svg';
|
import { Renderer as SVGRenderer } from '@antv/g-svg';
|
||||||
// import { Renderer as WebGLRenderer } from '@antv/g-webgl';
|
import { Renderer as WebGLRenderer } from '@antv/g-webgl';
|
||||||
import { isString } from '@antv/util';
|
import { isString } from '@antv/util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -28,8 +28,7 @@ export const createCanvas = (
|
|||||||
Renderer = SVGRenderer;
|
Renderer = SVGRenderer;
|
||||||
break;
|
break;
|
||||||
case 'webgl':
|
case 'webgl':
|
||||||
// Renderer = WebGLRenderer;
|
Renderer = WebGLRenderer;
|
||||||
// TODO
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
Renderer = CanvasRenderer;
|
Renderer = CanvasRenderer;
|
||||||
@ -44,7 +43,7 @@ export const createCanvas = (
|
|||||||
canvasTag.style.height = `${height}px`;
|
canvasTag.style.height = `${height}px`;
|
||||||
canvasTag.style.position = 'fixed';
|
canvasTag.style.position = 'fixed';
|
||||||
const containerDOM = isString(container) ? document.getElementById('container') : container;
|
const containerDOM = isString(container) ? document.getElementById('container') : container;
|
||||||
containerDOM.appendChild(canvasTag);
|
containerDOM!.appendChild(canvasTag);
|
||||||
return new Canvas({
|
return new Canvas({
|
||||||
canvas: canvasTag,
|
canvas: canvasTag,
|
||||||
devicePixelRatio: pixelRatio,
|
devicePixelRatio: pixelRatio,
|
||||||
@ -58,4 +57,4 @@ export const createCanvas = (
|
|||||||
devicePixelRatio: pixelRatio,
|
devicePixelRatio: pixelRatio,
|
||||||
renderer: new Renderer()
|
renderer: new Renderer()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { BehaviorRegistry } from '../types/behavior';
|
import { BehaviorRegistry } from '../types/behavior';
|
||||||
import Graph from '../runtime/graph';
|
import Graph from '../runtime/graph';
|
||||||
import registery from '../stdlib';
|
import registry from '../stdlib';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extend graph class with custom libs (extendLibrary), and extendLibrary will be merged into useLib.
|
* Extend graph class with custom libs (extendLibrary), and extendLibrary will be merged into useLib.
|
||||||
@ -20,9 +20,9 @@ export const extend = <B1 extends BehaviorRegistry, B2 extends BehaviorRegistry>
|
|||||||
): typeof Graph<B1 & B2> => {
|
): typeof Graph<B1 & B2> => {
|
||||||
// merged the extendLibrary to useLib for global usage
|
// merged the extendLibrary to useLib for global usage
|
||||||
Object.keys(extendLibrary).forEach((cat) => {
|
Object.keys(extendLibrary).forEach((cat) => {
|
||||||
registery.useLib[cat] = Object.assign({}, registery.useLib[cat], extendLibrary[cat] || {});
|
registry.useLib[cat] = Object.assign({}, registry.useLib[cat], extendLibrary[cat] || {});
|
||||||
Object.keys(registery.useLib[cat]).forEach((type) => {
|
Object.keys(registry.useLib[cat]).forEach((type) => {
|
||||||
const extension = registery.useLib[cat][type];
|
const extension = registry.useLib[cat][type];
|
||||||
extension.type = type;
|
extension.type = type;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
841
packages/g6/tests/datasets/dataset1.ts
Normal file
841
packages/g6/tests/datasets/dataset1.ts
Normal file
@ -0,0 +1,841 @@
|
|||||||
|
import { GraphData } from '../../src';
|
||||||
|
|
||||||
|
const data: GraphData = {
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
id: 'Argentina',
|
||||||
|
data: {
|
||||||
|
name: 'Argentina',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Australia',
|
||||||
|
data: {
|
||||||
|
name: 'Australia',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Belgium',
|
||||||
|
data: {
|
||||||
|
name: 'Belgium',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Brazil',
|
||||||
|
data: {
|
||||||
|
name: 'Brazil',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Colombia',
|
||||||
|
data: {
|
||||||
|
name: 'Colombia',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Costa Rica',
|
||||||
|
data: {
|
||||||
|
name: 'Costa Rica',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Croatia',
|
||||||
|
data: {
|
||||||
|
name: 'Croatia',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Denmark',
|
||||||
|
data: {
|
||||||
|
name: 'Denmark',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Egypt',
|
||||||
|
data: {
|
||||||
|
name: 'Egypt',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'England',
|
||||||
|
data: {
|
||||||
|
name: 'England',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'France',
|
||||||
|
data: {
|
||||||
|
name: 'France',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Germany',
|
||||||
|
data: {
|
||||||
|
name: 'Germany',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Iceland',
|
||||||
|
data: {
|
||||||
|
name: 'Iceland',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'IR Iran',
|
||||||
|
data: {
|
||||||
|
name: 'IR Iran',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Japan',
|
||||||
|
data: {
|
||||||
|
name: 'Japan',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Korea Republic',
|
||||||
|
data: {
|
||||||
|
name: 'Korea Republic',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Mexico',
|
||||||
|
data: {
|
||||||
|
name: 'Mexico',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Morocco',
|
||||||
|
data: {
|
||||||
|
name: 'Morocco',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Nigeria',
|
||||||
|
data: {
|
||||||
|
name: 'Nigeria',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Panama',
|
||||||
|
data: {
|
||||||
|
name: 'Panama',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Peru',
|
||||||
|
data: {
|
||||||
|
name: 'Peru',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Poland',
|
||||||
|
data: {
|
||||||
|
name: 'Poland',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Portugal',
|
||||||
|
data: {
|
||||||
|
name: 'Portugal',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Russia',
|
||||||
|
data: {
|
||||||
|
name: 'Russia',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Saudi Arabia',
|
||||||
|
data: {
|
||||||
|
name: 'Saudi Arabia',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Senegal',
|
||||||
|
data: {
|
||||||
|
name: 'Senegal',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Serbia',
|
||||||
|
data: {
|
||||||
|
name: 'Serbia',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Spain',
|
||||||
|
data: {
|
||||||
|
name: 'Spain',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Sweden',
|
||||||
|
data: {
|
||||||
|
name: 'Sweden',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Switzerland',
|
||||||
|
data: {
|
||||||
|
name: 'Switzerland',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Tunisia',
|
||||||
|
data: {
|
||||||
|
name: 'Tunisia',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Uruguay',
|
||||||
|
data: {
|
||||||
|
name: 'Uruguay',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
id: '0',
|
||||||
|
target: 'Russia',
|
||||||
|
source: 'Saudi Arabia',
|
||||||
|
data: {
|
||||||
|
target_score: 5,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
target: 'Uruguay',
|
||||||
|
source: 'Egypt',
|
||||||
|
data: {
|
||||||
|
target_score: 1,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
target: 'Russia',
|
||||||
|
source: 'Egypt',
|
||||||
|
data: {
|
||||||
|
target_score: 3,
|
||||||
|
source_score: 1,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
target: 'Uruguay',
|
||||||
|
source: 'Saudi Arabia',
|
||||||
|
data: {
|
||||||
|
target_score: 1,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
target: 'Uruguay',
|
||||||
|
source: 'Russia',
|
||||||
|
data: {
|
||||||
|
target_score: 3,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
target: 'Saudi Arabia',
|
||||||
|
source: 'Egypt',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 1,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '6',
|
||||||
|
target: 'IR Iran',
|
||||||
|
source: 'Morocco',
|
||||||
|
data: {
|
||||||
|
target_score: 1,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '7',
|
||||||
|
target: 'Portugal',
|
||||||
|
source: 'Spain',
|
||||||
|
data: {
|
||||||
|
target_score: 3,
|
||||||
|
source_score: 3,
|
||||||
|
directed: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '8',
|
||||||
|
target: 'Portugal',
|
||||||
|
source: 'Morocco',
|
||||||
|
data: {
|
||||||
|
target_score: 1,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '9',
|
||||||
|
target: 'Spain',
|
||||||
|
source: 'IR Iran',
|
||||||
|
data: {
|
||||||
|
target_score: 1,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '10',
|
||||||
|
target: 'IR Iran',
|
||||||
|
source: 'Portugal',
|
||||||
|
data: {
|
||||||
|
target_score: 1,
|
||||||
|
source_score: 1,
|
||||||
|
directed: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '11',
|
||||||
|
target: 'Spain',
|
||||||
|
source: 'Morocco',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 2,
|
||||||
|
directed: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '12',
|
||||||
|
target: 'France',
|
||||||
|
source: 'Australia',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 1,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '13',
|
||||||
|
target: 'Denmark',
|
||||||
|
source: 'Peru',
|
||||||
|
data: {
|
||||||
|
target_score: 1,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '14',
|
||||||
|
target: 'Denmark',
|
||||||
|
source: 'Australia',
|
||||||
|
data: {
|
||||||
|
target_score: 1,
|
||||||
|
source_score: 1,
|
||||||
|
directed: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '15',
|
||||||
|
target: 'France',
|
||||||
|
source: 'Peru',
|
||||||
|
data: {
|
||||||
|
target_score: 1,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '16',
|
||||||
|
target: 'Denmark',
|
||||||
|
source: 'France',
|
||||||
|
data: {
|
||||||
|
target_score: 0,
|
||||||
|
source_score: 0,
|
||||||
|
directed: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '17',
|
||||||
|
target: 'Peru',
|
||||||
|
source: 'Australia',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '18',
|
||||||
|
target: 'Argentina',
|
||||||
|
source: 'Iceland',
|
||||||
|
data: {
|
||||||
|
target_score: 1,
|
||||||
|
source_score: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '19',
|
||||||
|
target: 'Croatia',
|
||||||
|
source: 'Nigeria',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '20',
|
||||||
|
target: 'Croatia',
|
||||||
|
source: 'Argentina',
|
||||||
|
data: {
|
||||||
|
target_score: 3,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '21',
|
||||||
|
target: 'Nigeria',
|
||||||
|
source: 'Iceland',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '22',
|
||||||
|
target: 'Argentina',
|
||||||
|
source: 'Nigeria',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 1,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '23',
|
||||||
|
target: 'Croatia',
|
||||||
|
source: 'Iceland',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 1,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '24',
|
||||||
|
target: 'Serbia',
|
||||||
|
source: 'Costa Rica',
|
||||||
|
data: {
|
||||||
|
target_score: 1,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '25',
|
||||||
|
target: 'Brazil',
|
||||||
|
source: 'Switzerland',
|
||||||
|
data: {
|
||||||
|
target_score: 1,
|
||||||
|
source_score: 1,
|
||||||
|
directed: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '26',
|
||||||
|
target: 'Brazil',
|
||||||
|
source: 'Costa Rica',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '27',
|
||||||
|
target: 'Switzerland',
|
||||||
|
source: 'Serbia',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 1,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '28',
|
||||||
|
target: 'Brazil',
|
||||||
|
source: 'Serbia',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '29',
|
||||||
|
target: 'Switzerland',
|
||||||
|
source: 'Costa Rica',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 2,
|
||||||
|
directed: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '30',
|
||||||
|
target: 'Mexico',
|
||||||
|
source: 'Germany',
|
||||||
|
data: {
|
||||||
|
target_score: 1,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '31',
|
||||||
|
target: 'Sweden',
|
||||||
|
source: 'Korea Republic',
|
||||||
|
data: {
|
||||||
|
target_score: 1,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '32',
|
||||||
|
target: 'Mexico',
|
||||||
|
source: 'Korea Republic',
|
||||||
|
data: {
|
||||||
|
target_score: 1,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '33',
|
||||||
|
target: 'Germany',
|
||||||
|
source: 'Sweden',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 1,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '34',
|
||||||
|
target: 'Korea Republic',
|
||||||
|
source: 'Germany',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '35',
|
||||||
|
target: 'Sweden',
|
||||||
|
source: 'Mexico',
|
||||||
|
data: {
|
||||||
|
target_score: 3,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '36',
|
||||||
|
target: 'Belgium',
|
||||||
|
source: 'Panama',
|
||||||
|
data: {
|
||||||
|
target_score: 3,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '37',
|
||||||
|
target: 'England',
|
||||||
|
source: 'Tunisia',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 1,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '38',
|
||||||
|
target: 'Belgium',
|
||||||
|
source: 'Tunisia',
|
||||||
|
data: {
|
||||||
|
target_score: 5,
|
||||||
|
source_score: 2,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '39',
|
||||||
|
target: 'England',
|
||||||
|
source: 'Panama',
|
||||||
|
data: {
|
||||||
|
target_score: 6,
|
||||||
|
source_score: 1,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '40',
|
||||||
|
target: 'Belgium',
|
||||||
|
source: 'England',
|
||||||
|
data: {
|
||||||
|
target_score: 1,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '41',
|
||||||
|
target: 'Tunisia',
|
||||||
|
source: 'Panama',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 1,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '42',
|
||||||
|
target: 'Japan',
|
||||||
|
source: 'Colombia',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 1,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '43',
|
||||||
|
target: 'Senegal',
|
||||||
|
source: 'Poland',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 1,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '44',
|
||||||
|
target: 'Japan',
|
||||||
|
source: 'Senegal',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 2,
|
||||||
|
directed: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '45',
|
||||||
|
target: 'Colombia',
|
||||||
|
source: 'Poland',
|
||||||
|
data: {
|
||||||
|
target_score: 3,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '46',
|
||||||
|
target: 'Poland',
|
||||||
|
source: 'Japan',
|
||||||
|
data: {
|
||||||
|
target_score: 1,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '47',
|
||||||
|
target: 'Colombia',
|
||||||
|
source: 'Senegal',
|
||||||
|
data: {
|
||||||
|
target_score: 1,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '48',
|
||||||
|
target: 'Uruguay',
|
||||||
|
source: 'Portugal',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 1,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '49',
|
||||||
|
target: 'France',
|
||||||
|
source: 'Argentina',
|
||||||
|
data: {
|
||||||
|
target_score: 4,
|
||||||
|
source_score: 3,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '50',
|
||||||
|
target: 'Russia',
|
||||||
|
source: 'Spain',
|
||||||
|
data: {
|
||||||
|
target_score: 5,
|
||||||
|
source_score: 4,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '51',
|
||||||
|
target: 'Croatia',
|
||||||
|
source: 'Denmark',
|
||||||
|
data: {
|
||||||
|
target_score: 4,
|
||||||
|
source_score: 3,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '52',
|
||||||
|
target: 'Brazil',
|
||||||
|
source: 'Mexico',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '53',
|
||||||
|
target: 'Belgium',
|
||||||
|
source: 'Japan',
|
||||||
|
data: {
|
||||||
|
target_score: 3,
|
||||||
|
source_score: 2,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '54',
|
||||||
|
target: 'Sweden',
|
||||||
|
source: 'Switzerland',
|
||||||
|
data: {
|
||||||
|
target_score: 1,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '55',
|
||||||
|
target: 'England',
|
||||||
|
source: 'Colombia',
|
||||||
|
data: {
|
||||||
|
target_score: 4,
|
||||||
|
source_score: 3,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '56',
|
||||||
|
target: 'France',
|
||||||
|
source: 'Uruguay',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '57',
|
||||||
|
target: 'Belgium',
|
||||||
|
source: 'Brazil',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 1,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '58',
|
||||||
|
target: 'Croatia',
|
||||||
|
source: 'Russia',
|
||||||
|
data: {
|
||||||
|
target_score: 6,
|
||||||
|
source_score: 5,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '59',
|
||||||
|
target: 'England',
|
||||||
|
source: 'Sweden',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '60',
|
||||||
|
target: 'France',
|
||||||
|
source: 'Belgium',
|
||||||
|
data: {
|
||||||
|
target_score: 1,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '61',
|
||||||
|
target: 'Croatia',
|
||||||
|
source: 'England',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 1,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '62',
|
||||||
|
target: 'Belgium',
|
||||||
|
source: 'England',
|
||||||
|
data: {
|
||||||
|
target_score: 2,
|
||||||
|
source_score: 0,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '63',
|
||||||
|
target: 'France',
|
||||||
|
source: 'Croatia',
|
||||||
|
data: {
|
||||||
|
target_score: 4,
|
||||||
|
source_score: 2,
|
||||||
|
directed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export { data };
|
271
packages/g6/tests/unit/layout-spec.ts
Normal file
271
packages/g6/tests/unit/layout-spec.ts
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
import { Graph, Layout, LayoutMapping } from '@antv/layout';
|
||||||
|
import G6, { IGraph, stdLib } from '../../src/index';
|
||||||
|
import { data } from '../datasets/dataset1';
|
||||||
|
const container = document.createElement('div');
|
||||||
|
document.querySelector('body').appendChild(container);
|
||||||
|
|
||||||
|
describe('layout', () => {
|
||||||
|
let graph: IGraph<any>;
|
||||||
|
it('should apply circular layout correctly.', (done) => {
|
||||||
|
graph = new G6.Graph({
|
||||||
|
container,
|
||||||
|
width: 500,
|
||||||
|
height: 500,
|
||||||
|
type: 'graph',
|
||||||
|
data,
|
||||||
|
layout: {
|
||||||
|
type: 'circular',
|
||||||
|
center: [250, 250],
|
||||||
|
radius: 200,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
graph.once('afterlayout', () => {
|
||||||
|
const nodesData = graph.getAllNodesData();
|
||||||
|
expect(nodesData[0].data.x).toBe(450);
|
||||||
|
expect(nodesData[0].data.y).toBe(250);
|
||||||
|
|
||||||
|
graph.destroy();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should trigger re-layout by calling `layout` method manually.', (done) => {
|
||||||
|
graph = new G6.Graph({
|
||||||
|
container,
|
||||||
|
width: 500,
|
||||||
|
height: 500,
|
||||||
|
type: 'graph',
|
||||||
|
data,
|
||||||
|
layout: {
|
||||||
|
type: 'circular',
|
||||||
|
center: [250, 250],
|
||||||
|
radius: 200,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// first-time layout
|
||||||
|
graph.once('afterlayout', () => {
|
||||||
|
const nodesData = graph.getAllNodesData();
|
||||||
|
expect(nodesData[0].data.x).toBe(450);
|
||||||
|
expect(nodesData[0].data.y).toBe(250);
|
||||||
|
|
||||||
|
// re-layout
|
||||||
|
graph.once('afterlayout', () => {
|
||||||
|
const nodesData = graph.getAllNodesData();
|
||||||
|
expect(nodesData[0].data.x).toBe(350);
|
||||||
|
expect(nodesData[0].data.y).toBe(250);
|
||||||
|
|
||||||
|
graph.destroy();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
graph.layout({
|
||||||
|
type: 'circular',
|
||||||
|
center: [250, 250],
|
||||||
|
radius: 100, // change radius here
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should trigger re-layout by calling `changeData` method manually.', (done) => {
|
||||||
|
graph = new G6.Graph({
|
||||||
|
container,
|
||||||
|
width: 500,
|
||||||
|
height: 500,
|
||||||
|
type: 'graph',
|
||||||
|
data,
|
||||||
|
layout: {
|
||||||
|
type: 'circular',
|
||||||
|
center: [250, 250],
|
||||||
|
radius: 200,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// first-time layout
|
||||||
|
graph.once('afterlayout', () => {
|
||||||
|
const nodesData = graph.getAllNodesData();
|
||||||
|
expect(nodesData[0].data.x).toBe(450);
|
||||||
|
expect(nodesData[0].data.y).toBe(250);
|
||||||
|
|
||||||
|
// re-layout
|
||||||
|
graph.once('afterlayout', () => {
|
||||||
|
const nodesData = graph.getAllNodesData();
|
||||||
|
expect(nodesData[0].data.x).toBe(250);
|
||||||
|
expect(nodesData[0].data.y).toBe(250);
|
||||||
|
|
||||||
|
graph.destroy();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Only one single node.
|
||||||
|
const newData = {
|
||||||
|
nodes: [{ id: 'node13', data: { x: 50, y: 50 } }],
|
||||||
|
edges: [{ id: 'edge1', source: 'node13', target: 'node13', data: {} }],
|
||||||
|
};
|
||||||
|
graph.changeData(newData);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should run layout in WebWorker with `workerEnabled`.', (done) => {
|
||||||
|
graph = new G6.Graph({
|
||||||
|
container,
|
||||||
|
width: 500,
|
||||||
|
height: 500,
|
||||||
|
type: 'graph',
|
||||||
|
data,
|
||||||
|
layout: {
|
||||||
|
type: 'circular',
|
||||||
|
workerEnabled: true,
|
||||||
|
center: [250, 250],
|
||||||
|
radius: 200,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
graph.once('afterlayout', () => {
|
||||||
|
const nodesData = graph.getAllNodesData();
|
||||||
|
expect(nodesData[0].data.x).toBe(450);
|
||||||
|
expect(nodesData[0].data.y).toBe(250);
|
||||||
|
|
||||||
|
// re-layout
|
||||||
|
graph.once('afterlayout', () => {
|
||||||
|
const nodesData = graph.getAllNodesData();
|
||||||
|
expect(nodesData[0].data.x).toBe(350);
|
||||||
|
expect(nodesData[0].data.y).toBe(250);
|
||||||
|
|
||||||
|
graph.destroy();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
graph.layout({
|
||||||
|
type: 'circular',
|
||||||
|
center: [250, 250],
|
||||||
|
radius: 100, // change radius here
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display the layout process with `animated`.', (done) => {
|
||||||
|
graph = new G6.Graph({
|
||||||
|
container,
|
||||||
|
width: 500,
|
||||||
|
height: 500,
|
||||||
|
type: 'graph',
|
||||||
|
data,
|
||||||
|
layout: {
|
||||||
|
type: 'd3force',
|
||||||
|
animated: true,
|
||||||
|
center: [250, 250],
|
||||||
|
preventOverlap: true,
|
||||||
|
nodeSize: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
graph.once('afterlayout', () => {
|
||||||
|
const nodesData = graph.getAllNodesData();
|
||||||
|
expect(nodesData.every((node) => node.data.x > 0 && node.data.y > 0)).toBeTruthy();
|
||||||
|
graph.destroy();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should stop animated layout process with `stopLayout`.', (done) => {
|
||||||
|
graph = new G6.Graph({
|
||||||
|
container,
|
||||||
|
width: 500,
|
||||||
|
height: 500,
|
||||||
|
type: 'graph',
|
||||||
|
data,
|
||||||
|
layout: {
|
||||||
|
type: 'd3force',
|
||||||
|
animated: true,
|
||||||
|
center: [250, 250],
|
||||||
|
preventOverlap: true,
|
||||||
|
nodeSize: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
graph.stopLayout();
|
||||||
|
|
||||||
|
const nodesData = graph.getAllNodesData();
|
||||||
|
expect(nodesData.every((node) => node.data.x > 0 && node.data.y > 0)).toBeTruthy();
|
||||||
|
|
||||||
|
graph.destroy();
|
||||||
|
done();
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should manually steps the simulation with `iterations` and `animated` disabled.', (done) => {
|
||||||
|
graph = new G6.Graph({
|
||||||
|
container,
|
||||||
|
width: 500,
|
||||||
|
height: 500,
|
||||||
|
type: 'graph',
|
||||||
|
data,
|
||||||
|
layout: {
|
||||||
|
type: 'd3force',
|
||||||
|
animated: false,
|
||||||
|
center: [250, 250],
|
||||||
|
preventOverlap: true,
|
||||||
|
nodeSize: 20,
|
||||||
|
iterations: 1000,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
graph.once('afterlayout', () => {
|
||||||
|
const nodesData = graph.getAllNodesData();
|
||||||
|
expect(nodesData.every((node) => node.data.x > 0 && node.data.y > 0)).toBeTruthy();
|
||||||
|
|
||||||
|
graph.destroy();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow registering custom layout at runtime.', (done) => {
|
||||||
|
// Put all nodes at `[0, 0]`.
|
||||||
|
class MyCustomLayout implements Layout<{}> {
|
||||||
|
async assign(graph: Graph, options?: {}): Promise<void> {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
async execute(graph: Graph, options?: {}): Promise<LayoutMapping> {
|
||||||
|
const nodes = graph.getAllNodes();
|
||||||
|
return {
|
||||||
|
nodes: nodes.map((node) => ({
|
||||||
|
id: node.id,
|
||||||
|
data: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
edges: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
options: {};
|
||||||
|
id: 'myCustomLayout';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register custom layout
|
||||||
|
stdLib.layouts['myCustomLayout'] = MyCustomLayout;
|
||||||
|
|
||||||
|
graph = new G6.Graph({
|
||||||
|
container,
|
||||||
|
width: 500,
|
||||||
|
height: 500,
|
||||||
|
type: 'graph',
|
||||||
|
data,
|
||||||
|
layout: {
|
||||||
|
// @ts-ignore
|
||||||
|
type: 'myCustomLayout',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
graph.once('afterlayout', () => {
|
||||||
|
const nodesData = graph.getAllNodesData();
|
||||||
|
expect(nodesData.every((node) => node.data.x === 0 && node.data.y === 0)).toBeTruthy();
|
||||||
|
|
||||||
|
graph.destroy();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user