feat: emit beforecollapseexpandcombo and aftercollapseexpandcombo; fix: node state and update fontSize problem, closes: #2462

This commit is contained in:
Yanyan-Wang 2020-12-30 15:36:49 +08:00 committed by Yanyan Wang
parent 2ec2b72899
commit ab16b62697
10 changed files with 401 additions and 339 deletions

View File

@ -2216,6 +2216,7 @@ export default abstract class AbstractGraph extends EventEmitter implements IAbs
console.warn('The combo to be collapsed does not exist!');
return;
}
this.emit('beforecollapseexpandcombo', { action: 'expand', item: combo })
const comboModel = combo.getModel();
@ -2358,6 +2359,7 @@ export default abstract class AbstractGraph extends EventEmitter implements IAbs
false,
);
});
this.emit('aftercollapseexpandcombo', { action: 'collapse', item: combo })
}
/**
@ -2372,6 +2374,8 @@ export default abstract class AbstractGraph extends EventEmitter implements IAbs
console.warn('The combo to be collapsed does not exist!');
return;
}
this.emit('beforecollapseexpandcombo', { action: 'expand', item: combo })
const comboModel = combo.getModel();
const itemController: ItemController = this.get('itemController');
@ -2574,6 +2578,7 @@ export default abstract class AbstractGraph extends EventEmitter implements IAbs
}
}
});
this.emit('aftercollapseexpandcombo', { action: 'expand', item: combo })
}
public collapseExpandCombo(combo: string | ICombo) {
@ -2744,9 +2749,9 @@ export default abstract class AbstractGraph extends EventEmitter implements IAbs
const stackData = data
? clone(data)
: {
before: {},
after: clone(this.save()),
};
before: {},
after: clone(this.save()),
};
if (stackType === 'redo') {
this.redoStack.push({

View File

@ -1,26 +1,19 @@
import { isString, isPlainObject, isNil, mix, each, isArray, deepMix, indexOf } from '@antv/util';
import { isString, isPlainObject, isNil, mix } from '@antv/util';
import { IEdge, INode, ICombo } from '../interface/item';
import { IGroup } from '@antv/g-base';
import {
IShapeBase,
ModelConfig,
ShapeStyle,
EdgeConfig,
IPoint,
NodeConfig,
SourceTarget,
Indexable,
} from '../types';
import Shape from '../shape/shape';
import Item from './item';
import Node from './node';
import Global from '../global';
const END_MAP: Indexable<string> = { source: 'start', target: 'end' };
const ITEM_NAME_SUFFIX = 'Node'; // 端点的后缀,如 sourceNode, targetNode
const POINT_NAME_SUFFIX = 'Point'; // 起点或者结束点的后缀,如 startPoint, endPoint
const ANCHOR_NAME_SUFFIX = 'Anchor';
const ARROWS = ['startArrow', 'endArrow'];
export default class Edge extends Item implements IEdge {
protected getDefaultCfg() {
@ -214,7 +207,7 @@ export default class Edge extends Item implements IEdge {
return this.get('target');
}
public updatePosition() {}
public updatePosition() { }
/**
* path
@ -240,88 +233,6 @@ export default class Edge extends Item implements IEdge {
this.clearCache();
}
/**
*
* @param keyShape keyShape
* @param group Group
*/
public setOriginStyle(cfg?: ModelConfig) {
const group: IGroup = this.get('group');
const children = group.get('children');
const keyShape: IShapeBase = this.getKeyShape();
const self = this;
const keyShapeName = keyShape.get('name');
if (!this.get('originStyle')) {
// 第一次 set originStyle直接拿首次渲染所有图形的 attrs
const originStyles = {};
each(children, (child) => {
const shapeType = child.get('type');
const name = child.get('name');
if (name && name !== keyShapeName) {
originStyles[name] =
shapeType !== 'image' ? child.attr() : self.getShapeStyleByName(name);
} else {
const keyShapeStyle: ShapeStyle = self.getShapeStyleByName(); // 可优化,需要去除 child.attr 中其他 shape 名的对象
if (keyShapeStyle.path) delete keyShapeStyle.path;
if (keyShapeStyle.matrix) delete keyShapeStyle.matrix;
if (!keyShapeName) {
Object.assign(originStyles, keyShapeStyle);
} else {
originStyles[keyShapeName] = keyShapeStyle;
}
}
});
self.set('originStyle', originStyles);
} else {
// 第二次 set originStyles需要找到不是 stateStyles 的样式,更新到 originStyles 中
// 上一次设置的 originStyle是初始的 shape attrs
let styles: ShapeStyle = this.getOriginStyle();
// let styles: ShapeStyle = {};
if (keyShapeName && !styles[keyShapeName]) styles[keyShapeName] = {};
// 获取当前状态样式
const currentStatesStyle = this.getCurrentStatesStyle();
// 遍历当前所有图形的 attrs找到不是 stateStyles 的样式更新到 originStyles 中
each(children, (child) => {
const name = child.get('name');
const shapeAttrs = child.attr();
if (name && name !== keyShapeName) {
// 有 name 的非 keyShape 图形
const shapeStateStyle = currentStatesStyle[name];
if (!styles[name]) styles[name] = {};
shapeStateStyle &&
Object.keys(shapeAttrs).forEach((key) => {
const value = shapeAttrs[key];
if (value !== shapeStateStyle[key]) styles[name][key] = value;
});
} else {
const shapeAttrs = child.attr();
const keyShapeStateStyles = Object.assign(
{},
currentStatesStyle,
currentStatesStyle[keyShapeName],
);
Object.keys(shapeAttrs).forEach((key) => {
const value = shapeAttrs[key];
// 如果是对象且不是 arrow则是其他 shape 的样式
if (isPlainObject(value) && ARROWS.indexOf(name) === -1) return;
if (keyShapeStateStyles[key] !== value) {
if (keyShapeName) styles[keyShapeName][key] = value;
else styles[key] = value;
}
});
}
});
if (styles.path) delete styles.path;
if (styles.matrix) delete styles.matrix;
self.set('originStyle', styles);
}
}
public destroy() {
const sourceItem: Node = this.get(`source${ITEM_NAME_SUFFIX}`);
const targetItem: Node = this.get(`target${ITEM_NAME_SUFFIX}`);

View File

@ -9,6 +9,7 @@ import {
mix,
deepMix,
isArray,
clone,
} from '@antv/util';
import { IItemBase, IItemBaseConfig } from '../interface/item';
import Shape from '../shape/shape';
@ -205,13 +206,13 @@ export default class ItemBase implements IItemBase {
this.restoreStates(shapeFactory, shapeType!);
}
/**
*
* @param keyShape keyShape
* @param group Group
*/
public setOriginStyle(cfg?: ModelConfig) {
const originStyles = {};
const group: IGroup = this.get('group');
const children = group.get('children');
const keyShape: IShapeBase = this.getKeyShape();
@ -219,12 +220,16 @@ export default class ItemBase implements IItemBase {
const keyShapeName = keyShape.get('name');
if (!this.get('originStyle')) {
// 第一次 set originStyle直接拿首次渲染所有图形的 attrs
const originStyles = {};
each(children, (child) => {
const shapeType = child.get('type');
const name = child.get('name');
if (name && name !== keyShapeName) {
originStyles[name] = self.getShapeStyleByName(name);
originStyles[name] =
shapeType !== 'image' ? clone(child.attr()) : self.getShapeStyleByName(name);
} else {
const keyShapeStyle: ShapeStyle = self.getShapeStyleByName();
const keyShapeStyle: ShapeStyle = self.getShapeStyleByName(); // 可优化,需要去除 child.attr 中其他 shape 名的对象
if (keyShapeStyle.path) delete keyShapeStyle.path;
if (keyShapeStyle.matrix) delete keyShapeStyle.matrix;
if (!keyShapeName) {
@ -234,61 +239,54 @@ export default class ItemBase implements IItemBase {
}
}
});
}
const itemType = this.get('type');
const model = this.getModel();
let shapeType = model.type;
if (!shapeType) {
switch (itemType) {
case 'edge':
shapeType = 'line';
break;
default:
shapeType = 'circle';
break;
}
}
let shapeFactory = Shape.getFactory(itemType)[shapeType];
if (!shapeFactory) shapeFactory = Shape.getFactory(itemType).getShape();
const shapeOptions = shapeFactory.getOptions ? shapeFactory.getOptions(model) : {};
const defaultStyle = shapeOptions.style || {};
const size = shapeOptions.size;
if (itemType === 'edge') {
if (!defaultStyle.lineWidth) defaultStyle.lineWidth = size || Global.defaultEdge.size;
self.set('originStyle', originStyles);
} else {
if (!defaultStyle.r) defaultStyle.r = size / 2 || Global.defaultNode.size / 2;
if (!defaultStyle.width)
defaultStyle.width = (isArray(size) ? size[0] : size) || Global.defaultNode.size / 2;
if (!defaultStyle.height)
defaultStyle.height = (isArray(size) ? size[1] : size) || Global.defaultNode.size / 2;
}
if (!keyShapeName) {
Object.assign(originStyles, defaultStyle);
} else {
const styles: ShapeStyle = {};
for (const key in defaultStyle) {
const style = defaultStyle[key];
if (!isPlainObject(style) || ARROWS.includes(key)) styles[key] = style;
}
if (!originStyles[keyShapeName]) originStyles[keyShapeName] = styles;
else originStyles[keyShapeName] = Object.assign(styles, originStyles[keyShapeName]);
}
// 第二次 set originStyles需要找到不是 stateStyles 的样式,更新到 originStyles 中
const drawOriginStyle = this.getOriginStyle();
let styles: ShapeStyle = {};
if (cfg) {
styles = deepMix({}, drawOriginStyle, originStyles, cfg.style, {
labelCfg: cfg.labelCfg,
// 上一次设置的 originStyle是初始的 shape attrs
let styles: ShapeStyle = this.getOriginStyle();
// let styles: ShapeStyle = {};
if (keyShapeName && !styles[keyShapeName]) styles[keyShapeName] = {};
// 获取当前状态样式
const currentStatesStyle = this.getCurrentStatesStyle();
// 遍历当前所有图形的 attrs找到不是 stateStyles 的样式更新到 originStyles 中
each(children, (child) => {
const name = child.get('name');
const shapeAttrs = child.attr();
if (name && name !== keyShapeName) {
// 有 name 的非 keyShape 图形
const shapeStateStyle = currentStatesStyle[name];
if (!styles[name]) styles[name] = {};
shapeStateStyle &&
Object.keys(shapeAttrs).forEach((key) => {
const value = shapeAttrs[key];
if (value !== shapeStateStyle[key]) styles[name][key] = value;
});
} else {
const shapeAttrs = child.attr();
const keyShapeStateStyles = Object.assign(
{},
currentStatesStyle,
currentStatesStyle[keyShapeName],
);
Object.keys(shapeAttrs).forEach((key) => {
const value = shapeAttrs[key];
// 如果是对象且不是 arrow则是其他 shape 的样式
if (isPlainObject(value) && ARROWS.indexOf(name) === -1) return;
if (keyShapeStateStyles[key] !== value) {
if (keyShapeName) styles[keyShapeName][key] = value;
else styles[key] = value;
}
});
}
});
} else {
styles = deepMix({}, drawOriginStyle, originStyles);
}
if (styles.path) delete styles.path;
if (styles.matrix) delete styles.matrix;
self.set('originStyle', styles);
if (styles.path) delete styles.path;
if (styles.matrix) delete styles.matrix;
self.set('originStyle', styles);
}
}
/**
@ -348,17 +346,17 @@ export default class ItemBase implements IItemBase {
/**
*
*/
protected beforeDraw() {}
protected beforeDraw() { }
/**
*
*/
protected afterDraw() {}
protected afterDraw() { }
/**
*
*/
protected afterUpdate() {}
protected afterUpdate() { }
/**
* draw shape

View File

@ -73,7 +73,7 @@ export const shapeBase: ShapeOptions = {
fontFamily:
typeof window !== 'undefined'
? window.getComputedStyle(document.body, null).getPropertyValue('font-family') ||
'Arial, sans-serif'
'Arial, sans-serif'
: 'Arial, sans-serif',
},
},
@ -82,7 +82,7 @@ export const shapeBase: ShapeOptions = {
fontFamily:
typeof window !== 'undefined'
? window.getComputedStyle(document.body, null).getPropertyValue('font-family') ||
'Arial, sans-serif'
'Arial, sans-serif'
: 'Arial, sans-serif',
},
},
@ -114,7 +114,7 @@ export const shapeBase: ShapeOptions = {
* @param group
* @param keyShape
*/
afterDraw(cfg?: ModelConfig, group?: IGroup, keyShape?: IShape) {},
afterDraw(cfg?: ModelConfig, group?: IGroup, keyShape?: IShape) { },
drawShape(cfg?: ModelConfig, group?: IGroup): IShape {
return null as any;
},
@ -335,7 +335,7 @@ export const shapeBase: ShapeOptions = {
},
// update(cfg, item) // 默认不定义
afterUpdate(cfg?: ModelConfig, item?: Item) {},
afterUpdate(cfg?: ModelConfig, item?: Item) { },
/**
* draw
* selectedactive
@ -355,6 +355,8 @@ export const shapeBase: ShapeOptions = {
const stateName = isBoolean(value) ? name : `${name}:${value}`;
const shapeStateStyle = this.getStateStyle(stateName, item);
const itemStateStyle = item.getStateStyle(stateName);
// const originStyle = item.getOriginStyle();
// 不允许设置一个不存在的状态
if (!itemStateStyle && !shapeStateStyle) {

View File

@ -174,10 +174,10 @@ export interface States {
export interface StateStyles {
[key: string]:
| ShapeStyle
| {
[key: string]: ShapeStyle;
};
| ShapeStyle
| {
[key: string]: ShapeStyle;
};
}
// model types (node edge group)
@ -252,7 +252,7 @@ export interface GraphOptions {
size: number | number[];
color: string;
}> &
ModelStyle;
ModelStyle;
/**
* type, size, color data
@ -262,7 +262,7 @@ export interface GraphOptions {
size: number | number[];
color: string;
}> &
ModelStyle;
ModelStyle;
/**
* Combo
@ -272,7 +272,7 @@ export interface GraphOptions {
size: number | number[];
color: string;
}> &
ModelStyle;
ModelStyle;
nodeStateStyles?: StateStyles;
@ -402,10 +402,10 @@ export interface TreeGraphData {
depth?: number;
collapsed?: boolean;
style?:
| ShapeStyle
| {
[key: string]: ShapeStyle;
};
| ShapeStyle
| {
[key: string]: ShapeStyle;
};
stateStyles?: StateStyles;
[key: string]: unknown;
}
@ -689,6 +689,8 @@ export enum G6Event {
AFTERANIMATE = 'afteranimate',
BEFOREPAINT = 'beforepaint',
AFTERPAINT = 'afterpaint',
BEFORECOLLAPSEEXPANDCOMBO = 'beforecollapseexpandcombo',
AFTERCOLLAPSEEXPANDCOMBO = 'aftercollapseexpandcombo',
GRAPHSTATECHANGE = 'graphstatechange',
AFTERACTIVATERELATIONS = 'afteractivaterelations',

View File

@ -238,7 +238,7 @@ describe('graph refactor states', () => {
const group = item.getContainer();
const subShape = group.find((element) => element.get('name') === 'sub-node');
expect(subShape.attr('fill')).toEqual('#fff');
// default value
// // default value
expect(subShape.attr('lineWidth')).toEqual(1);
graph.destroy();

View File

@ -1,172 +0,0 @@
import G6 from '../../../src';
const data = {
// 点集
nodes: [
{
id: 'node1', // String该节点存在则必须节点的唯一标识
x: 100, // Number可选节点位置的 x 值
y: 200, // Number可选节点位置的 y 值
},
{
id: 'node2', // String该节点存在则必须节点的唯一标识
x: 300, // Number可选节点位置的 x 值
y: 200, // Number可选节点位置的 y 值
},
],
// 边集
edges: [
{
source: 'node1', // String必须起始点 id
target: 'node2', // String必须目标点 id
label: 'edge1',
// type: 'polyline',
// controlPoints: [{ x: 50, y: 50 }],
style: {
endArrow: true,
stroke: '#f00',
},
},
],
};
const div = document.createElement('div');
div.id = 'force-layout';
document.body.appendChild(div);
describe('force layout', () => {
it('force layout and hover with wrong label position', () => {
const graph = new G6.Graph({
container: div,
layout: {
type: 'radial',
},
width: 500,
height: 500,
defaultNode: { size: 10 },
modes: {
default: ['drag-node'],
},
});
graph.on('edge:mouseenter', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', true);
});
graph.on('edge:mouseleave', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', false);
});
graph.on('edge:click', (evt) => {
const { item } = evt;
graph.setItemState(item, 'selected', true);
});
graph.on('canvas:click', () => {
console.log('canvas click');
graph.getEdges().forEach((edge) => {
console.log('selected false');
graph.setItemState(edge, 'selected', false);
});
});
graph.data(data);
graph.render();
const edge = graph.getEdges()[0];
graph.emit('edge:mouseenter', { item: edge });
const keyShape = edge.getKeyShape();
const text = edge.getContainer().find((e) => e.get('name') === 'text-shape');
expect(keyShape.attr('stroke')).toBe('rgb(95, 149, 255)');
expect(text.attr('x')).not.toBe(200);
graph.emit('edge:mouseleave', { item: edge });
expect(keyShape.attr('stroke')).toBe('#f00');
expect(text.attr('x')).not.toBe(200);
graph.emit('edge:mouseenter', { item: edge });
graph.emit('edge:click', { item: edge });
expect(keyShape.attr('stroke')).toBe('rgb(95, 149, 255)');
expect(text.attr('x')).not.toBe(200);
expect(keyShape.attr('lineWidth')).toBe(2);
graph.emit('edge:mouseleave', { item: edge });
graph.emit('canvas:click', {});
expect(keyShape.attr('lineWidth')).toBe(1);
expect(text.attr('x')).not.toBe(200);
expect(keyShape.attr('stroke')).toBe('#f00');
graph.destroy();
});
it('acitvate-relations wrong label position', () => {
const data2 = {
nodes: [
{
id: '0',
x: 150,
y: 100,
},
{
id: '1',
x: 350,
y: 300,
},
],
edges: [
// Built-in polyline
{
source: '0',
target: '1',
label: 'edge-label',
},
],
};
const graph = new G6.Graph({
container: div,
width: 500,
height: 500,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
defaultNode: {
style: {
fill: '#DEE9FF',
stroke: '#5B8FF9',
},
},
modes: {
// behavior
default: [
'drag-node',
{
type: 'activate-relations',
trigger: 'click',
},
],
},
});
graph.data(data2);
graph.render();
const edge = graph.getEdges()[0];
const node = graph.getNodes()[0];
const text = edge.getContainer().find((e) => e.get('name') === 'text-shape');
graph.emit('edge:click', { item: edge });
expect(text.attr('x')).toBe(250);
expect(text.attr('y')).toBe(200);
graph.emit('node:click', { item: node });
expect(text.attr('x')).toBe(250);
expect(text.attr('y')).toBe(200);
graph.emit('canvas:click', {});
expect(text.attr('x')).toBe(250);
expect(text.attr('y')).toBe(200);
// graph.destroy()
});
});

View File

@ -0,0 +1,298 @@
import G6 from '../../../src';
const div = document.createElement('div');
div.id = 'global-spec';
document.body.appendChild(div);
describe('node state label update', () => {
const data = {
nodes: [
{
id: 'node1',
x: 100,
y: 100,
label: 'node1',
type: 'rect',
labelCfg: {
style: {
fill: "rgb(49,158,236)",
// fontSize: 12
},
position: "bottom"
},
},
{
id: 'node2',
x: 100,
y: 300,
label: 'node2',
type: 'rect',
labelCfg: {
style: {
fill: "rgb(49,158,236)",
// fontSize: 12
},
position: "bottom"
},
},
],
edges: [
{ source: 'node1', target: 'node2', label: 'edge' }
]
};
G6.registerNode(
"rect-img",
{
afterDraw(cfg, item) {
// 添加图片
let _cfg = cfg;
_cfg.x = -cfg.size / 2;
_cfg.y = -cfg.size / 2;
_cfg.width = cfg.size;
_cfg.height = cfg.size;
item.addShape("image", {
attrs: _cfg,
draggable: true,
name: "image-shape"
});
},
update: undefined,
afterUpdate: undefined,
setItemState: undefined
},
"rect"
);
it('label update', () => {
const graph = new G6.Graph({
container: div,
width: 500,
height: 500,
nodeStateStyles: {
hover: {},
selected: {
lineWidth: 3,
fill: "rgba(0,0,0,0)" //背景填充色
}
},
});
graph.data(data);
graph.render();
graph.on('canvas:click', () => {
const node = graph.getNodes()[0]
// model.labelCfg.style.fontSize = 50;
graph.updateItem(node, {
labelCfg: {
style: {
fontSize: 50
}
}
});
const edge = graph.getEdges()[0]
graph.updateItem(edge, {
labelCfg: {
style: {
fontSize: 30
}
}
});
})
graph.on('node:click', e => {
graph.setItemState(e.item, 'selected', !e.item.hasState("selected"));
});
graph.on('edge:click', e => {
graph.setItemState(e.item, 'selected', !e.item.hasState("selected"));
});
const node = graph.getNodes()[0];
const edge = graph.getEdges()[0];
const nodeTextShape = node.getContainer().find(e => e.get('name') === 'text-shape');
const edgeTextShape = edge.getContainer().find(e => e.get('name') === 'text-shape');
expect(edgeTextShape.attr('fontSize')).toBe(12);
graph.emit('canvas:click', {});
expect(nodeTextShape.attr('fontSize')).toBe(50);
expect(edgeTextShape.attr('fontSize')).toBe(30);
graph.emit('node:click', { item: node });
expect(nodeTextShape.attr('fontSize')).toBe(50);
graph.emit('edge:click', { item: edge });
expect(edgeTextShape.attr('fontSize')).toBe(30);
graph.emit('node:click', { item: node });
expect(nodeTextShape.attr('fontSize')).toBe(50);
graph.emit('edge:click', { item: edge });
expect(edgeTextShape.attr('fontSize')).toBe(30);
graph.destroy();
});
});
describe('force layout', () => {
const data = {
// 点集
nodes: [
{
id: 'node1', // String该节点存在则必须节点的唯一标识
x: 100, // Number可选节点位置的 x 值
y: 200, // Number可选节点位置的 y 值
},
{
id: 'node2', // String该节点存在则必须节点的唯一标识
x: 300, // Number可选节点位置的 x 值
y: 200, // Number可选节点位置的 y 值
},
],
// 边集
edges: [
{
source: 'node1', // String必须起始点 id
target: 'node2', // String必须目标点 id
label: 'edge1',
// type: 'polyline',
// controlPoints: [{ x: 50, y: 50 }],
style: {
endArrow: true,
stroke: '#f00',
},
},
],
};
it('force layout and hover with wrong label position', () => {
const graph = new G6.Graph({
container: div,
layout: {
type: 'radial',
},
width: 500,
height: 500,
defaultNode: { size: 10 },
modes: {
default: ['drag-node'],
},
});
graph.on('edge:mouseenter', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', true);
});
graph.on('edge:mouseleave', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', false);
});
graph.on('edge:click', (evt) => {
const { item } = evt;
graph.setItemState(item, 'selected', true);
});
graph.on('canvas:click', () => {
console.log('canvas click');
graph.getEdges().forEach((edge) => {
console.log('selected false');
graph.setItemState(edge, 'selected', false);
});
});
graph.data(data);
graph.render();
const edge = graph.getEdges()[0];
graph.emit('edge:mouseenter', { item: edge });
const keyShape = edge.getKeyShape();
const text = edge.getContainer().find((e) => e.get('name') === 'text-shape');
expect(keyShape.attr('stroke')).toBe('rgb(95, 149, 255)');
expect(text.attr('x')).not.toBe(200);
graph.emit('edge:mouseleave', { item: edge });
expect(keyShape.attr('stroke')).toBe('#f00');
expect(text.attr('x')).not.toBe(200);
graph.emit('edge:mouseenter', { item: edge });
graph.emit('edge:click', { item: edge });
expect(keyShape.attr('stroke')).toBe('rgb(95, 149, 255)');
expect(text.attr('x')).not.toBe(200);
expect(keyShape.attr('lineWidth')).toBe(2);
graph.emit('edge:mouseleave', { item: edge });
graph.emit('canvas:click', {});
expect(keyShape.attr('lineWidth')).toBe(1);
expect(text.attr('x')).not.toBe(200);
expect(keyShape.attr('stroke')).toBe('#f00');
graph.destroy();
});
it('acitvate-relations wrong label position', () => {
const data2 = {
nodes: [
{
id: '0',
x: 150,
y: 100,
},
{
id: '1',
x: 350,
y: 300,
},
],
edges: [
// Built-in polyline
{
source: '0',
target: '1',
label: 'edge-label',
},
],
};
const graph = new G6.Graph({
container: div,
width: 500,
height: 500,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
defaultNode: {
style: {
fill: '#DEE9FF',
stroke: '#5B8FF9',
},
},
modes: {
// behavior
default: [
'drag-node',
{
type: 'activate-relations',
trigger: 'click',
},
],
},
});
graph.data(data2);
graph.render();
const edge = graph.getEdges()[0];
const node = graph.getNodes()[0];
const text = edge.getContainer().find((e) => e.get('name') === 'text-shape');
graph.emit('edge:click', { item: edge });
expect(text.attr('x')).toBe(250);
expect(text.attr('y')).toBe(200);
graph.emit('node:click', { item: node });
expect(text.attr('x')).toBe(250);
expect(text.attr('y')).toBe(200);
graph.emit('canvas:click', {});
expect(text.attr('x')).toBe(250);
expect(text.attr('y')).toBe(200);
graph.destroy()
});
});

View File

@ -176,6 +176,8 @@ graph.on(timingEventName, evt => {
| afteranimate | Activated after global animation |
| beforecreateedge | Activated before an edge is created by the built-in behavior `create-edge` |
| aftercreateedge | Activated after an edge is created by the built-in behavior `create-edge` |
| beforecollapseexpandcombo | Activated before an combo is collapsed or expanded, the parameter `action` indicates collapse or expand |
| aftercollapseexpandcombo | Activated after an combo is collapsed or expanded, the parameter `action` indicates collapse or expand |
| graphstatechange | Activated after `graph.updateItemState` being called. |
| afteractivaterelations | Activated while activating a node by `'activate-relations'` Behavior which is assigned to the the instance of Graph. |
| nodeselectchange | Activated while the selected items are changed by `'brush-select'`, `'click-select'` or `'lasso-select'` Behavior which is assigned to the instance of Graph. |
@ -294,6 +296,13 @@ No parameters.
| ---- | ---- | ---------------- |
| edge | Item | The created edge |
#### beforecollapseexpandcombo / aftercollapseexpandcombo
| Name | Type | Description |
| ---- | ---- | ---------------- |
| action | string | The action, `'collapse'` or `'expand'` |
| combo | Item | The manipulated combo |
#### itemcollapsed
| Name | Type | Description |

View File

@ -176,6 +176,8 @@ graph.on(timingEventName, evt => {
| afteranimate | 全局动画发生后触发 |
| beforecreateedge | 使用内置交互 `create-edge`,创建边之前触发 |
| aftercreateedge | 使用内置交互 `create-edge`,创建边之后触发 |
| beforecollapseexpandcombo | 当一个 combo 被收起或展开之前被触发,参数 `action` 指明了是收起还是展开 |
| aftercollapseexpandcombo | 当一个 combo 被收起或展开之后被触发,参数 `action` 指明了是收起还是展开 |
| graphstatechange | 调用 `graph.updateItemState` 方法之后触发 |
| afteractivaterelations | 使用了 `'activate-relations'` Behavior 并触发了该行为后,该事件被触发 |
| nodeselectchange | 使用了 `'brush-select'` , `'click-select'``'lasso-select'` Behavior 且选中元素发生变化时,该事件被触发 |
@ -287,6 +289,13 @@ graph.on(timingEventName, evt => {
| ---- | ---- | ------------------ |
| edge | Item | 当前被创建的边实例 |
#### beforecollapseexpandcombo / aftercollapseexpandcombo
| 名称 | 类型 | 描述 |
| ---- | ---- | ---------------- |
| action | string | 具体的操作, `'collapse'``'expand'` |
| combo | Item | 被操作的 combo item |
#### itemcollapsed
| 名称 | 类型 | 描述 |