fix: error edge link positions for circle combo with size config (#4216)

* fix: error edge link positions for circle combo with size config, closes: #4193;

* fix: indented layout with different node widths, closes: #4200; feat: indented layout with align config to tell the node drawing alignment;

* chore: update verison num

* chore: update tests
This commit is contained in:
Yanyan Wang 2023-01-30 11:36:14 +08:00 committed by GitHub
parent 57b11a93f7
commit 7cdc01cfa6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 451 additions and 321 deletions

View File

@ -1,5 +1,11 @@
# ChangeLog
### 4.8.4
- fix: error edge link positions for circle combo with size config, closes: #4193;
- fix: indented layout with different node widths, closes: #4200;
- feat: indented layout with align config to tell the node drawing alignment;
### 4.8.3
- fix: unexpected error occurs when points of a hull are all duplicated;

View File

@ -1,6 +1,6 @@
{
"name": "@antv/g6-core",
"version": "0.8.3",
"version": "0.8.4",
"description": "A Graph Visualization Framework in JavaScript",
"keywords": [
"antv",

View File

@ -64,7 +64,7 @@ const colorSet = {
};
export default {
version: '0.8.3',
version: '0.8.4',
rootContainerClassName: 'root-container',
nodeContainerClassName: 'node-container',
edgeContainerClassName: 'edge-container',

View File

@ -28,7 +28,7 @@ export default class Combo extends Node implements ICombo {
// merge graph的item样式与数据模型中的样式
const newModel = model;
const size = {
r: bbox.width / 2 || Global.defaultCombo.size[0] / 2,
r: (Math.max(bbox.width, bbox.height) || Math.max(...Global.defaultCombo.size)) / 2, // bbox.width / 2 || Global.defaultCombo.size[0] / 2,
width: bbox.width || Global.defaultCombo.size[0],
height: bbox.height || Global.defaultCombo.size[1],
};
@ -60,33 +60,15 @@ export default class Combo extends Node implements ICombo {
const bbox = getBBox(keyShape, group);
bbox.centerX = (bbox.minX + bbox.maxX) / 2;
bbox.centerY = (bbox.minY + bbox.maxY) / 2;
const cacheSize = this.get(CACHE_SIZE);
const cacheBBox = this.get(CACHE_BBOX) || {};
const oriX = cacheBBox.x;
const oriY = cacheBBox.x;
if (cacheSize) {
cacheSize.width = Math.max(cacheSize.width, bbox.width);
cacheSize.height = Math.max(cacheSize.height, bbox.height);
const type: string = keyShape.get('type');
if (type === 'circle') {
bbox.width = cacheSize.r * 2;
bbox.height = cacheSize.r * 2;
} else {
bbox.width = cacheSize.width;
bbox.height = cacheSize.height;
}
bbox.minX = bbox.centerX - bbox.width / 2;
bbox.minY = bbox.centerY - bbox.height / 2;
bbox.maxX = bbox.centerX + bbox.width / 2;
bbox.maxY = bbox.centerY + bbox.height / 2;
} else {
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;
}
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;
bbox.x = bbox.minX;
bbox.y = bbox.minY;
if (bbox.x !== oriX || bbox.y !== oriY) this.set(CACHE_ANCHOR_POINTS, null);

View File

@ -326,7 +326,11 @@ export interface ModeOption {
minZoom?: number;
enableOptimize?: boolean;
enableDebounce?: boolean;
allowDragOnItem?: boolean;
allowDragOnItem?: boolean | {
node?: boolean,
edge?: boolean,
combo?: boolean
};
optimizeZoom?: number;
multiple?: boolean;
activeState?: string;

View File

@ -1557,13 +1557,10 @@ describe('redo stack & undo stack', () => {
graph.render();
it('fill undo stack', () => {
// redo 后undo stack 有一条数据
let stackData = graph.getStackData();
let undoStack = stackData.undoStack;
let redoStack = stackData.redoStack;
expect(undoStack.length).toBe(1);
expect(undoStack[0].action).toEqual('render');
expect(undoStack[0].data.after.nodes.length).toEqual(2);
expect(undoStack.length).toBe(0);
expect(redoStack.length).toBe(0);
// update 后undo stack 中有 2 条数据,一条 render一条 update
@ -1574,7 +1571,7 @@ describe('redo stack & undo stack', () => {
stackData = graph.getStackData();
undoStack = stackData.undoStack;
expect(undoStack.length).toBe(2);
expect(undoStack.length).toBe(1);
let firstStackData = undoStack[0];
expect(firstStackData.action).toEqual('update');
@ -1590,7 +1587,7 @@ describe('redo stack & undo stack', () => {
stackData = graph.getStackData();
undoStack = stackData.undoStack;
expect(undoStack.length).toBe(3);
expect(undoStack.length).toBe(2);
firstStackData = undoStack[0];
expect(firstStackData.action).toEqual('update');
@ -1608,7 +1605,7 @@ describe('redo stack & undo stack', () => {
stackData = graph.getStackData();
undoStack = stackData.undoStack;
expect(undoStack.length).toBe(4);
expect(undoStack.length).toBe(3);
firstStackData = undoStack[0];
expect(firstStackData.action).toEqual('add');
@ -1621,7 +1618,7 @@ describe('redo stack & undo stack', () => {
stackData = graph.getStackData();
undoStack = stackData.undoStack;
expect(undoStack.length).toBe(5);
expect(undoStack.length).toBe(4);
firstStackData = undoStack[0];
expect(firstStackData.action).toEqual('visible');
@ -1632,7 +1629,7 @@ describe('redo stack & undo stack', () => {
stackData = graph.getStackData();
undoStack = stackData.undoStack;
expect(undoStack.length).toBe(6);
expect(undoStack.length).toBe(5);
firstStackData = undoStack[0];
expect(firstStackData.action).toEqual('delete');
@ -1645,7 +1642,7 @@ describe('redo stack & undo stack', () => {
stackData = graph.getStackData();
undoStack = stackData.undoStack;
expect(undoStack.length).toBe(7);
expect(undoStack.length).toBe(6);
firstStackData = undoStack[0];
expect(firstStackData.action).toEqual('addItems');

View File

@ -544,32 +544,32 @@ describe('hierarchy data 2: combo A has 2 children: an empty combo B, a node', (
expect(comboAModel.x).toBe(100);
expect(comboAModel.y).toBe(200);
// the child combo follow the parent
expect(comboBModel.x).toBe(232.75);
expect(comboBModel.y).toBe(377.75);
expect(nodeModel.x).toBe(-57.25);
expect(nodeModel.y).toBe(-2.25);
expect(comboBModel.x).toBe(232.5);
expect(comboBModel.y).toBe(377.5);
expect(nodeModel.x).toBe(-57.5);
expect(nodeModel.y).toBe(-2.5);
});
it('move child empty combo B', () => {
graph.updateItem('B', { x: 330, y: 120 });
expect(comboBModel.x).toBe(330);
expect(comboBModel.y).toBe(120);
// the sibling node is not changed
expect(nodeModel.x).toBe(-57.25);
expect(nodeModel.y).toBe(-2.25);
expect(nodeModel.x).toBe(-57.5);
expect(nodeModel.y).toBe(-2.5);
// the parent combo follows the children
graph.updateCombos();
expect(comboAModel.x).toBe(148.625);
expect(comboAModel.y).toBe(71.125);
expect(comboAModel.x).toBe(148.75);
expect(comboAModel.y).toBe(71.25);
});
it('move parent combo A', () => { // done
graph.updateItem('A', { x: 450, y: 350 });
expect(comboAModel.x).toBe(450);
expect(comboAModel.y).toBe(350);
// the child follow the parent
expect(comboBModel.x).toBe(450 - 148.625 + 330);
expect(comboBModel.y).toBe(350 - 71.125 + 120);
expect(nodeModel.x).toBe(450 - 148.625 - 57.25);
expect(nodeModel.y).toBe(350 - 71.125 - 2.25);
expect(comboBModel.x).toBe(450 - 148.75 + 330);
expect(comboBModel.y).toBe(350 - 71.25 + 120);
expect(nodeModel.x).toBe(450 - 148.75 - 57.5);
expect(nodeModel.y).toBe(350 - 71.25 - 2.5);
});
it('parent combo without pos, children with pos', () => {
const testData = clone(data);
@ -591,8 +591,8 @@ describe('hierarchy data 2: combo A has 2 children: an empty combo B, a node', (
expect(nodeModel.x).toBe(10);
expect(nodeModel.y).toBe(20);
// A has no position, follows the child
expect(comboAModel.x).toBe(167.25);
expect(comboAModel.y).toBe(222.25);
expect(comboAModel.x).toBe(167.5);
expect(comboAModel.y).toBe(222.5);
});
it('parent combo with pos, child combo without pos', () => {
const testData = clone(data);
@ -670,16 +670,16 @@ describe('hierarchy data3: combo A has 2 children: combo B with 2 nodes, 2 nodes
expect(comboAModel.x).toBe(100);
expect(comboAModel.y).toBe(200);
// the child combo follow the parent
expect(comboBModel.x).toBe(161.7898448916085);
expect(comboBModel.y).toBe(321.7898448916085);
expect(nodeModels[0].x).toBe(1.7898448916085101);
expect(nodeModels[0].y).toBe(41.78984489160848);
expect(nodeModels[1].x).toBe(11.78984489160851);
expect(nodeModels[1].y).toBe(51.78984489160848);
expect(nodeModels[2].x).toBe(156.7898448916085);
expect(nodeModels[2].y).toBe(316.7898448916085);
expect(nodeModels[3].x).toBe(166.7898448916085);
expect(nodeModels[3].y).toBe(326.7898448916085);
expect(comboBModel.x).toBe(161.5398448916085);
expect(comboBModel.y).toBe(321.5398448916085);
expect(nodeModels[0].x).toBe(1.5398448916085101);
expect(nodeModels[0].y).toBe(41.53984489160848);
expect(nodeModels[1].x).toBe(11.53984489160851);
expect(nodeModels[1].y).toBe(51.53984489160848);
expect(nodeModels[2].x).toBe(156.5398448916085);
expect(nodeModels[2].y).toBe(316.5398448916085);
expect(nodeModels[3].x).toBe(166.5398448916085);
expect(nodeModels[3].y).toBe(326.5398448916085);
});
it('move child combo B', () => {
graph.updateItem('B', { x: 330, y: 120 });
@ -687,10 +687,10 @@ describe('hierarchy data3: combo A has 2 children: combo B with 2 nodes, 2 nodes
expect(comboBModel.x).toBe(330);
expect(comboBModel.y).toBe(120);
// the sibling node is not changed
expect(nodeModels[0].x).toBe(1.7898448916085101);
expect(nodeModels[0].y).toBe(41.78984489160848);
expect(nodeModels[1].x).toBe(11.78984489160851);
expect(nodeModels[1].y).toBe(51.78984489160848);
expect(nodeModels[0].x).toBe(1.5398448916085101);
expect(nodeModels[0].y).toBe(41.53984489160848);
expect(nodeModels[1].x).toBe(11.53984489160851);
expect(nodeModels[1].y).toBe(51.53984489160848);
// the children nodes of combo B follow B
expect(nodeModels[2].x).toBe(325);
expect(nodeModels[2].y).toBe(115);
@ -698,21 +698,21 @@ describe('hierarchy data3: combo A has 2 children: combo B with 2 nodes, 2 nodes
expect(nodeModels[3].y).toBe(125);
// the parent combo follows the children
expect(comboAModel.x).toBe(184.10507755419576);
expect(comboAModel.y).toBe(99.10507755419573);
expect(comboAModel.x).toBe(184.23007755419576);
expect(comboAModel.y).toBe(99.23007755419573);
});
it('move parent combo A', (done) => {
graph.updateItem('A', { x: 150, y: 150 });
expect(comboAModel.x).toBe(150);
expect(comboAModel.y).toBe(150);
// the child follow the parent
const dx = 150 - 184.10507755419576, dy = 150 - 99.10507755419573;
const dx = 150 - 184.23007755419576, dy = 150 - 99.23007755419573;
expect(comboBModel.x).toBe(dx + 330);
expect(comboBModel.y).toBe(dy + 120);
expect(nodeModels[0].x).toBe(dx + 1.7898448916085101);
expect(nodeModels[0].y).toBe(dy + 41.78984489160848);
expect(nodeModels[1].x).toBe(dx + 11.78984489160851);
expect(nodeModels[1].y).toBe(dy + 51.78984489160848);
expect(nodeModels[0].x).toBe(dx + 1.5398448916085101);
expect(nodeModels[0].y).toBe(dy + 41.53984489160848);
expect(nodeModels[1].x).toBe(dx + 11.53984489160851);
expect(nodeModels[1].y).toBe(dy + 51.53984489160848);
expect(nodeModels[2].x).toBe(dx + 325);
expect(nodeModels[2].y).toBe(dy + 115);
expect(nodeModels[3].x).toBe(dx + 335);
@ -727,8 +727,8 @@ describe('hierarchy data3: combo A has 2 children: combo B with 2 nodes, 2 nodes
graph.updateCombos();
setTimeout(() => {
expect(Math.abs(comboA.getKeyShape().attr('r') - 105) < 1).toBe(true);
expect(comboAModel.x).toBe(21.092383668706375);
expect(comboAModel.y).toBe(64.09238366870638);
expect(comboAModel.x).toBe(21.154883668706375);
expect(comboAModel.y).toBe(63.654883668706375);
done();
}, 500);
}, 500);
@ -760,8 +760,8 @@ describe('hierarchy data3: combo A has 2 children: combo B with 2 nodes, 2 nodes
expect(nodeModels[3].x).toBe(305);
expect(nodeModels[3].y).toBe(405);
// A has no position, follows the child
expect(comboAModel.x).toBe(238.2101551083915);
expect(comboAModel.y).toBe(278.2101551083915);
expect(comboAModel.x).toBe(238.4601551083915);
expect(comboAModel.y).toBe(278.4601551083915);
});
it('parent combo with pos, child combo without pos', () => {
const testData = clone(data);
@ -824,16 +824,16 @@ describe('hierarchy data3: combo A has 2 children: combo B with 2 nodes, 2 nodes
setTimeout(() => {
expect(comboB.isVisible()).toBe(true);
// the child combo follow the parent
expect(comboBModel.x).toBe(161.7898448916085);
expect(comboBModel.y).toBe(321.7898448916085);
expect(nodeModels[0].x).toBe(1.7898448916085101);
expect(nodeModels[0].y).toBe(41.78984489160848);
expect(nodeModels[1].x).toBe(11.78984489160851);
expect(nodeModels[1].y).toBe(51.78984489160848);
expect(nodeModels[2].x).toBe(156.7898448916085);
expect(nodeModels[2].y).toBe(316.7898448916085);
expect(nodeModels[3].x).toBe(166.7898448916085);
expect(nodeModels[3].y).toBe(326.7898448916085);
expect(comboBModel.x).toBe(161.5398448916085);
expect(comboBModel.y).toBe(321.5398448916085);
expect(nodeModels[0].x).toBe(1.5398448916085101);
expect(nodeModels[0].y).toBe(41.53984489160848);
expect(nodeModels[1].x).toBe(11.53984489160851);
expect(nodeModels[1].y).toBe(51.53984489160848);
expect(nodeModels[2].x).toBe(156.5398448916085);
expect(nodeModels[2].y).toBe(316.5398448916085);
expect(nodeModels[3].x).toBe(166.5398448916085);
expect(nodeModels[3].y).toBe(326.5398448916085);
graph.destroy();
done()
}, 500)
@ -1011,7 +1011,7 @@ describe('hierarchy data5: combo A has 2 children: combo B with 2 nodes, 2 nodes
setTimeout(() => {
expect(comboModels[0].x).toBe(115.70219861834002);
expect(comboModels[0].y).toBe(120);
expect(Math.abs(combos[0].getKeyShape().attr('r') - 157) < 1).toBe(true);
expect(Math.abs(combos[0].getKeyShape().attr('r') - 158) < 1).toBe(true);
done();
}, 500)
});
@ -1144,7 +1144,7 @@ describe('hierarchy data6: combo A has 2 children: combo B with 2 nodes, 2 nodes
setTimeout(() => {
expect(comboModels[0].x).toBe(130);
expect(comboModels[0].y).toBe(120);
expect(Math.abs(combos[0].getKeyShape().attr('r') - 130) < 1).toBe(true);
expect(Math.abs(combos[0].getKeyShape().attr('r') - 132) < 1).toBe(true);
done();
}, 500)
});

View File

@ -1,6 +1,6 @@
{
"name": "@antv/g6-element",
"version": "0.8.3",
"version": "0.8.4",
"description": "A Graph Visualization Framework in JavaScript",
"keywords": [
"antv",
@ -61,7 +61,7 @@
},
"dependencies": {
"@antv/g-base": "^0.5.1",
"@antv/g6-core": "0.8.3",
"@antv/g6-core": "0.8.4",
"@antv/util": "~2.0.5"
},
"devDependencies": {

View File

@ -88,7 +88,7 @@ registerEdge(
const routeCfg = mix({}, defaultRouteCfg, cfg.routeCfg);
routeCfg.offset = style.offset;
let path = (this as any).getPath(points, source, target, radius, routeCfg);
let path = (this as any).getPath(points, source, target, radius, routeCfg, !Boolean(controlPoints));
if ((isArray(path) && path.length <= 1) || (isString(path) && path.indexOf('L') === -1)) {
path = 'M0 0, L0 0';
}
@ -133,7 +133,7 @@ registerEdge(
const routeCfg = mix({}, defaultRouteCfg, cfg.routeCfg);
routeCfg.offset = previousStyle.offset;
let path = (this as any).getPath(points, source, target, radius, routeCfg);
let path = (this as any).getPath(points, source, target, radius, routeCfg, !Boolean(controlPoints));
if ((isArray(path) && path.length <= 1) || (isString(path) && path.indexOf('L') === -1)) {
path = 'M0 0, L0 0';
}
@ -170,11 +170,12 @@ registerEdge(
target: INode,
radius: number,
routeCfg?: RouterCfg,
auto?: boolean, // whether calculate the path with A*
): Array<Array<string | number>> | string {
const { offset, obstacles } = routeCfg;
let simple = routeCfg.simple;
// 指定了控制点
if (!offset || points.length > 2) {
if (!offset || points.length > 2 || auto === false) {
if (radius) {
return getPathWithBorderRadiusByPolyline(points, radius);
}

View File

@ -1,6 +1,6 @@
{
"name": "@antv/g6",
"version": "4.8.3",
"version": "4.8.4",
"description": "A Graph Visualization Framework in JavaScript",
"keywords": [
"antv",
@ -66,7 +66,7 @@
]
},
"dependencies": {
"@antv/g6-pc": "0.8.3"
"@antv/g6-pc": "0.8.4"
},
"devDependencies": {
"@babel/core": "^7.7.7",

View File

@ -1,7 +1,7 @@
import G6 from '@antv/g6-pc';
G6.version = '4.8.3';
G6.version = '4.8.4';
export * from '@antv/g6-pc';
export default G6;
export const version = '4.8.3';
export const version = '4.8.4';

View File

@ -1,6 +1,6 @@
{
"name": "@antv/g6-pc",
"version": "0.8.3",
"version": "0.8.4",
"description": "A Graph Visualization Framework in JavaScript",
"keywords": [
"antv",
@ -75,9 +75,9 @@
"@antv/g-canvas": "^0.5.2",
"@antv/g-math": "^0.1.1",
"@antv/g-svg": "^0.5.1",
"@antv/g6-core": "0.8.3",
"@antv/g6-element": "0.8.3",
"@antv/g6-plugin": "0.8.3",
"@antv/g6-core": "0.8.4",
"@antv/g6-element": "0.8.4",
"@antv/g6-plugin": "0.8.4",
"@antv/hierarchy": "^0.6.7",
"@antv/layout": "^0.3.0",
"@antv/matrix-util": "^3.1.0-beta.3",

View File

@ -1,4 +1,5 @@
import { G6Event, IG6GraphEvent } from '@antv/g6-core';
import { isBoolean, isObject } from '@antv/util';
import { IGraph } from '../interface/graph';
import Util from '../util';
@ -136,9 +137,7 @@ export default {
}
if (self.keydown) return;
const target = e.target;
const targetIsCanvas = target && target.isCanvas && target.isCanvas();
if (!this.allowDragOnItem && !targetIsCanvas) return;
if (!this.allowDrag(e)) return;
self.origin = { x: e.clientX, y: e.clientY };
self.dragging = false;
@ -194,9 +193,7 @@ export default {
if (!this.mousedown) return;
const { graph } = this;
if (this.keydown) return;
const target = e.target;
const targetIsCanvas = target && target.isCanvas && target.isCanvas();
if (!this.allowDragOnItem && !targetIsCanvas) return;
if (!this.allowDrag(e)) return;
e = cloneEvent(e);
if (!this.origin) {
@ -310,4 +307,17 @@ export default {
this.dragging = false;
this.dragbegin = false;
},
allowDrag(evt: IG6GraphEvent) {
const target = evt.target;
const targetIsCanvas = target && target.isCanvas && target.isCanvas();
if (isBoolean(this.allowDragOnItem) && !this.allowDragOnItem && !targetIsCanvas) return false;
if (isObject(this.allowDragOnItem)) {
const { node, edge, combo } = this.allowDragOnItem;
const itemType = evt.item?.getType?.();
if (!node && itemType === 'node') return false;
if (!edge && itemType === 'edge') return false;
if (!combo && itemType === 'combo') return false;
}
return true;
}
};

View File

@ -1,4 +1,5 @@
import { G6Event, IG6GraphEvent } from '@antv/g6-core';
import { isBoolean, isObject } from '@antv/util';
const ALLOW_EVENTS = ['shift', 'ctrl', 'alt', 'control', 'meta'];
@ -25,9 +26,7 @@ export default {
},
onWheel(ev: IG6GraphEvent) {
const target = ev.target;
const targetIsCanvas = target && target.isCanvas && target.isCanvas();
if (!this.allowDragOnItem && !targetIsCanvas) return;
if (!this.allowDrag(ev)) return;
const graph = this.graph;
const zoomKeys = Array.isArray(this.zoomKey) ? [].concat(this.zoomKey) : [this.zoomKey];
if (zoomKeys.includes('control')) zoomKeys.push('ctrl');
@ -193,4 +192,17 @@ export default {
this.set('timeout', timeout);
}
},
allowDrag(evt: IG6GraphEvent) {
const target = evt.target;
const targetIsCanvas = target && target.isCanvas && target.isCanvas();
if (isBoolean(this.allowDragOnItem) && !this.allowDragOnItem && !targetIsCanvas) return false;
if (isObject(this.allowDragOnItem)) {
const { node, edge, combo } = this.allowDragOnItem;
const itemType = evt.item?.getType?.();
if (!node && itemType === 'node') return false;
if (!edge && itemType === 'edge') return false;
if (!combo && itemType === 'combo') return false;
}
return true;
}
};

View File

@ -7,7 +7,7 @@ const textColor = 'rgb(0, 0, 0)';
const colorSet = getColorsWithSubjectColor(subjectColor, backColor);
export default {
version: '0.8.3',
version: '0.8.4',
rootContainerClassName: 'root-container',
nodeContainerClassName: 'node-container',
edgeContainerClassName: 'edge-container',

View File

@ -10,6 +10,7 @@ import Global from '../global';
import { LayoutController, EventController } from './controller';
import { PluginBase } from '@antv/g6-plugin';
import { createDom } from '@antv/dom-util';
import { cloneGElement } from '../util/image';
const { transform } = ext;
const SVG = 'svg';
@ -376,7 +377,8 @@ export default class Graph extends AbstractGraph implements IGraph {
const vCanvas = renderer === 'svg' ? new GSVGCanvas(canvasOptions) : new GCanvas(canvasOptions);
const group = this.get('group');
const vGroup = group.clone();
// clone group and clone image shape's clip
const vGroup = cloneGElement(group);
let matrix = clone(vGroup.getMatrix());
if (!matrix) matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1];

View File

@ -44,7 +44,7 @@ export default class TreeGraph extends Graph implements ITreeGraph {
layout.type = 'dendrogram';
}
if (!layout.direction) {
layout.direction = 'TB';
layout.direction = layout.type === 'indented' ? 'LR' : 'TB';
}
if (layout.radial) {
return (data: any) => {

View File

@ -0,0 +1,34 @@
import { IElement } from "@antv/g-base";
/**
* Clone group and clone the clip shapes of image shapes.
* @param group group to be cloned
* @returns cloned group with same clipped shapes of original group
*/
export const cloneGElement = (element: IElement): IElement => {
const vElement = element.clone();
applyCloneClip(element, vElement);
return vElement;
}
/**
* Apply the clipShape for image shapes from original element to cloned one (clonedElement).
* @param element original element
* @param clonedElement cloned element of original element
*/
const applyCloneClip = (element: IElement, clonedElement: IElement) => {
if (element.isGroup() && clonedElement.isGroup()) {
element.get('children')?.forEach((child, i) => {
const clonedChild = clonedElement.get('children')[i];
applyCloneClip(child, clonedChild);
});
}
const type = element.get('type');
const clonedType = clonedElement.get('type');
if (type !== 'image' || clonedType !== 'image') return;
const clipShape = element.get('clipShape');
clonedElement.setClip({
type: clipShape.get('type'),
attrs: clipShape.attr()
});
}

View File

@ -292,7 +292,7 @@ describe('create-edge', () => {
let stackData = graph.getStackData();
const { undoStack, redoStack } = stackData;
expect(undoStack.length).toBe(1);
expect(undoStack.length).toBe(0);
expect(redoStack.length).toBe(0);
// cancel
@ -304,14 +304,14 @@ describe('create-edge', () => {
graph.emit('node:click', { x: 120, y: 120, item: node1 });
expect(graph.getEdges().length).toEqual(1);
stackData = graph.getStackData();
expect(stackData.undoStack.length).toBe(2);
expect(stackData.undoStack.length).toBe(1);
expect(stackData.redoStack.length).toBe(0);
// loop
graph.emit('node:click', { x: 100, y: 100, item: node0 });
graph.emit('node:click', { x: 100, y: 100, item: node0 });
stackData = graph.getStackData();
expect(stackData.undoStack.length).toBe(3);
expect(stackData.undoStack.length).toBe(2);
expect(stackData.redoStack.length).toBe(0);
expect(graph.getEdges().length).toEqual(2);
const loop = graph.getEdges()[1];

View File

@ -50,7 +50,6 @@ describe('drag-canvas', () => {
graph.on('canvas:dragend', () => {
start = false;
});
graph.paint();
graph.emit('mousedown', { clientX: 150, clientY: 150, target: graph.get('canvas') });
graph.emit('drag', { clientX: 150, clientY: 150, target: graph.get('canvas') });
graph.emit('drag', { clientX: 200, clientY: 200, target: graph.get('canvas') });
@ -64,7 +63,7 @@ describe('drag-canvas', () => {
expect(start).toBe(false);
graph.destroy();
});
it('drag canvas with allowDragOnItem', () => {
it('drag canvas with boolean allowDragOnItem', () => {
const graph = new Graph({
container: div,
width: 500,
@ -87,7 +86,6 @@ describe('drag-canvas', () => {
graph.on('canvas:dragend', () => {
start = false;
});
graph.paint();
graph.emit('mousedown', { clientX: 150, clientY: 150, target: graph.getNodes()[0] });
graph.emit('drag', { clientX: 150, clientY: 150, target: graph.getNodes()[0] });
graph.emit('drag', { clientX: 200, clientY: 200, target: graph.getNodes()[0] });
@ -101,6 +99,80 @@ describe('drag-canvas', () => {
expect(start).toBe(false);
graph.destroy();
});
it('drag canvas with object allowDragOnItem', () => {
const graph = new Graph({
container: div,
width: 500,
height: 500,
defaultEdge: {
size: 10
},
modes: {
default: [
{
type: 'drag-canvas',
allowDragOnItem: {
node: false,
edge: true
},
},
],
},
});
const gdata = {
...data,
combos: [{
id: 'combo1'
}]
};
gdata.nodes[0].comboId = 'combo1';
graph.data(gdata);
graph.render();
let start = false;
graph.on('canvas:dragstart', () => {
start = true;
});
graph.on('canvas:dragend', () => {
start = false;
});
// drag failed on node
graph.emit('mousedown', { clientX: 150, clientY: 150, target: graph.getNodes()[0], item: graph.getNodes()[0] });
graph.emit('drag', { clientX: 150, clientY: 150, target: graph.getNodes()[0], item: graph.getNodes()[0] });
graph.emit('drag', { clientX: 200, clientY: 200, target: graph.getNodes()[0], item: graph.getNodes()[0] });
expect(start).toBe(false);
graph.emit('drag', { clientX: 250, clientY: 250, target: graph.getNodes()[0], item: graph.getNodes()[0] });
expect(start).toBe(false);
let matrix = graph.get('group').getMatrix();
expect(matrix).toEqual(null);
graph.emit('dragend', {});
// drag success on edge
graph.emit('mousedown', { clientX: 150, clientY: 150, target: graph.getEdges()[0], item: graph.getEdges()[0] });
graph.emit('drag', { clientX: 150, clientY: 150, target: graph.getEdges()[0], item: graph.getEdges()[0] });
graph.emit('drag', { clientX: 200, clientY: 200, target: graph.getEdges()[0], item: graph.getEdges()[0] });
expect(start).toBe(true);
graph.emit('drag', { clientX: 250, clientY: 250, target: graph.getEdges()[0], item: graph.getEdges()[0] });
expect(start).toBe(true);
matrix = graph.get('group').getMatrix();
expect(matrix[6]).toEqual(100);
expect(matrix[7]).toEqual(100);
graph.emit('dragend', {});
expect(start).toBe(false);
// drag failed on combo
graph.emit('mousedown', { clientX: 150, clientY: 150, target: graph.getCombos()[0], item: graph.getCombos()[0] });
graph.emit('drag', { clientX: 150, clientY: 150, target: graph.getCombos()[0], item: graph.getCombos()[0] });
graph.emit('drag', { clientX: 200, clientY: 200, target: graph.getCombos()[0], item: graph.getCombos()[0] });
expect(start).toBe(false);
graph.emit('drag', { clientX: 250, clientY: 250, target: graph.getCombos()[0], item: graph.getCombos()[0] });
expect(start).toBe(false);
matrix = graph.get('group').getMatrix();
expect(matrix[6]).toEqual(100);
expect(matrix[7]).toEqual(100);
graph.emit('dragend', {});
graph.destroy();
});
it('prevent default drag behavior', () => {
const graph = new Graph({
container: div,
@ -126,7 +198,6 @@ describe('drag-canvas', () => {
graph.on('canvas:dragend', () => {
start = false;
});
graph.paint();
graph.emit('mousedown', { clientX: 150, clientY: 150, target: graph.get('canvas') });
graph.emit('drag', { clientX: 150, clientY: 150, target: graph.get('canvas') });
graph.emit('drag', { clientX: 200, clientY: 200, target: graph.get('canvas') });
@ -197,7 +268,6 @@ describe('drag-canvas', () => {
graph.on('canvas:dragend', () => {
start = false;
});
graph.paint();
graph.emit('mousedown', { clientX: 150, clientY: 150, target: graph.get('canvas') });
graph.emit('drag', { clientX: 150, clientY: 150, target: graph.get('canvas') });
graph.emit('drag', { clientX: 200, clientY: 200, target: graph.get('canvas') });
@ -227,7 +297,6 @@ describe('drag-canvas', () => {
});
let start = false;
graph.addItem('node', { x: 100, y: 100, color: '#666', type: 'rect', id: 'test' });
graph.paint();
graph.emit('mousedown', { clientX: 150, clientY: 150, target: graph.get('canvas') });
graph.emit('drag', { clientX: 150, clientY: 150, target: graph.get('canvas') });
graph.emit('drag', { clientX: 250, clientY: 250, target: graph.get('canvas') });
@ -517,7 +586,6 @@ describe('drag-canvas', () => {
graph.on('canvas:dragend', () => {
start = false;
});
graph.paint();
graph.emit('mousedown', { clientX: 150, clientY: 150, target: graph.get('canvas') });
graph.emit('drag', { clientX: 150, clientY: 150, target: graph.get('canvas') });
graph.emit('drag', { clientX: 200, clientY: 200, target: graph.get('canvas') });

View File

@ -88,7 +88,7 @@ describe('stack', () => {
graph.data(data);
graph.render();
it('initial collapsed and stack', () => {
expect(graph.getUndoStack().linkedList.head.value.action).toBe('render')
expect(graph.getUndoStack().linkedList.head).toBe(null)
})
it('drag combo stack', () => {
graph.on('canvas:click', e => {

View File

@ -136,5 +136,6 @@ describe('donut test', () => {
const fanShapes = Object.values(graph.findById('node').getContainer()['shapeMap']).filter(shape => shape.get('name').includes('fan-shape-'));
expect(fanShapes[0].attr('path')[0][1]).toBe(150 / 2 * 1.6 / 2); // (keyShapeR + 0.6 * keyShapeR) / 2
expect(fanShapes[0].attr('lineWidth')).toBe(150 / 2 * (1 - 0.6)); // keyShapeR - 0.6 * keyShapeR
graph.destroy();
});
});

View File

@ -1551,9 +1551,7 @@ describe('redo stack & undo stack', () => {
let stackData = graph.getStackData();
let undoStack = stackData.undoStack;
let redoStack = stackData.redoStack;
expect(undoStack.length).toBe(1);
expect(undoStack[0].action).toEqual('render');
expect(undoStack[0].data.after.nodes.length).toEqual(2);
expect(undoStack.length).toBe(0);
expect(redoStack.length).toBe(0);
// update 后undo stack 中有 2 条数据,一条 render一条 update
@ -1564,7 +1562,7 @@ describe('redo stack & undo stack', () => {
stackData = graph.getStackData();
undoStack = stackData.undoStack;
expect(undoStack.length).toBe(2);
expect(undoStack.length).toBe(1);
let firstStackData = undoStack[0];
expect(firstStackData.action).toEqual('update');
@ -1580,7 +1578,7 @@ describe('redo stack & undo stack', () => {
stackData = graph.getStackData();
undoStack = stackData.undoStack;
expect(undoStack.length).toBe(3);
expect(undoStack.length).toBe(2);
firstStackData = undoStack[0];
expect(firstStackData.action).toEqual('update');
@ -1598,7 +1596,7 @@ describe('redo stack & undo stack', () => {
stackData = graph.getStackData();
undoStack = stackData.undoStack;
expect(undoStack.length).toBe(4);
expect(undoStack.length).toBe(3);
firstStackData = undoStack[0];
expect(firstStackData.action).toEqual('add');
@ -1611,7 +1609,7 @@ describe('redo stack & undo stack', () => {
stackData = graph.getStackData();
undoStack = stackData.undoStack;
expect(undoStack.length).toBe(5);
expect(undoStack.length).toBe(4);
firstStackData = undoStack[0];
expect(firstStackData.action).toEqual('visible');
@ -1622,7 +1620,7 @@ describe('redo stack & undo stack', () => {
stackData = graph.getStackData();
undoStack = stackData.undoStack;
expect(undoStack.length).toBe(6);
expect(undoStack.length).toBe(5);
firstStackData = undoStack[0];
expect(firstStackData.action).toEqual('delete');

View File

@ -38,7 +38,8 @@ describe('random', () => {
width: 500,
height: 500,
fitView: true
})
});
G6.Util.traverseTree(layoutData, subtree => delete subtree.parent);
tree.data(layoutData);
tree.render();
expect(!isNaN(layoutData.x)).toBe(true);

View File

@ -1,6 +1,6 @@
{
"name": "@antv/g6-plugin",
"version": "0.8.3",
"version": "0.8.4",
"description": "G6 Plugin",
"main": "lib/index.js",
"module": "es/index.js",
@ -22,8 +22,8 @@
"@antv/g-base": "^0.5.1",
"@antv/g-canvas": "^0.5.2",
"@antv/g-svg": "^0.5.2",
"@antv/g6-core": "0.8.3",
"@antv/g6-element": "0.8.3",
"@antv/g6-core": "0.8.4",
"@antv/g6-element": "0.8.4",
"@antv/matrix-util": "^3.1.0-beta.3",
"@antv/scale": "^0.3.4",
"@antv/util": "^2.0.9",

View File

@ -10,59 +10,3 @@ graph.render();
```
Render the graph with data onto the canvas.
### graph.refresh()
Refresh the canvas when the **existing** data items' configurations is changed in the source data.
Attention: If there are some new nodes/edges/combos to be added or some nodes/edges/combos to be removed, use [graph.addItem](/en/docs/api/graphFunc/item/#graphadditemtype-model-stack) / [graph.removeItem](/en/docs/api/graphFunc/item/#graphremoveitemitem-stack) or [graph.changeData](/en/docs/api/graphFunc/data/#graphchangedatadata-stack) instead.
**Usage**
```javascript
graph.refresh();
```
### graph.paint()
Repaint the canvas. Use it after changing the item's style or state.
**Usage**
```javascript
const item = e.item;
const graph = this.graph;
const autoPaint = graph.get('autoPaint');
graph.setAutoPaint(false);
graph.setItemState(item, 'selected', true);
graph.paint();
graph.setAutoPaint(autoPaint);
```
### graph.setAutoPaint(auto)
Whether to repaint the canvas automatically after updating or deleting items.
**Parameters**
| Name | Type | Required | Description |
| ---- | ------- | -------- | -------------------------------------------- |
| auto | Boolean | true | Whether to repaint the canvas automatically. |
**Usage**
```javascript
const item = e.item;
const graph = this.graph;
const autoPaint = graph.get('autoPaint');
graph.setAutoPaint(false);
graph.setItemState(item, 'selected', true);
graph.paint();
graph.setAutoPaint(autoPaint);
```

View File

@ -12,63 +12,3 @@ order: 1
```javascript
graph.render();
```
### graph.refresh()
当源数据中**现有**节点/边/ Combo 的数据项发生配置的变更时,根据新数据刷新视图。
注意:节点/边/ Combo 数据的增删需要使用 [graph.addItem](/zh/docs/api/graphFunc/item/#graphadditemtype-model-stack) / [graph.removeItem](/zh/docs/api/graphFunc/item/#graphremoveitemitem-stack) 或 [graph.changeData](/zh/docs/api/graphFunc/data/#graphchangedatadata-stack)。
该方法无参数。
**用法**
```javascript
graph.refresh();
```
### graph.paint()
仅重新绘制画布。当设置了元素样式或状态后,通过调用 `paint()` 方法,让修改生效。
该方法无参数。
**用法**
```javascript
const item = e.item;
const graph = this.graph;
const autoPaint = graph.get('autoPaint');
graph.setAutoPaint(false);
graph.setItemState(item, 'selected', true);
graph.paint();
graph.setAutoPaint(autoPaint);
```
### graph.setAutoPaint(auto)
设置是否在更新/删除后自动重绘,一般搭配 `paint()` 方法使用。
**参数**
| 名称 | 类型 | 是否必选 | 描述 |
| ---- | ------- | -------- | ------------ |
| auto | Boolean | true | 是否自动重绘 |
**用法**
```javascript
const item = e.item;
const graph = this.graph;
const autoPaint = graph.get('autoPaint');
graph.setAutoPaint(false);
graph.setItemState(item, 'selected', true);
graph.paint();
graph.setAutoPaint(autoPaint);
```

View File

@ -41,7 +41,7 @@ const graph = new G6.Graph({
**Type**: Number<br />**Default**: 30<br />**Required**: false<br />**Description**: The diameter of the node. It is used for preventing node overlappings
## layoutCfg.minNodeSpacing
## layoutCfg.nodeSpacing
**Type**: Number<br />**Default**: 10<br />**Required**: false<br />**Description**: The minimum separation between adjacent circles

View File

@ -41,7 +41,7 @@ const graph = new G6.Graph({
**类型** Number<br />**默认值**30<br />**是否必须**false<br />**说明**:节点大小(直径)。用于防止节点重叠时的碰撞检测
## layoutCfg.minNodeSpacing
## layoutCfg.nodeSpacing
**类型** Number<br />**默认值**10<br />**是否必须**false<br />**说明**:环与环之间最小间距,用于调整半径

View File

@ -41,6 +41,22 @@ If you want to fix the positions for some nodes during calculation, assign `fx`
**Type**: Boolean<br />**示例**false<br />**Default**: false<br />**Default**: false<br />**Description**: Whether refresh the node positions on the canvas each iteration. If it is `true`, the nodes on the canvas will looks like animating with forces
## layoutCfg.preset
_Supported from v4.7.0_
**Type**:
```javascript
{
type: string; // preset layout name, could be any static layout like random, concentric, grid, circular, radial, and dagre
[key: string]: unkown; // corresponding configurations for the preset layout type
}
```
<br />
**Default**: undefined<br />**Required**: false<br />**Description**: Preset layout calculates intialize positions for nodes, and the force layout will start from the inited result. The quality of the force layout's result depends on the initial positions of nodes. Configuring a proper preset for a force layout will speed up the convergence of force layout, and enhance the quality in the same time. By default, the positions of nodes will be inited as grid
## layoutCfg.linkDistance
**Type**: Number / Function<br />**Default**: 1<br />**Required**: false<br />**Description**: The edge length

View File

@ -41,6 +41,22 @@ const graph = new G6.Graph({
**类型** Boolean<br />**示例**false<br />**默认值**false<br />**是否必须**false<br />**说明**:是否每次迭代都刷新画布,若为 `true` 则将表现出带有动画逐步布局的效果
## layoutCfg.preset
_自 G6 v4.7.0 起支持。_
**类型**:
```javascript
{
type: string; // 布局名称,可以是 random、concentric、grid、circular、radial、dagre 等静态布局
[key: string]: unkown; // 对应布局的配置项
}
```
<br />
**默认值**: undefined<br />**是否必须**: false<br />**说明**: 力导向布局的初始化布局,将先执行 preset 指定的布局,再进行力导向计算。由于力导向布局的结果非常依赖节点的初始位置,配置 preset 的可以给力导向布局一个好的初始化让力导向算法更快收敛、效果更好。默认情况下力导向的初始化为格子布局grid的结果
## layoutCfg.linkDistance
**类型** Number / Function<br />**默认值**1<br />**是否必须**false<br />**说明**:边长度

View File

@ -37,6 +37,23 @@ const graph = new G6.Graph({
**Type**: Boolean<br />**Default**: false<br />**Required**: false<br />**Description**: Whether to enable the web-worker in case layout calculation takes too long to block page interaction.
<span style="background-color: rgb(251, 233, 231); color: rgb(139, 53, 56)"><strong>⚠️ Notice:</strong></span> When `workerEnabled: true`, all the function type parameters are not supported.
## layoutCfg.preset
_Supported from v4.7.0_
**Type**:
```javascript
{
type: string; // preset layout name, could be any static layout like random, concentric, grid, circular, radial, and dagre
[key: string]: unkown; // corresponding configurations for the preset layout type
}
```
<br />
**Default**: undefined<br />**Required**: false<br />**Description**: Preset layout calculates intialize positions for nodes, and the force layout will start from the inited result. The quality of the force layout's result depends on the initial positions of nodes. Configuring a proper preset for a force layout will speed up the convergence of force layout, and enhance the quality in the same time. By default, the positions of nodes will be inited as grid
## layoutCfg.kr
**Type**: Number<br />**Default**: 5<br />**Required**: false<br />**Description**: Repulsive parameter, smaller the kr, more compact the graph

View File

@ -37,6 +37,22 @@ const graph = new G6.Graph({
**类型**: Boolean<br />**默认值**: false<br />**是否必须**: false<br />**说明**: 是否启用 web-worker 以防布局计算时间过长阻塞页面交互。
<span style="background-color: rgb(251, 233, 231); color: rgb(139, 53, 56)"><strong>⚠️ 注意:</strong></span> `workerEnabled: true` 时,不支持所有函数类型的参数。
## layoutCfg.preset
_自 G6 v4.7.0 起支持。_
**类型**:
```javascript
{
type: string; // 布局名称,可以是 random、concentric、grid、circular、radial、dagre 等静态布局
[key: string]: unkown; // 对应布局的配置项
}
```
<br />
**默认值**: undefined<br />**是否必须**: false<br />**说明**: 力导向布局的初始化布局,将先执行 preset 指定的布局,再进行力导向计算。由于力导向布局的结果非常依赖节点的初始位置,配置 preset 的可以给力导向布局一个好的初始化让力导向算法更快收敛、效果更好。默认情况下力导向的初始化为格子布局grid的结果
## layoutCfg.kr
**类型** Number<br />**默认值**5<br />**是否必须**false<br />**说明**斥力系数可用于调整布局的紧凑程度。kr 越大,布局越松散

View File

@ -32,6 +32,24 @@ If you want to fix the positions for some nodes during calculation, assign `fx`
**Type**: Array<br />**Example**: [ 0, 0 ]<br />**Default**: The center of the graph<br />**Required**: false<br />**Description**: The center of the layout
## layoutCfg.preset
_Supported from v4.7.0_
**Type**:
```javascript
{
type: string; // preset layout name, could be any static layout like random, concentric, grid, circular, radial, and dagre
[key: string]: unkown; // corresponding configurations for the preset layout type
}
```
<br />
**Default**: undefined<br />**Required**: false<br />**Description**: Preset layout calculates intialize positions for nodes, and the force layout will start from the inited result. The quality of the force layout's result depends on the initial positions of nodes. Configuring a proper preset for a force layout will speed up the convergence of force layout, and enhance the quality in the same time. By default, the positions of nodes will be inited as grid
## layoutCfg.maxIteration
**Type**: Number<br />**Default**: 1000<br />**Required**: false<br />**Description**: The maximum iteration number

View File

@ -32,6 +32,22 @@ const graph = new G6.Graph({
**类型** Array<br />**示例**[ 0, 0 ]<br />**默认值**:图的中心<br />**是否必须**false<br />**说明**:布局的中心
## layoutCfg.preset
_自 G6 v4.7.0 起支持。_
**类型**:
```javascript
{
type: string; // 布局名称,可以是 random、concentric、grid、circular、radial、dagre 等静态布局
[key: string]: unkown; // 对应布局的配置项
}
```
<br />
**默认值**: undefined<br />**是否必须**: false<br />**说明**: 力导向布局的初始化布局,将先执行 preset 指定的布局,再进行力导向计算。由于力导向布局的结果非常依赖节点的初始位置,配置 preset 的可以给力导向布局一个好的初始化让力导向算法更快收敛、效果更好。默认情况下力导向的初始化为格子布局grid的结果
## layoutCfg.maxIteration
**类型** Number<br />**默认值**1000<br />**是否必须**false<br />**说明**:最大迭代次数

View File

@ -38,6 +38,23 @@ If you want to fix the positions for some nodes during calculation, assign `fx`
**Type**: Array<br />**Example**: [ 0, 0 ]<br />**Default**: The center of the graph<br />**Required**: false<br />**Description**: The center of the layout
## layoutCfg.preset
_Supported from v4.7.0_
**Type**:
```javascript
{
type: string; // preset layout name, could be any static layout like random, concentric, grid, circular, radial, and dagre
[key: string]: unkown; // corresponding configurations for the preset layout type
}
```
<br />
**Default**: undefined<br />**Required**: false<br />**Description**: Preset layout calculates intialize positions for nodes, and the force layout will start from the inited result. The quality of the force layout's result depends on the initial positions of nodes. Configuring a proper preset for a force layout will speed up the convergence of force layout, and enhance the quality in the same time. By default, the positions of nodes will be inited as grid
## layoutCfg.linkDistance
**Type**: Number / Function<br />**Default**: 1<br />**Required**: false<br />**Description**: The edge length

View File

@ -38,6 +38,22 @@ const graph = new G6.Graph({
**类型** Array<br />**示例**[ 0, 0 ]<br />**默认值**:图的中心<br />**是否必须**false<br />**说明**:布局的中心
## layoutCfg.preset
_自 G6 v4.7.0 起支持。_
**类型**:
```javascript
{
type: string; // 布局名称,可以是 random、concentric、grid、circular、radial、dagre 等静态布局
[key: string]: unkown; // 对应布局的配置项
}
```
<br />
**默认值**: undefined<br />**是否必须**: false<br />**说明**: 力导向布局的初始化布局,将先执行 preset 指定的布局,再进行力导向计算。由于力导向布局的结果非常依赖节点的初始位置,配置 preset 的可以给力导向布局一个好的初始化让力导向算法更快收敛、效果更好。默认情况下力导向的初始化为格子布局grid的结果
## layoutCfg.linkDistance
**类型** Number / Function<br />**默认值**1<br />**是否必须**false<br />**说明**:边长度

View File

@ -46,6 +46,9 @@ The configurations of each layout algorithms are different. Please refer to corr
- If there are `x` and `y` in node data, the graph will render with these information;
- If there is no positions information in node data, the graph will arrange nodes with Random Layout by default.
If the worker is enabled, notice that worker will visit the latest online version of @antv/layout. If your application cannot reach the online resource, download and save the [layout script](https://unpkg.com/@antv/layout@latest/dist/layout.min.js), and put it on an address which is visitable for your application. And then config `workerScriptURL` with the address in `layout`.
## Instantiate Independently
The functions in this section should be concerned in these two situation:

View File

@ -49,6 +49,8 @@ const graph = new G6.Graph({
- 若数据中节点有位置信息(`x` 和 `y`),则按照数据的位置信息进行绘制;
- 若数据中节点没有位置信息,则默认使用 Random Layout 进行布局。
如果开启了 webworkerworker 使用的是 @antv/layout 线上的脚本,如果你的项目无法访问到线上资源,请保存 [layout 脚本](https://unpkg.com/@antv/layout@latest/dist/layout.min.js),并放在可以访问到的地址上,将 layout 的 `workerScriptURL` 配置为该地址即可。
## 单独使用布局
以下方法为通过 `const layout = new G6.Layout['layoutName']` 单独使用布局时,或自定义布局时可能需要复写的方法。如果上述两种情况,仅在实例化图时通过配置 `layout` 使用内置布局方法时,以下方法由 G6 控制并调用,用户不需要了解。

View File

@ -39,7 +39,6 @@ graph.on('node:click', (ev) => {
const node = ev.item;
console.log('before hide(), the nodevisible = ', node.get('visible'));
node.hide();
graph.paint();
console.log('after hide(), the node visible = ', node.get('visible'));
});
@ -48,7 +47,6 @@ graph.on('edge:click', (ev) => {
const edge = ev.item;
console.log('before hide(), the edge visible = ', edge.get('visible'));
edge.hide();
graph.paint();
console.log('after hide(), the edge visible = ', edge.get('visible'));
});
@ -62,6 +60,5 @@ graph.on('canvas:click', (ev) => {
edges.forEach((edge) => {
edge.show();
});
graph.paint();
});
```

View File

@ -39,7 +39,6 @@ graph.on('node:click', (ev) => {
const node = ev.item;
console.log('before hide(), the nodevisible = ', node.get('visible'));
node.hide();
graph.paint();
console.log('after hide(), the node visible = ', node.get('visible'));
});
@ -48,7 +47,6 @@ graph.on('edge:click', (ev) => {
const edge = ev.item;
console.log('before hide(), the edge visible = ', edge.get('visible'));
edge.hide();
graph.paint();
console.log('after hide(), the edge visible = ', edge.get('visible'));
});
@ -62,6 +60,5 @@ graph.on('canvas:click', (ev) => {
edges.forEach((edge) => {
edge.show();
});
graph.paint();
});
```

View File

@ -141,8 +141,6 @@ const nodes = graph.getNodes();
nodes.forEach((node) => {
node.toFront();
});
// Repaint the graph after shifting
graph.paint();
```
<br />Now, all the nodes are drawed on the top of edges:<br /><img src='https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*8TnuS7pkUfwAAAAAAAAAAABkARQnAQ' width=150 alt='img'/>
@ -192,8 +190,6 @@ graph.on('edge:mouseenter', (ev) => {
edge.toFront();
source.toFront();
target.toFront();
// Attention: the following code must be called to repaint the graph
graph.paint();
});
graph.on('edge:mouseleave', (ev) => {
@ -203,8 +199,6 @@ graph.on('edge:mouseleave', (ev) => {
edges.forEach((edge) => {
edge.toBack();
});
// Attention: the following code must be called to repaint the graph
graph.paint();
});
graph.on('node:mouseenter', (ev) => {
@ -218,8 +212,6 @@ graph.on('node:mouseenter', (ev) => {
edge.getSource().toFront();
edge.getTarget().toFront();
});
// Attention: the following code must be called to repaint the graph
graph.paint();
});
graph.on('node:mouseleave', (ev) => {
@ -229,7 +221,5 @@ graph.on('node:mouseleave', (ev) => {
edges.forEach((edge) => {
edge.toBack();
});
// Attention: the following code must be called to repaint the graph
graph.paint();
});
```

View File

@ -139,8 +139,6 @@ const nodes = graph.getNodes();
nodes.forEach((node) => {
node.toFront();
});
// 更改层级后需要重新绘制图
graph.paint();
```
<br />这样,所有节点被绘制在边上层:<br /><img src='https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*8TnuS7pkUfwAAAAAAAAAAABkARQnAQ' width=150 alt='img'/>
@ -190,8 +188,6 @@ graph.on('edge:mouseenter', (ev) => {
edge.toFront();
source.toFront();
target.toFront();
// 注意:必须调用以根据新的层级顺序重绘
graph.paint();
});
graph.on('edge:mouseleave', (ev) => {
@ -201,8 +197,6 @@ graph.on('edge:mouseleave', (ev) => {
edges.forEach((edge) => {
edge.toBack();
});
// 注意:必须调用以根据新的层级顺序重绘
graph.paint();
});
graph.on('node:mouseenter', (ev) => {
@ -216,8 +210,6 @@ graph.on('node:mouseenter', (ev) => {
edge.getSource().toFront();
edge.getTarget().toFront();
});
// 注意:必须调用以根据新的层级顺序重绘
graph.paint();
});
graph.on('node:mouseleave', (ev) => {
@ -227,7 +219,5 @@ graph.on('node:mouseleave', (ev) => {
edges.forEach((edge) => {
edge.toBack();
});
// 注意:必须调用以根据新的层级顺序重绘
graph.paint();
});
```

View File

@ -57,7 +57,6 @@ G6.registerBehavior('drag-canvas-exclude-lockedNode', {
lockedNodes.forEach((node) => {
node.get('group').translate(dx, dy);
});
this.graph.paint();
},
onMouseDown(e) {
if (this.keydown) {
@ -208,7 +207,6 @@ G6.registerBehavior('zoom-canvas-exclude-lockedNode', {
]);
node.get('group').setMatrix(matrix);
});
graph.paint();
graph.emit('wheelzoom', e);
},
});

View File

@ -57,7 +57,6 @@ G6.registerBehavior('drag-canvas-exclude-lockedNode', {
lockedNodes.forEach((node) => {
node.get('group').translate(dx, dy);
});
this.graph.paint();
},
onMouseDown(e) {
if (this.keydown) {
@ -208,7 +207,6 @@ G6.registerBehavior('zoom-canvas-exclude-lockedNode', {
]);
node.get('group').setMatrix(matrix);
});
graph.paint();
graph.emit('wheelzoom', e);
},
});

View File

@ -295,7 +295,7 @@ centripetalOptions: {
| --- | --- | --- | --- | --- |
| center | Array | [ 0, 0 ] | The center of the graph | The center of the layout |
| nodeSize | Number | 30 | 30 | The diameter of the node. It is used for preventing node overlappings |
| minNodeSpacing | Number | 10 | 10 | The minimum separation between adjacent circles |
| nodeSpacing | Number | 10 | 10 | The minimum separation between adjacent circles |
| preventOverlap | Boolean | false | false | Whether to prevent node overlappings. To activate preventing node overlappings, `nodeSize` is required, which is used for collide detection. The size in the node data will take effect if `nodeSize` is not assigned. If the size in node data does not exist either, `nodeSize` is assigned to 30 by default |
| sweep | Number | Math.PI | undefined | How many radians should be between the first and last node (defaults to full circle). If it is undefined, 2 _ Math.PI _ (1 - 1 / | level.nodes | ) will be used, where level.nodes is nodes set of each level, | level.nodes | is the number of nodes of the level |
| equidistant | Boolean | false | false | Whether levels have an equal radial distance between them, may cause bounding box overflow |

View File

@ -296,7 +296,7 @@ centripetalOptions: {
| --- | --- | --- | --- | --- |
| center | Array | [ 0, 0 ] | 图的中心 | 布局的中心 |
| nodeSize | Number | 30 | 30 | 节点大小(直径)。用于防止节点重叠时的碰撞检测 |
| minNodeSpacing | Number | 10 | 10 | 环与环之间最小间距,用于调整半径 |
| nodeSpacing | Number | 10 | 10 | 环与环之间最小间距,用于调整半径 |
| preventOverlap | Boolean | false | false | 是否防止重叠,必须配合属性 `nodeSize` ,只有设置了与当前图节点大小相同的 `nodeSize` 值,才能够进行节点重叠的碰撞检测。若未设置 `nodeSize` ,则将根据节点数据中的 `size` 进行碰撞检测。若二者都未设置,则默认以 30 为节点大小进行碰撞检测 |
| sweep | Number | Math.PI | undefined | 第一个节点与最后一个节点之间的弧度差 |
| equidistant | Boolean | false | false | 环与环之间的距离是否相等 |

View File

@ -86,6 +86,7 @@ const graph = new G6.TreeGraph({
| getHeight | Function | (d) => {<br />  // d is a node<br />  return 10;<br />} | undefined | The height of each node |
| getWidth | Function | (d) => {<br />  // d is a node<br />  return 20;<br />} | undefined | The width of each node |
| getSide | Function | (d) => {<br />  // d is a node<br />  return 'left';<br />} | undefined | The callback function of node position(left or right of root node). Only affects the nodes which are connected to the root node directly. And the descendant nodes will be placed according to it |
| align | 'center' / undefined | 'center' | undefined | Tell the layout whether the nodes drawing aligned at the center or the left-top. Built-in nodes are all aligned at the center, e.g. built-in 'circle' type node has circle type keyShape, and the circle shape's x and y are assigned with 0, which means the origin of this node's coordinate system is aligned at the circle's center; built-in 'rect' type node has rect type keyShape, and the rect shape's x and y are assigned with `width / 2` and `height / 2` respectively, which means the origin of this node's coordinate system is aligned at the center of the rect shape. But user customed node type with rect keyShape usually has [0, 0] for x and y, which means the origin is aligned at the left-top of the rect. Tell layout this info and correct `getWidth`, `getHeight` in the same time will make the coordinate calculation more precise. |
### mindmap

View File

@ -86,6 +86,7 @@ const graph = new G6.TreeGraph({
| getHeight | Function | (d) => {<br />  // d 是一个节点<br />  return 10;<br />} | undefined | 节点高度的回调函数 |
| getWidth | Function | (d) => {<br />  // d 是一个节点<br />  return 20;<br />} | undefined | 节点宽度的回调函数 |
| getSide | Function | (d) => {<br />  // d 是一个节点<br />  return 'left';<br />} | undefined | 节点放置在根节点左侧或右侧的回调函数,仅对与根节点直接相连的节点有效,设置后将会影响被设置节点的所有子孙节点 |
| align | 'center' / undefined | 'center' | undefined | 告知 indented 布局,节点在绘制时自身坐标系的原点在其中心还是左上角。所有的内置节点的自身坐标系原点均在中心,例如 'circle' 类型节点的 keyShape 是圆形图形,它的圆心 x、y 被设置为 0代表原点对齐在该 keyShape 的圆心(即中心);而 'rect' 类型的内置节点 keyShape 是矩形图形,内置定义中它的 x、y 分别被设置为 `width / 2``height / 2`,意味着矩形的左上角在 [width / 2, height / 2] 坐标上,那么坐标系原点就在矩形的中心。但自定义的 keyShape 为矩形的节点中,矩形的 x、y 可能被设置为 0那么这一节点类型的自身坐标系原点就在其左上角。从整个图来看每个节点的自身坐标系原点将对齐到布局计算的节点位置上画布绘制坐标系。根据你使用的节点类型告知 indented 布局是如何对齐的,可以更准确地计算节点位置,当然,这同时需要配合准确的 `getWidth``getHeight` 的返回值 |
### mindmap

View File

@ -19,4 +19,5 @@ const graph = new G6.Graph({
Note:
- TreeGraph layouts do not support Web-Worker;
- Sub-Graph layout mechanism do not support Web-Worker.
- Sub-Graph layout mechanism do not support Web-Worker;
- Worker will visit the latest online version of @antv/layout. If your application cannot reach the online resource, download and save the [layout script](https://unpkg.com/@antv/layout@latest/dist/layout.min.js), and put it on an address which is visitable for your application. And then config `workerScriptURL` with the address in `layout`.

View File

@ -20,4 +20,5 @@ const graph = new G6.Graph({
注意:
- 树图不支持 Web-Worker 机制;
- 子图布局机制暂不支持 Web-Worker 机制。
- 子图布局机制暂不支持 Web-Worker 机制;
- worker 使用的是 @antv/layout 线上的脚本,如果你的项目无法访问到线上资源,请保存 [layout 脚本](https://unpkg.com/@antv/layout@latest/dist/layout.min.js),并放在可以访问到的地址上,将 layout 的 `workerScriptURL` 配置为该地址即可

View File

@ -117,7 +117,7 @@ const graph = new G6.Graph({
<img src='https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*IFfoS67_HssAAAAAAAAAAAAAARQnAQ' width='650' alt="" />
- `allowDragOnItem`: whether response when the users drag on items(node/edge/combo), `false` by default;
- `allowDragOnItem`: whether response when the users drag on items(node/edge/combo), `false` by default. **Supported by 4.8.4** Allow object config with type `{ node?: boolean, edge?: boolean, combo?: boolean }` to control draggable on different item types;
- Related timing events:
- `canvas:dragstart`: Triggered when drag start. Listened by `graph.on('canvas:dragstart', e => {...})`;
- `canvas:drag`: Triggered when dragging. Listened by `graph.on('canvas:drag', e => {...})`;
@ -162,7 +162,7 @@ The canvas can be dragged along x direction only.<br /><img src='https://gw.alip
- `enableOptimize`: whether enable optimization, `false` by default. `enableOptimize: true` means hiding all edges and the shapes beside keyShapes of nodes while dragging canvas;
- `zoomKey`: switch to zooming while pressing the key and wheeling. Options: `'shift'`, `'ctrl'`, `'alt'`, `'control'`, `'meta'`, using an array of these options allows any of these keys to trigger zooming;
- `scalableRange`: scalable range when drag canvas, `zero` by default. -1 to 1 means the scalable percentage of the viewport; the image bellow illustrate the situation when it is smaller than -1 or bigger than 1:
- `allowDragOnItem`: whether response when the users drag on items(node/edge/combo), `true` by default;
- `allowDragOnItem`: whether response when the users drag on items(node/edge/combo), `false` by default. **Supported after 4.8.4** allows object config with type `{ node?: boolean, edge?: boolean, combo?: boolean }` to control draggable on different item types;
<img src='https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*IFfoS67_HssAAAAAAAAAAAAAARQnAQ' width='650' alt="" />

View File

@ -114,7 +114,7 @@ const graph = new G6.Graph({
- `direction`:允许拖拽方向,支持`'x'``'y'``'both'`,默认方向为 `'both'`
- `enableOptimize`:是否开启优化,开启后拖动画布过程中隐藏所有的边及节点上非 keyShape 部分,默认关闭;
- `shouldBegin(e, self)`:是否允许触发该操作。**v4.7.16 起支持** 最后一个参数为 behavior 实例,方便在箭头函数定义的 `shouldBegin` 中访问该实例;
- `allowDragOnItem`:是否允许用户在节点/边/ combo 上拖拽时响应,默认为 false
- `allowDragOnItem`:是否允许用户在节点/边/ combo 上拖拽时响应,默认为 false。**v4.8.4 起支持:** 支持配置类型为 `{ node?: boolean, edge?: boolean, combo?: boolean }` 的对象,以支持控制是否允许响应不同元素类型上的拖拽事件
- `scalableRange`:拖动 canvas 可扩展的范围,默认为 0值为 -1 1 代表可超出视口的范围的比例值(相对于视口大小)。值小于 -1 或大于 1 时,为正和负数时的效果如下图所示。
<img src='https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*IFfoS67_HssAAAAAAAAAAAAAARQnAQ' width='650' />
@ -164,7 +164,7 @@ const graph = new G6.Graph({
- `enableOptimize`:是否开启优化,开启后拖动画布过程中隐藏所有的边及节点上非 keyShape 部分,默认关闭;
- `zoomKey`:切换为滚动缩放的键盘按钮,按住该键并滚动滚轮,则切换为滚轮缩放画布,可选项为:`'shift'``'ctrl'``'alt'``'control'``'meta'`, 可使用数组监听多个按键,任意按键按下时都会触发缩放;
- `scalableRange`:拖动 canvas 可扩展的范围,默认为 0值为 -1 1 代表可超出视口的范围的比例值(相对于视口大小)。值小于 -1 或大于 1 时,为正和负数时的效果如下图所示。
- `allowDragOnItem`:是否允许用户在节点/边/ combo 上拖拽时响应,默认为 false
- `allowDragOnItem`:是否允许用户在节点/边/ combo 上拖拽时响应,默认为 false。**v4.8.4 起支持:** 支持配置类型为 `{ node?: boolean, edge?: boolean, combo?: boolean }` 的对象,以支持控制是否允许响应不同元素类型上的拖拽事件
<img src='https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*IFfoS67_HssAAAAAAAAAAAAAARQnAQ' width='650' alt="" />

View File

@ -41,9 +41,24 @@ fetch('https://gw.alipayobjects.com/os/bmw-prod/b0ca4b15-bd0c-43ec-ae41-c810374a
graph.data(data);
graph.render();
// store the selected nodes according to the clicked order
let selectedNodeIds = [];
graph.on('node:click', ({ item }) => {
const id = item.getID();
const index = selectedNodeIds.indexOf(id);
if (item.hasState('selected') && index < 0) {
selectedNodeIds.push(id);
} else if (!item.hasState('selected')) {
selectedNodeIds.splice(index, 1);
}
});
graph.on('canvas:click', e => {
selectedNodeIds = [];
});
button.addEventListener('click', (e) => {
const selectedNodes = graph.findAllByState('node', 'selected');
if (selectedNodes.length !== 2) {
if (selectedNodeIds.length !== 2) {
alert('Please select TWO nodes!\n\r请选择有且两个节点');
return;
}
@ -52,35 +67,39 @@ fetch('https://gw.alipayobjects.com/os/bmw-prod/b0ca4b15-bd0c-43ec-ae41-c810374a
// path 为其中一条最短路径allPath 为所有的最短路径
const { path, allPath } = findShortestPath(
data,
selectedNodes[0].getID(),
selectedNodes[1].getID(),
selectedNodeIds[0],
selectedNodeIds[1],
true
);
selectedNodeIds = [];
const pathNodeMap = {};
path.forEach((id) => {
const pathNode = graph.findById(id);
pathNode.toFront();
graph.setItemState(pathNode, 'highlight', true);
pathNodeMap[id] = true;
});
graph.getEdges().forEach((edge) => {
const edgeModel = edge.getModel();
const source = edgeModel.source;
const target = edgeModel.target;
const sourceInPathIdx = path.indexOf(source);
const targetInPathIdx = path.indexOf(target);
if (sourceInPathIdx === -1 || targetInPathIdx === -1) return;
if (Math.abs(sourceInPathIdx - targetInPathIdx) === 1) {
graph.setItemState(edge, 'highlight', true);
} else {
graph.setItemState(edge, 'inactive', true);
}
});
graph.getNodes().forEach((node) => {
if (!pathNodeMap[node.getID()]) {
graph.setItemState(node, 'inactive', true);
}
});
if (path?.length) {
const pathNodeMap = {};
path.forEach((id) => {
const pathNode = graph.findById(id);
pathNode.toFront();
graph.setItemState(pathNode, 'highlight', true);
pathNodeMap[id] = true;
});
graph.getEdges().forEach((edge) => {
const edgeModel = edge.getModel();
const source = edgeModel.source;
const target = edgeModel.target;
const sourceInPathIdx = path.indexOf(source);
const targetInPathIdx = path.indexOf(target);
if (sourceInPathIdx === -1 || targetInPathIdx === -1) return;
if (Math.abs(sourceInPathIdx - targetInPathIdx) === 1) {
graph.setItemState(edge, 'highlight', true);
} else {
graph.setItemState(edge, 'inactive', true);
}
});
graph.getNodes().forEach((node) => {
if (!pathNodeMap[node.getID()]) {
graph.setItemState(node, 'inactive', true);
}
});
}
});
});

View File

@ -1,7 +1,7 @@
{
"private": true,
"name": "@antv/g6-site",
"version": "4.8.3",
"version": "4.8.4",
"description": "G6 sites deployed on gh-pages",
"keywords": [
"antv",