mirror of
https://gitee.com/antv/g6.git
synced 2024-11-29 18:28:19 +08:00
feat: emit beforecollapseexpandcombo and aftercollapseexpandcombo; fix: node state and update fontSize problem, closes: #2462
This commit is contained in:
parent
2ec2b72899
commit
ab16b62697
@ -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({
|
||||
|
@ -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}`);
|
||||
|
@ -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
|
||||
|
@ -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 方法中实现
|
||||
* 单图形的节点仅考虑 selected、active 状态,有其他状态需求的用户自己复写这个方法
|
||||
@ -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) {
|
||||
|
@ -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',
|
||||
|
@ -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();
|
||||
|
@ -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()
|
||||
});
|
||||
});
|
298
packages/pc/tests/unit/state/state-update-spec.ts
Normal file
298
packages/pc/tests/unit/state/state-update-spec.ts
Normal 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()
|
||||
});
|
||||
});
|
@ -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 |
|
||||
|
@ -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
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
|
Loading…
Reference in New Issue
Block a user