mirror of
https://gitee.com/antv/g6.git
synced 2024-12-05 05:09:07 +08:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
90a03d1d44
18
.gitlab-ci.yml
Normal file
18
.gitlab-ci.yml
Normal file
@ -0,0 +1,18 @@
|
||||
# 不要修改该文件, 会自动生成, 详见 http://gitlab.alibaba-inc.com/node/ci
|
||||
# 不要使用 ci 生成,直接按照 gitlab 文档自己写吧!
|
||||
before_script:
|
||||
- export PATH=$PWD/node_modules/.bin:$PWD/.node/bin:$PATH
|
||||
- time enclose install tnpm:tnpm
|
||||
- tnpm -v
|
||||
stages:
|
||||
- test
|
||||
|
||||
# job1
|
||||
test:
|
||||
image: reg.docker.alibaba-inc.com/dockerlab/node-ci:3.2.0
|
||||
stage: test
|
||||
script:
|
||||
- env -u CI tnpm i --silent --internal-oss-cache --install-node=10
|
||||
- time tnpm run lint
|
||||
tags:
|
||||
- swarm
|
@ -56,7 +56,7 @@ G6.registerNode('tree-node', {
|
||||
}
|
||||
});
|
||||
const bbox = text.getBBox();
|
||||
const hasChildren = cfg.data.children && cfg.data.children.length > 0;
|
||||
const hasChildren = cfg.children && cfg.children.length > 0;
|
||||
if (hasChildren) {
|
||||
group.addShape('marker', {
|
||||
attrs: {
|
||||
@ -88,15 +88,12 @@ const graph = new G6.TreeGraph({
|
||||
default: [{
|
||||
type: 'collapse-expand',
|
||||
onChange(item, collapsed) {
|
||||
const data = item.get('model').data;
|
||||
const icon = item.get('group').findByClassName('collapse-icon');
|
||||
if (collapsed) {
|
||||
icon.attr('symbol', EXPAND_ICON);
|
||||
} else {
|
||||
icon.attr('symbol', COLLAPSE_ICON);
|
||||
}
|
||||
data.collapsed = collapsed;
|
||||
return true;
|
||||
}
|
||||
}, 'drag-canvas', 'zoom-canvas']
|
||||
},
|
||||
|
@ -49,17 +49,17 @@
|
||||
width: 500,
|
||||
height: 500,
|
||||
pixelRatio: 2,
|
||||
renderer: 'svg',
|
||||
modes: {
|
||||
default: [{
|
||||
type: 'collapse-expand',
|
||||
onChange(item, collapsed) {
|
||||
const data = item.get('model').data;
|
||||
data.collapsed = collapsed;
|
||||
return true;
|
||||
}
|
||||
}, 'drag-canvas']
|
||||
default: ['collapse-expand', 'drag-canvas']
|
||||
},
|
||||
defaultNode: {
|
||||
size: 16,
|
||||
anchorPoints: [[0,0.5], [1,0.5]]
|
||||
},
|
||||
defaultEdge: {
|
||||
shape: 'cubic-horizontal'
|
||||
},
|
||||
defaultNode: { size: 16 },
|
||||
nodeStyle: {
|
||||
default: {
|
||||
fill: '#40a9ff',
|
||||
@ -144,13 +144,6 @@
|
||||
};
|
||||
graph.data(data);
|
||||
graph.render();
|
||||
graph.getNodes().forEach(node => {
|
||||
node.get('model').anchorPoints = [[0,0.5], [1,0.5]];
|
||||
});
|
||||
graph.getEdges().forEach((edge) => {
|
||||
edge.get('model').shape = 'cubic-horizontal';
|
||||
});
|
||||
graph.refresh();
|
||||
graph.fitView();
|
||||
document.getElementById('layout').addEventListener('change', (e) => {
|
||||
const layout = e.target.value;
|
||||
|
@ -115,6 +115,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@antv/g": "~3.4.3",
|
||||
"@antv/hierarchy": "~0.4.0",
|
||||
"@antv/util": "~1.3.1"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -1,20 +1,10 @@
|
||||
const Util = require('../util');
|
||||
|
||||
const DEFAULT_ANIMATE = {
|
||||
duration: 500,
|
||||
delay: 0,
|
||||
callback() {},
|
||||
easing: 'easeLinear'
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getDefaultCfg() {
|
||||
return {
|
||||
/**
|
||||
* 发生收缩/扩展变化时,是否需要重新layout。
|
||||
* 发生收缩/扩展变化时的回调
|
||||
*/
|
||||
onChange() {},
|
||||
animate: {}
|
||||
onChange() {}
|
||||
};
|
||||
},
|
||||
getEvents() {
|
||||
@ -24,227 +14,22 @@ module.exports = {
|
||||
},
|
||||
onNodeClick(e) {
|
||||
const item = e.item;
|
||||
const children = item.get('model').data.children;
|
||||
const model = item.getModel();
|
||||
const children = model.children;
|
||||
// 叶子节点的收缩和展开没有意义
|
||||
if (!children || children.length === 0) {
|
||||
return;
|
||||
}
|
||||
const collapsed = !item.hasState('collapsed');
|
||||
const collapsed = !model.collapsed;
|
||||
if (!this.shouldBegin(e, collapsed)) {
|
||||
return;
|
||||
}
|
||||
item.set('collapsed', collapsed);
|
||||
model.collapsed = collapsed;
|
||||
this.graph.emit('itemcollapsed', { item: e.item, collapsed });
|
||||
if (!this.shouldUpdate(e, collapsed)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const autoPaint = this.graph.get('autoPaint');
|
||||
this.graph.setAutoPaint(false);
|
||||
this.updateLayout(item, collapsed);
|
||||
this.setChildrenState(item, 'collapsed', collapsed);
|
||||
this.graph.setAutoPaint(autoPaint);
|
||||
},
|
||||
updateLayout(item, isCollapsed) {
|
||||
const graph = this.graph;
|
||||
let data = this.onChange(item, isCollapsed);
|
||||
if (data) {
|
||||
data = typeof data === 'boolean' ? graph.get('data') : data;
|
||||
if (graph.get('layout')) {
|
||||
data = graph.get('layout')(data);
|
||||
}
|
||||
if (this.animate) {
|
||||
// 有动画,且有重布局,先停掉原有动画
|
||||
if (this.graph.isLayoutAnimating()) {
|
||||
this.graph.stopLayoutAnimate();
|
||||
}
|
||||
// 计算每个节点移动的起始位置和最终位置
|
||||
this.animateChild(data);
|
||||
// 如果是展开,缓存一下需要 show,因为要先出
|
||||
if (!isCollapsed) {
|
||||
Util.traverseTree(item.get('model').data, data => {
|
||||
const node = graph.findById(data.id);
|
||||
node.set('shouldShow', true);
|
||||
if (data.collapsed) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
this.performAnimate();
|
||||
} else {
|
||||
Util.traverseTree(data, child => {
|
||||
const node = graph.findById(child.id);
|
||||
node.get('model').x = child.x;
|
||||
node.get('model').y = child.y;
|
||||
});
|
||||
graph.refresh();
|
||||
Util.traverseTree(item.get('model').data, child => {
|
||||
const node = graph.findById(child.id);
|
||||
if (node === item) { return; }
|
||||
if (isCollapsed) {
|
||||
graph.hideItem(node);
|
||||
} else {
|
||||
graph.showItem(node);
|
||||
}
|
||||
});
|
||||
graph.paint();
|
||||
}
|
||||
} else {
|
||||
if (this.animate) {
|
||||
// 没有重布局,有动画的情况
|
||||
const model = item.get('model');
|
||||
if (isCollapsed) {
|
||||
this.collapsePosition(item, {
|
||||
x: model.x,
|
||||
y: model.y
|
||||
});
|
||||
} else {
|
||||
this.expandPosition(item, { x: model.x, y: model.y });
|
||||
}
|
||||
this.performAnimate();
|
||||
} else {
|
||||
// 没有重布局,没有动画
|
||||
if (isCollapsed) {
|
||||
this.collapse(item);
|
||||
} else {
|
||||
this.expand(item);
|
||||
}
|
||||
this.graph.paint();
|
||||
}
|
||||
}
|
||||
},
|
||||
// 执行位置动画
|
||||
performAnimate() {
|
||||
const self = this;
|
||||
const animate = Util.mix({}, DEFAULT_ANIMATE, self.animate);
|
||||
const graph = self.graph;
|
||||
graph.layoutAnimate(graph.get('data'), (node, ratio, origin) => {
|
||||
const to = node.get('toPosition');
|
||||
if (to) {
|
||||
return {
|
||||
x: origin.x + (to.x - origin.x) * ratio,
|
||||
y: origin.y + (to.y - origin.y) * ratio
|
||||
};
|
||||
}
|
||||
}, animate.duration, animate.easing, () => {
|
||||
Util.each(graph.get('nodes'), node => {
|
||||
if (node.get('shouldHide')) {
|
||||
node.set('shouldHide', false);
|
||||
node.hide();
|
||||
}
|
||||
if (node.get('shouldShow')) {
|
||||
node.set('shouldShow', false);
|
||||
graph.showItem(node);
|
||||
}
|
||||
node.set('toPosition', null);
|
||||
});
|
||||
graph.paint();
|
||||
animate.callback();
|
||||
}, animate.delay);
|
||||
},
|
||||
// 根据最新layout,整理节点位置
|
||||
animateChild(data) {
|
||||
const self = this;
|
||||
const node = self.graph.findById(data.id);
|
||||
const nodeModel = node.get('model');
|
||||
const point = {
|
||||
x: data.x,
|
||||
y: data.y
|
||||
};
|
||||
if (Util.isNil(point.x) || Util.isNil(point.y)) {
|
||||
const model = node.get('parent').get('model');
|
||||
point.x = model.x;
|
||||
point.y = model.y;
|
||||
}
|
||||
if (nodeModel.x !== point.x || nodeModel.y !== point.y) {
|
||||
node.set('toPosition', {
|
||||
x: point.x,
|
||||
y: point.y
|
||||
});
|
||||
}
|
||||
// 收缩状态的节点,都跑收缩动画
|
||||
if (node.get('collapsed')) {
|
||||
self.collapsePosition(node, point);
|
||||
} else {
|
||||
// 还是展开状态的子节点,也做位移动画
|
||||
Util.each(data.children, child => {
|
||||
self.animateChild(child, data);
|
||||
});
|
||||
}
|
||||
node.show();
|
||||
},
|
||||
// 收缩的节点的动画终态是最顶收缩态节点的坐标
|
||||
collapsePosition(node, toPoint) {
|
||||
const self = this;
|
||||
Util.each(node.get('model').children, child => {
|
||||
child = self.graph.findById(child.id);
|
||||
const model = child.get('model');
|
||||
if (!child.get('originPosition')) {
|
||||
child.set('originPosition', { x: model.x, y: model.y });
|
||||
}
|
||||
child.set('toPosition', {
|
||||
x: toPoint.x,
|
||||
y: toPoint.y
|
||||
});
|
||||
child.set('shouldHide', true);
|
||||
if (!child.get('collapsed')) {
|
||||
self.collapsePosition(child, toPoint);
|
||||
}
|
||||
});
|
||||
node.getOutEdges().forEach(edge => {
|
||||
edge.hide();
|
||||
});
|
||||
},
|
||||
// 张开的节点,从startPoint开始,动画到节点原本的位置
|
||||
expandPosition(item, startPoint) {
|
||||
const self = this;
|
||||
const children = item.get('model').children;
|
||||
Util.each(children, child => {
|
||||
child = self.graph.findById(child.id);
|
||||
self.graph.showItem(child);
|
||||
const origin = child.get('originPosition');
|
||||
child.set('toPosition', { x: origin.x, y: origin.y });
|
||||
if (!child.get('collapsed')) {
|
||||
self.expandPosition(child, startPoint);
|
||||
}
|
||||
});
|
||||
},
|
||||
// 设置子节点的状态
|
||||
setChildrenState(item, state, enabled) {
|
||||
const self = this;
|
||||
// 如果父节点展开了,子节点还有原本就收缩的,不设置这个子树的状态
|
||||
if (!enabled && item.get('collapsed')) {
|
||||
return;
|
||||
}
|
||||
self.graph.setItemState(item, state, enabled);
|
||||
Util.each(item.get('model').children, child => {
|
||||
const node = self.graph.findById(child.id);
|
||||
self.setChildrenState(node, state, enabled);
|
||||
});
|
||||
},
|
||||
// 不包含动画,仅展示展开节点
|
||||
expand(item) {
|
||||
const self = this;
|
||||
const graph = self.graph;
|
||||
if (item.get('collapsed')) {
|
||||
graph.showItem(item);
|
||||
return;
|
||||
}
|
||||
graph.showItem(item);
|
||||
Util.each(item.get('model').children, child => {
|
||||
const node = self.graph.findById(child.id);
|
||||
self.expand(node);
|
||||
});
|
||||
},
|
||||
// 不包含动画,仅隐藏收缩节点
|
||||
collapse(item) {
|
||||
const self = this;
|
||||
const graph = self.graph;
|
||||
graph.setItemState(item, 'collapsed', true);
|
||||
Util.each(item.get('model').children, child => {
|
||||
const node = graph.findById(child.id);
|
||||
self.collapse(node);
|
||||
self.graph.hideItem(node);
|
||||
});
|
||||
this.onChange(item, collapsed);
|
||||
this.graph.refreshLayout();
|
||||
}
|
||||
};
|
||||
|
@ -1,3 +1,4 @@
|
||||
const Hierarchy = require('@antv/hierarchy');
|
||||
const Util = require('../util');
|
||||
const Graph = require('./graph');
|
||||
|
||||
@ -13,6 +14,18 @@ function indexOfChild(children, child) {
|
||||
}
|
||||
|
||||
class TreeGraph extends Graph {
|
||||
constructor(cfg) {
|
||||
super(cfg);
|
||||
// 用于缓存动画结束后需要删除的节点
|
||||
this.set('removeList', []);
|
||||
this.set('layoutMethod', this._getLayout());
|
||||
}
|
||||
getDefaultCfg() {
|
||||
const cfg = super.getDefaultCfg();
|
||||
// 树图默认打开动画
|
||||
cfg.animate = true;
|
||||
return cfg;
|
||||
}
|
||||
/**
|
||||
* 根据data接口的数据渲染视图
|
||||
*/
|
||||
@ -24,17 +37,11 @@ class TreeGraph extends Graph {
|
||||
}
|
||||
self.clear();
|
||||
self.emit('beforerender');
|
||||
const autoPaint = self.get('autoPaint');
|
||||
self.setAutoPaint(false);
|
||||
const rootData = self._refreshLayout(data);
|
||||
const root = self._addChild(rootData, null);
|
||||
self.set('root', root);
|
||||
self.paint();
|
||||
self.setAutoPaint(autoPaint);
|
||||
self.refreshLayout();
|
||||
self.emit('afterrender');
|
||||
}
|
||||
/**
|
||||
* 添加子树
|
||||
* 添加子树到对应 id 的节点
|
||||
* @param {object} data 子树数据模型
|
||||
* @param {string} parent 子树的父节点id
|
||||
*/
|
||||
@ -44,23 +51,46 @@ class TreeGraph extends Graph {
|
||||
if (!Util.isString(parent)) {
|
||||
parent = parent.get('id');
|
||||
}
|
||||
const parentData = self.findDataById(parent);
|
||||
const parentData = self.findById(parent).getModel();
|
||||
if (!parentData.children) {
|
||||
parentData.children = [];
|
||||
}
|
||||
parentData.children.push(data);
|
||||
self.changeData(self.get('data'));
|
||||
self.changeData();
|
||||
}
|
||||
// 计算好layout的数据添加到graph中
|
||||
_addChild(data, parent) {
|
||||
_addChild(data, parent, animate) {
|
||||
const self = this;
|
||||
const node = self.addItem('node', data);
|
||||
const model = data.data;
|
||||
// model 中应存储真实的数据,特别是真实的 children
|
||||
model.x = data.x;
|
||||
model.y = data.y;
|
||||
const node = self.addItem('node', model);
|
||||
if (parent) {
|
||||
node.set('parent', parent);
|
||||
if (animate) {
|
||||
const origin = parent.get('origin');
|
||||
if (origin) {
|
||||
node.set('origin', origin);
|
||||
} else {
|
||||
const parentModel = parent.getModel();
|
||||
node.set('origin', {
|
||||
x: parentModel.x,
|
||||
y: parentModel.y
|
||||
});
|
||||
}
|
||||
}
|
||||
const childrenList = parent.get('children');
|
||||
if (!childrenList) {
|
||||
parent.set('children', [ node ]);
|
||||
} else {
|
||||
childrenList.push(node);
|
||||
}
|
||||
self.addItem('edge', { source: parent, target: node, id: parent.get('id') + ':' + node.get('id') });
|
||||
}
|
||||
// 渲染到视图上应参考布局的children, 避免多绘制了收起的节点
|
||||
Util.each(data.children, child => {
|
||||
self._addChild(child, node);
|
||||
self._addChild(child, node, animate);
|
||||
});
|
||||
return node;
|
||||
}
|
||||
@ -70,69 +100,83 @@ class TreeGraph extends Graph {
|
||||
*/
|
||||
changeData(data) {
|
||||
const self = this;
|
||||
if (!self.get('data')) {
|
||||
self.data(data);
|
||||
self.render();
|
||||
return;
|
||||
}
|
||||
const autoPaint = this.get('autoPaint');
|
||||
self.setAutoPaint(false);
|
||||
if (data) {
|
||||
self.data(data);
|
||||
self.render();
|
||||
} else {
|
||||
self.refreshLayout();
|
||||
}
|
||||
const root = self._refreshLayout();
|
||||
self._updateChild(root, null);
|
||||
self.paint();
|
||||
self.setAutoPaint(autoPaint);
|
||||
}
|
||||
/**
|
||||
* 差量更新子树
|
||||
* 更新源数据,差量更新子树
|
||||
* @param {object} data 子树数据模型
|
||||
* @param {string} parent 子树的父节点id
|
||||
*/
|
||||
updateChild(data, parent) {
|
||||
const self = this;
|
||||
// 如果有父节点,则更新父节点下的子树
|
||||
if (parent) {
|
||||
parent = self.findDataById(parent).get('model');
|
||||
}
|
||||
// 如果没有父节点,是全量的更新,直接重置data
|
||||
if (!parent) {
|
||||
self.changeData(data);
|
||||
return;
|
||||
}
|
||||
const parentModel = self.findById(parent).getModel();
|
||||
const current = self.findById(data.id);
|
||||
// 如果不存在该节点,则添加
|
||||
if (!current) {
|
||||
if (!parent.children) {
|
||||
parent.children = [];
|
||||
if (!parentModel.children) {
|
||||
parentModel.children = [ current ];
|
||||
} else {
|
||||
parentModel.children.push(data);
|
||||
}
|
||||
parent.children.push(data);
|
||||
} else {
|
||||
const index = indexOfChild(parent.children, data);
|
||||
const index = indexOfChild(parentModel.children, data);
|
||||
parent.children[index] = data;
|
||||
}
|
||||
self.changeData();
|
||||
}
|
||||
_updateChild(data, parent) {
|
||||
|
||||
// 将数据上的变更转换到视图上
|
||||
_updateChild(data, parent, animate) {
|
||||
const self = this;
|
||||
const current = self.findById(data.id);
|
||||
// 若子树不存在,整体添加即可
|
||||
if (!current) {
|
||||
self._addChild(data, parent);
|
||||
self._addChild(data, parent, animate);
|
||||
return;
|
||||
}
|
||||
// 更新新节点下所有子节点
|
||||
Util.each(data.children, child => {
|
||||
self._updateChild(child, current);
|
||||
self._updateChild(child, current, animate);
|
||||
});
|
||||
// 用现在节点的children来删除移除的子节点
|
||||
Util.each(current.get('model').children, child => {
|
||||
if (indexOfChild(data.children, child) === -1) {
|
||||
self._removeChild(child.id);
|
||||
// 用现在节点的children实例来删除移除的子节点
|
||||
const children = current.get('children');
|
||||
if (children) {
|
||||
const len = children.length;
|
||||
if (len > 0) {
|
||||
let child;
|
||||
for (let i = children.length - 1; i >= 0; i--) {
|
||||
child = children[i].getModel();
|
||||
if (indexOfChild(data.children, child) === -1) {
|
||||
self._removeChild(child.id, {
|
||||
x: data.x,
|
||||
y: data.y
|
||||
}, animate);
|
||||
// 更新父节点下缓存的子节点 item 实例列表
|
||||
children.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// 最后更新节点本身
|
||||
self.updateItem(current, data);
|
||||
}
|
||||
const model = current.getModel();
|
||||
if (animate) {
|
||||
// 如果有动画,先缓存节点运动再更新节点
|
||||
current.set('origin', {
|
||||
x: model.x,
|
||||
y: model.y
|
||||
});
|
||||
}
|
||||
model.x = data.x;
|
||||
model.y = data.y;
|
||||
}
|
||||
/**
|
||||
* 删除子树
|
||||
@ -146,24 +190,30 @@ class TreeGraph extends Graph {
|
||||
}
|
||||
const parent = node.get('parent');
|
||||
if (parent && !parent.destroyed) {
|
||||
const siblings = self.findDataById(parent.get('id')).children;
|
||||
const index = indexOfChild(siblings, node.get('model'));
|
||||
const siblings = parent.getModel().children;
|
||||
const index = indexOfChild(siblings, node.getModel());
|
||||
siblings.splice(index, 1);
|
||||
}
|
||||
self._removeChild(id);
|
||||
self.changeData();
|
||||
}
|
||||
// 删除子节点Item对象
|
||||
_removeChild(id) {
|
||||
_removeChild(id, to, animate) {
|
||||
const self = this;
|
||||
const node = self.findById(id);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
Util.each(node.get('model').children, child => {
|
||||
self._removeChild(child.id);
|
||||
Util.each(node.get('children'), child => {
|
||||
self._removeChild(child.getModel().id, to, animate);
|
||||
});
|
||||
self.removeItem(node);
|
||||
if (animate) {
|
||||
const model = node.getModel();
|
||||
node.set('to', to);
|
||||
node.set('origin', { x: model.x, y: model.y });
|
||||
self.get('removeList').push(node);
|
||||
} else {
|
||||
self.removeItem(node);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 导出图数据
|
||||
@ -172,19 +222,6 @@ class TreeGraph extends Graph {
|
||||
save() {
|
||||
return this.get('data');
|
||||
}
|
||||
// 数据变更后,刷新图布局
|
||||
_refreshLayout() {
|
||||
let root = this.get('data');
|
||||
const layout = this.get('layout');
|
||||
if ((!layout) && (!(root.x && root.y))) {
|
||||
console.warn('tree graph accepts either a layout method or calculated data');
|
||||
return;
|
||||
}
|
||||
if (layout) {
|
||||
root = layout(root);
|
||||
}
|
||||
return root;
|
||||
}
|
||||
/**
|
||||
* 根据id获取对应的源数据
|
||||
* @param {string|object} id 元素id
|
||||
@ -218,16 +255,37 @@ class TreeGraph extends Graph {
|
||||
*/
|
||||
changeLayout(layout) {
|
||||
const self = this;
|
||||
self.set('layout', layout);
|
||||
if (layout) {
|
||||
const autoPaint = self.get('autoPaint');
|
||||
self.setAutoPaint(false);
|
||||
const root = self._refreshLayout();
|
||||
self._updateChild(root, null);
|
||||
self.fitView();
|
||||
self.paint();
|
||||
self.setAutoPaint(autoPaint);
|
||||
if (!layout) {
|
||||
console.warn('layout cannot be null');
|
||||
return;
|
||||
}
|
||||
self.set('layout', self._getLayout());
|
||||
if (layout) {
|
||||
self.refreshLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据目前的 data 刷新布局,更新到画布上。用于变更数据之后刷新视图。
|
||||
*/
|
||||
refreshLayout() {
|
||||
const self = this;
|
||||
const data = self.get('data');
|
||||
const layoutData = self.get('layoutMethod')(data);
|
||||
const animate = self.get('animate');
|
||||
const autoPaint = self.get('autoPaint');
|
||||
self.emit('beforerefreshlayout', { data, layoutData });
|
||||
self.setAutoPaint(false);
|
||||
self._updateChild(layoutData, null, animate);
|
||||
if (!animate) {
|
||||
// 如果没有动画,目前仅更新了节点的位置,刷新一下边的样式
|
||||
self.refreshPositions();
|
||||
self.paint();
|
||||
} else {
|
||||
self.layoutAnimate(layoutData, null);
|
||||
}
|
||||
self.setAutoPaint(autoPaint);
|
||||
self.emit('afterrefreshlayout', { data, layoutData });
|
||||
}
|
||||
/**
|
||||
* 布局动画接口,用于数据更新时做节点位置更新的动画
|
||||
@ -238,11 +296,17 @@ class TreeGraph extends Graph {
|
||||
* @param {function} callback 动画结束的回调
|
||||
* @param {number} delay 动画延迟执行(ms)
|
||||
*/
|
||||
layoutAnimate(data, onFrame, duration = 500, ease = 'easeLinear', callback, delay = 0) {
|
||||
layoutAnimate(data, onFrame) {
|
||||
const self = this;
|
||||
const animateCfg = this.get('animateCfg');
|
||||
self.emit('layoutanimatestart', { data });
|
||||
const autoPaint = self.get('autoPaint');
|
||||
self.setAutoPaint(false);
|
||||
// 如果边中没有指定锚点,但是本身有锚点控制,在动画过程中保持锚点不变
|
||||
self.getEdges().forEach(edge => {
|
||||
const model = edge.get('model');
|
||||
if (!model.sourceAnchor) {
|
||||
model.sourceAnchor = edge.get('sourceAnchorIndex');
|
||||
}
|
||||
});
|
||||
this.get('canvas').animate({
|
||||
onFrame(ratio) {
|
||||
Util.traverseTree(data, child => {
|
||||
@ -252,8 +316,7 @@ class TreeGraph extends Graph {
|
||||
if (!origin) {
|
||||
origin = {
|
||||
x: model.x,
|
||||
y: model.y,
|
||||
style: model.style
|
||||
y: model.y
|
||||
};
|
||||
node.set('origin', origin);
|
||||
}
|
||||
@ -261,22 +324,32 @@ class TreeGraph extends Graph {
|
||||
const attrs = onFrame(node, ratio, origin, data);
|
||||
node.set('model', Util.mix(model, attrs));
|
||||
} else {
|
||||
model.x = origin.x + (data.x - origin.x) * ratio;
|
||||
model.y = origin.y + (data.y - origin.y) * ratio;
|
||||
model.x = origin.x + (child.x - origin.x) * ratio;
|
||||
model.y = origin.y + (child.y - origin.y) * ratio;
|
||||
}
|
||||
});
|
||||
Util.each(self.get('removeList'), node => {
|
||||
const model = node.getModel();
|
||||
const from = node.get('origin');
|
||||
const to = node.get('to');
|
||||
model.x = from.x + (to.x - from.x) * ratio;
|
||||
model.y = from.y + (to.y - from.y) * ratio;
|
||||
});
|
||||
self.refreshPositions();
|
||||
}
|
||||
}, duration, ease, () => {
|
||||
self.setAutoPaint(autoPaint);
|
||||
self.emit('layoutanimateend', { data });
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
Util.each(self.get('nodes'), node => {
|
||||
}, animateCfg.duration, animateCfg.ease, () => {
|
||||
Util.each(self.getNodes(), node => {
|
||||
node.set('origin', null);
|
||||
});
|
||||
}, delay);
|
||||
Util.each(self.get('removeList'), node => {
|
||||
self.removeItem(node);
|
||||
});
|
||||
self.set('removeList', []);
|
||||
if (animateCfg.callback) {
|
||||
animateCfg.callback();
|
||||
}
|
||||
self.emit('layoutanimateend', { data });
|
||||
}, animateCfg.delay);
|
||||
}
|
||||
/**
|
||||
* 立即停止布局动画
|
||||
@ -294,6 +367,24 @@ class TreeGraph extends Graph {
|
||||
isLayoutAnimating() {
|
||||
return this.layoutAnimating;
|
||||
}
|
||||
_getLayout() {
|
||||
const layout = this.get('layout');
|
||||
if (!layout) {
|
||||
return null;
|
||||
}
|
||||
if (typeof layout === 'function') {
|
||||
return layout;
|
||||
}
|
||||
if (!layout.type) {
|
||||
layout.type = 'dendrogram';
|
||||
}
|
||||
if (!layout.direction) {
|
||||
layout.direction = 'TB';
|
||||
}
|
||||
return function(data) {
|
||||
return Hierarchy[layout.type](data, layout);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TreeGraph;
|
||||
|
@ -78,6 +78,9 @@ class Edge extends Item {
|
||||
}
|
||||
// 如果锚点没有对应的点或者没有锚点,则直接计算连接点
|
||||
point = point || item.getLinkPoint(prePoint);
|
||||
if (!Util.isNil(point.index)) {
|
||||
this.set(name + 'AnchorIndex', point.index);
|
||||
}
|
||||
}
|
||||
return point;
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ const Item = require('./item');
|
||||
const CACHE_ANCHOR_POINTS = 'anchorPointsCache';
|
||||
|
||||
function getNearestPoint(points, curPoint) {
|
||||
let index = 0;
|
||||
let nearestPoint = points[0];
|
||||
let minDistance = pointDistance(points[0], curPoint);
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
@ -16,8 +17,10 @@ function getNearestPoint(points, curPoint) {
|
||||
if (distance < minDistance) {
|
||||
nearestPoint = point;
|
||||
minDistance = distance;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
nearestPoint.anchorIndex = index;
|
||||
return nearestPoint;
|
||||
}
|
||||
|
||||
|
@ -6,301 +6,17 @@ const div = document.createElement('div');
|
||||
div.id = 'tree-spec';
|
||||
document.body.appendChild(div);
|
||||
|
||||
function isNumberEqual(a, b) {
|
||||
return Math.abs(a - b) < 0.0001;
|
||||
}
|
||||
|
||||
describe('tree graph without layout', () => {
|
||||
const graph = new G6.TreeGraph({
|
||||
container: div,
|
||||
width: 500,
|
||||
height: 500,
|
||||
pixelRatio: 2,
|
||||
modes: {
|
||||
default: [ 'drag-canvas' ]
|
||||
}
|
||||
});
|
||||
let root = {
|
||||
isRoot: true,
|
||||
id: 'Root',
|
||||
children: [
|
||||
{
|
||||
id: 'SubTreeNode1',
|
||||
children: [
|
||||
{
|
||||
id: 'SubTreeNode1.1'
|
||||
},
|
||||
{
|
||||
id: 'SubTreeNode1.2'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'SubTreeNode2'
|
||||
}
|
||||
]
|
||||
};
|
||||
const NODE_SIZE = 16;
|
||||
const PEM = 5;
|
||||
root = Hierarchy.dendrogram(root, {
|
||||
direction: 'TB', // H / V / LR / RL / TB / BT
|
||||
nodeSep: 200,
|
||||
getId(d) {
|
||||
return d.id;
|
||||
},
|
||||
getHeight() {
|
||||
return NODE_SIZE;
|
||||
},
|
||||
getWidth() {
|
||||
return NODE_SIZE;
|
||||
},
|
||||
getHGap() {
|
||||
return 10;
|
||||
},
|
||||
getVGap() {
|
||||
return 10;
|
||||
},
|
||||
getSubTreeSep(d) {
|
||||
if (!d.children || !d.children.length) {
|
||||
return 0;
|
||||
}
|
||||
return PEM;
|
||||
}
|
||||
});
|
||||
it('render tree graph', () => {
|
||||
graph.data(root);
|
||||
graph.render();
|
||||
const rootNode = graph.get('root');
|
||||
expect(rootNode).not.to.be.undefined;
|
||||
expect(rootNode.get('model').id).to.equal('Root');
|
||||
expect(Object.keys(graph.get('itemMap')).length).to.equal(9);
|
||||
const edge = graph.findById('Root:SubTreeNode1');
|
||||
expect(edge).not.to.be.undefined;
|
||||
expect(edge.get('source')).to.equal(graph.findById('Root'));
|
||||
expect(edge.get('target')).to.equal(graph.findById('SubTreeNode1'));
|
||||
});
|
||||
it('change data', () => {
|
||||
let data = {
|
||||
isRoot: true,
|
||||
id: 'Root',
|
||||
children: [
|
||||
{
|
||||
id: 'SubTreeNode1',
|
||||
children: [
|
||||
{
|
||||
id: 'SubTreeNode1.1'
|
||||
},
|
||||
{
|
||||
id: 'SubTreeNode1.2'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'SubTreeNode3'
|
||||
}, {
|
||||
id: 'SubTreeNode4',
|
||||
children: [{ id: 'SubTreeNode4.1' }]
|
||||
}
|
||||
]
|
||||
};
|
||||
const root = graph.get('root');
|
||||
data = Hierarchy.dendrogram(data, {
|
||||
direction: 'LR', // H / V / LR / RL / TB / BT
|
||||
nodeSep: 200,
|
||||
getId(d) {
|
||||
return d.id;
|
||||
},
|
||||
getHeight() {
|
||||
return NODE_SIZE;
|
||||
},
|
||||
getWidth() {
|
||||
return NODE_SIZE;
|
||||
},
|
||||
getHGap() {
|
||||
return 10;
|
||||
},
|
||||
getVGap() {
|
||||
return 10;
|
||||
},
|
||||
getSubTreeSep(d) {
|
||||
if (!d.children || !d.children.length) {
|
||||
return 0;
|
||||
}
|
||||
return PEM;
|
||||
}
|
||||
});
|
||||
graph.changeData(data);
|
||||
graph.fitView();
|
||||
expect(graph.get('root')).to.equal(root);
|
||||
expect(Object.keys(graph.get('itemMap')).length).to.equal(13);
|
||||
expect(graph.findById('SubTreeNode2')).to.be.undefined;
|
||||
expect(graph.findById('SubTreeNode3')).not.to.be.undefined;
|
||||
expect(graph.findById('SubTreeNode4')).not.to.be.undefined;
|
||||
const edge = graph.findById('SubTreeNode4:SubTreeNode4.1');
|
||||
expect(edge).not.to.be.undefined;
|
||||
expect(edge.get('source')).to.equal(graph.findById('SubTreeNode4'));
|
||||
expect(edge.get('target')).to.equal(graph.findById('SubTreeNode4.1'));
|
||||
});
|
||||
it('add child', () => {
|
||||
const parent = graph.findById('SubTreeNode3');
|
||||
const child = { id: 'SubTreeNode3.1', x: 100, y: 100, shape: 'rect', children: [{ x: 150, y: 150, id: 'SubTreeNode3.1.1' }] };
|
||||
graph.addChild(child, parent);
|
||||
const children = parent.get('model').children;
|
||||
expect(children).not.to.be.undefined;
|
||||
expect(children.length).to.equal(1);
|
||||
expect(children[0].id).to.equal('SubTreeNode3.1');
|
||||
expect(graph.findById('SubTreeNode3.1')).not.to.be.undefined;
|
||||
expect(graph.findById('SubTreeNode3:SubTreeNode3.1')).not.to.be.undefined;
|
||||
expect(graph.findById('SubTreeNode3.1.1')).not.to.be.undefined;
|
||||
expect(graph.findById('SubTreeNode3.1:SubTreeNode3.1.1')).not.to.be.undefined;
|
||||
});
|
||||
it('remove child', () => {
|
||||
graph.removeChild('SubTreeNode3.1');
|
||||
const parent = graph.findById('SubTreeNode3');
|
||||
const children = parent.get('model').children;
|
||||
expect(children.length).to.equal(0);
|
||||
expect(graph.findById('SubTreeNode3.1')).to.be.undefined;
|
||||
expect(graph.findById('SubTreeNode3:SubTreeNode3.1')).to.be.undefined;
|
||||
expect(graph.findById('SubTreeNode3.1.1')).to.be.undefined;
|
||||
expect(graph.findById('SubTreeNode3.1:SubTreeNode3.1.1')).to.be.undefined;
|
||||
});
|
||||
it('collapse & expand with default animate', done => {
|
||||
const parent = graph.findById('SubTreeNode1');
|
||||
const child = graph.findById('SubTreeNode1.1');
|
||||
let collapsed = true;
|
||||
graph.addBehaviors({
|
||||
type: 'collapse-expand',
|
||||
animate: {
|
||||
duration: 500,
|
||||
callback() {
|
||||
if (collapsed) {
|
||||
expect(parent.get('collapsed')).to.be.true;
|
||||
expect(parent.hasState('collapsed')).to.be.true;
|
||||
expect(isNumberEqual(child.get('model').x, parent.get('model').x)).to.be.true;
|
||||
expect(!!child.get('collapsed')).to.be.false;
|
||||
expect(child.hasState('collapsed')).to.be.true;
|
||||
expect(isNumberEqual(child.get('model').y, parent.get('model').y)).to.be.true;
|
||||
} else {
|
||||
expect(parent.get('collapsed')).to.be.false;
|
||||
expect(parent.hasState('collapsed')).to.be.false;
|
||||
expect(child.get('model').x).not.to.equal(parent.get('model').x);
|
||||
expect(!!child.get('collapsed')).to.be.false;
|
||||
expect(child.hasState('collapsed')).to.be.false;
|
||||
expect(child.get('model').y).not.to.equal(parent.get('model').y);
|
||||
graph.removeBehaviors('collapse-expand', 'default');
|
||||
done();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 'default');
|
||||
graph.emit('node:click', { item: parent });
|
||||
setTimeout(() => {
|
||||
collapsed = false;
|
||||
graph.emit('node:click', { item: parent });
|
||||
}, 600);
|
||||
});
|
||||
it('collapse & expand without onChange', () => {
|
||||
graph.addBehaviors({
|
||||
type: 'collapse-expand',
|
||||
animate: false
|
||||
}, 'default');
|
||||
const root = graph.get('root');
|
||||
const child = graph.findById('SubTreeNode1');
|
||||
const leave = graph.findById('SubTreeNode1.1');
|
||||
graph.emit('node:click', { item: root });
|
||||
expect(root.isVisible()).to.be.true;
|
||||
expect(child.isVisible()).to.be.false;
|
||||
expect(leave.isVisible()).to.be.false;
|
||||
expect(child.hasState('collapsed')).to.be.true;
|
||||
expect(leave.hasState('collapsed')).to.be.true;
|
||||
graph.emit('node:click', { item: root });
|
||||
expect(root.isVisible()).to.be.true;
|
||||
expect(child.isVisible()).to.be.true;
|
||||
expect(leave.isVisible()).to.be.true;
|
||||
expect(child.hasState('collapsed')).to.be.false;
|
||||
expect(leave.hasState('collapsed')).to.be.false;
|
||||
graph.removeBehaviors('collapse-expand', 'default');
|
||||
});
|
||||
it('collapse & expand animate', done => {
|
||||
G6.Global.defaultNode.style.fill = '#fff';
|
||||
const parent = graph.findById('SubTreeNode1');
|
||||
const child = graph.findById('SubTreeNode1.1');
|
||||
let collapsed = true;
|
||||
graph.addBehaviors({
|
||||
type: 'collapse-expand',
|
||||
animate: {
|
||||
duration: 500,
|
||||
callback() {
|
||||
if (collapsed) {
|
||||
expect(parent.get('collapsed')).to.be.true;
|
||||
expect(parent.hasState('collapsed')).to.be.true;
|
||||
expect(isNumberEqual(child.get('model').x, parent.get('model').x)).to.be.true;
|
||||
expect(!!child.get('collapsed')).to.be.false;
|
||||
expect(child.hasState('collapsed')).to.be.true;
|
||||
expect(isNumberEqual(child.get('model').y, parent.get('model').y)).to.be.true;
|
||||
} else {
|
||||
expect(parent.get('collapsed')).to.be.false;
|
||||
expect(parent.hasState('collapsed')).to.be.false;
|
||||
expect(child.get('model').x).not.to.equal(parent.get('model').x);
|
||||
expect(!!child.get('collapsed')).to.be.false;
|
||||
expect(child.hasState('collapsed')).to.be.false;
|
||||
expect(child.get('model').y).not.to.equal(parent.get('model').y);
|
||||
graph.removeBehaviors('collapse-expand', 'default');
|
||||
done();
|
||||
}
|
||||
}
|
||||
},
|
||||
onChange(item, collapsed) {
|
||||
let data = graph.get('data');
|
||||
item.get('model').data.collapsed = collapsed;
|
||||
data = Hierarchy.dendrogram(data.data, {
|
||||
direction: 'LR', // H / V / LR / RL / TB / BT
|
||||
nodeSep: 200,
|
||||
getId(d) {
|
||||
return d.id;
|
||||
},
|
||||
getHeight() {
|
||||
return NODE_SIZE;
|
||||
},
|
||||
getWidth() {
|
||||
return NODE_SIZE;
|
||||
},
|
||||
getHGap() {
|
||||
return 10;
|
||||
},
|
||||
getVGap() {
|
||||
return 10;
|
||||
},
|
||||
getSubTreeSep(d) {
|
||||
if (!d.children || !d.children.length) {
|
||||
return 0;
|
||||
}
|
||||
return PEM;
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
}, 'default');
|
||||
graph.emit('node:click', { item: parent });
|
||||
setTimeout(() => {
|
||||
collapsed = false;
|
||||
graph.emit('node:click', { item: parent });
|
||||
}, 600);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tree graph with layout', () => {
|
||||
let count = 0;
|
||||
describe('tree graph without animate', () => {
|
||||
const graph = new G6.TreeGraph({
|
||||
container: div,
|
||||
width: 500,
|
||||
height: 500,
|
||||
pixelRatio: 2,
|
||||
animate: false,
|
||||
modes: {
|
||||
default: [ 'drag-canvas' ]
|
||||
},
|
||||
layout: data => {
|
||||
count++;
|
||||
return Hierarchy.dendrogram(data, {
|
||||
direction: 'LR', // H / V / LR / RL / TB / BT
|
||||
nodeSep: 50,
|
||||
@ -332,16 +48,12 @@ describe('tree graph with layout', () => {
|
||||
graph.data(data);
|
||||
graph.render();
|
||||
graph.fitView();
|
||||
const rootNode = graph.get('root');
|
||||
expect(rootNode).not.to.be.undefined;
|
||||
expect(rootNode.get('model').id).to.equal('Root');
|
||||
expect(Object.keys(graph.get('itemMap')).length).to.equal(9);
|
||||
const edge = graph.findById('Root:SubTreeNode1');
|
||||
expect(edge).not.to.be.undefined;
|
||||
expect(edge.get('source')).to.equal(graph.findById('Root'));
|
||||
expect(edge.get('target')).to.equal(graph.findById('SubTreeNode1'));
|
||||
expect(graph.save()).to.equal(data);
|
||||
expect(count).to.equal(1);
|
||||
});
|
||||
it('changeData', () => {
|
||||
const data = {
|
||||
@ -377,7 +89,6 @@ describe('tree graph with layout', () => {
|
||||
expect(edge).not.to.be.undefined;
|
||||
expect(edge.get('source')).to.equal(graph.findById('SubTreeNode4'));
|
||||
expect(edge.get('target')).to.equal(graph.findById('SubTreeNode4.1'));
|
||||
expect(count).to.equal(2);
|
||||
});
|
||||
it('add child', () => {
|
||||
const parent = graph.findById('SubTreeNode3');
|
||||
@ -391,7 +102,6 @@ describe('tree graph with layout', () => {
|
||||
expect(graph.findById('SubTreeNode3:SubTreeNode3.1')).not.to.be.undefined;
|
||||
expect(graph.findById('SubTreeNode3.1.1')).not.to.be.undefined;
|
||||
expect(graph.findById('SubTreeNode3.1:SubTreeNode3.1.1')).not.to.be.undefined;
|
||||
expect(count).to.equal(3);
|
||||
});
|
||||
it('remove child', () => {
|
||||
graph.removeChild('SubTreeNode3.1');
|
||||
@ -402,138 +112,146 @@ describe('tree graph with layout', () => {
|
||||
expect(graph.findById('SubTreeNode3:SubTreeNode3.1')).to.be.undefined;
|
||||
expect(graph.findById('SubTreeNode3.1.1')).to.be.undefined;
|
||||
expect(graph.findById('SubTreeNode3.1:SubTreeNode3.1.1')).to.be.undefined;
|
||||
expect(count).to.equal(4);
|
||||
});
|
||||
it('collapse & expand with layout', done => {
|
||||
const parent = graph.findById('SubTreeNode1');
|
||||
const child = graph.findById('SubTreeNode1.1');
|
||||
let child = graph.findById('SubTreeNode1.1');
|
||||
let collapsed = true;
|
||||
graph.addBehaviors({
|
||||
type: 'collapse-expand',
|
||||
onChange: (item, collapsed) => {
|
||||
const data = item.get('model').data;
|
||||
data.collapsed = collapsed;
|
||||
return false;
|
||||
},
|
||||
animate: {
|
||||
callback() {
|
||||
if (collapsed) {
|
||||
expect(parent.get('collapsed')).to.be.true;
|
||||
expect(parent.hasState('collapsed')).to.be.true;
|
||||
expect(isNumberEqual(child.get('model').x, parent.get('model').x)).to.be.true;
|
||||
expect(!!child.get('collapsed')).to.be.false;
|
||||
expect(child.hasState('collapsed')).to.be.true;
|
||||
expect(isNumberEqual(child.get('model').y, parent.get('model').y)).to.be.true;
|
||||
} else {
|
||||
expect(parent.get('collapsed')).to.be.false;
|
||||
expect(parent.hasState('collapsed')).to.be.false;
|
||||
expect(child.get('model').x).not.to.equal(parent.get('model').x);
|
||||
expect(!!child.get('collapsed')).to.be.false;
|
||||
expect(child.hasState('collapsed')).to.be.false;
|
||||
expect(child.get('model').y).not.to.equal(parent.get('model').y);
|
||||
done();
|
||||
}
|
||||
}
|
||||
} }, 'default');
|
||||
graph.on('afterrefreshlayout', () => {
|
||||
if (collapsed) {
|
||||
expect(parent.getModel().collapsed).to.be.true;
|
||||
expect(child.destroyed).to.be.true;
|
||||
} else {
|
||||
child = graph.findById('SubTreeNode1.1');
|
||||
expect(parent.getModel().collapsed).to.be.false;
|
||||
expect(child.get('model').x).not.to.equal(parent.get('model').x);
|
||||
expect(!!child.getModel().collapsed).to.be.false;
|
||||
expect(child.get('model').y).not.to.equal(parent.get('model').y);
|
||||
done();
|
||||
}
|
||||
});
|
||||
graph.addBehaviors('collapse-expand', 'default');
|
||||
graph.emit('node:click', { item: parent });
|
||||
setTimeout(() => {
|
||||
collapsed = false;
|
||||
graph.emit('node:click', { item: parent });
|
||||
}, 600);
|
||||
});
|
||||
/* const treeData = {
|
||||
isRoot: true,
|
||||
id: 'Root',
|
||||
children: [
|
||||
{
|
||||
id: 'SubTreeNode1',
|
||||
children: [
|
||||
{
|
||||
id: 'SubTreeNode1.1'
|
||||
},
|
||||
{
|
||||
id: 'SubTreeNode1.2'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'SubTreeNode2',
|
||||
children: [
|
||||
{
|
||||
id: 'SubTreeNode2.1'
|
||||
},
|
||||
{
|
||||
id: 'SubTreeNode2.2',
|
||||
children: [
|
||||
{
|
||||
id: 'SubTreeNode1.2.1'
|
||||
},
|
||||
{
|
||||
id: 'SubTreeNode1.2.2'
|
||||
},
|
||||
{
|
||||
id: 'SubTreeNode1.2.3'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}, {
|
||||
id: 'SubTreeNode3'
|
||||
}, {
|
||||
id: 'SubTreeNode4'
|
||||
}, {
|
||||
id: 'SubTreeNode5'
|
||||
}, {
|
||||
id: 'SubTreeNode6'
|
||||
}, {
|
||||
id: 'SubTreeNode7',
|
||||
children: [
|
||||
{
|
||||
id: 'SubTreeNode3.1'
|
||||
},
|
||||
{
|
||||
id: 'SubTreeNode3.2'
|
||||
},
|
||||
{
|
||||
id: 'SubTreeNode3.3'
|
||||
}
|
||||
]
|
||||
}, {
|
||||
id: 'SubTreeNode8'
|
||||
}, {
|
||||
id: 'SubTreeNode9'
|
||||
}, {
|
||||
id: 'SubTreeNode10'
|
||||
}, {
|
||||
id: 'SubTreeNode11'
|
||||
}
|
||||
]
|
||||
};
|
||||
it('radial layout', () => {
|
||||
graph.data(treeData);
|
||||
graph.render();
|
||||
G6.Util.radialLayout(graph, 'LR');
|
||||
});
|
||||
describe('tree graph with animate', () => {
|
||||
const graph = new G6.TreeGraph({
|
||||
container: div,
|
||||
width: 500,
|
||||
height: 500,
|
||||
pixelRatio: 2,
|
||||
modes: {
|
||||
default: [ 'drag-canvas' ]
|
||||
},
|
||||
layout: {
|
||||
type: 'dendrogram',
|
||||
direction: 'LR',
|
||||
nodeSep: 50,
|
||||
rankSep: 100
|
||||
}
|
||||
});
|
||||
it('vertical layout to radial layout', () => {
|
||||
const graph = new G6.TreeGraph({
|
||||
container: div,
|
||||
width: 500,
|
||||
height: 500,
|
||||
pixelRatio: 2,
|
||||
modes: {
|
||||
default: [ 'drag-canvas' ]
|
||||
},
|
||||
layout: data => {
|
||||
count++;
|
||||
return Hierarchy.dendrogram(data, {
|
||||
direction: 'TB', // H / V / LR / RL / TB / BT
|
||||
nodeSep: 50,
|
||||
rankSep: 100
|
||||
});
|
||||
it('layout init', done => {
|
||||
const data = {
|
||||
isRoot: true,
|
||||
id: 'Root',
|
||||
children: [
|
||||
{
|
||||
id: 'SubTreeNode1',
|
||||
children: [
|
||||
{
|
||||
id: 'SubTreeNode1.1'
|
||||
},
|
||||
{
|
||||
id: 'SubTreeNode1.2'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'SubTreeNode2'
|
||||
}
|
||||
]
|
||||
};
|
||||
graph.data(data);
|
||||
graph.render();
|
||||
graph.fitView();
|
||||
const layoutMethod = graph.get('layoutMethod');
|
||||
expect(layoutMethod).not.to.be.undefined;
|
||||
expect(typeof layoutMethod).to.equal('function');
|
||||
expect(Object.keys(graph.get('itemMap')).length).to.equal(9);
|
||||
const edge = graph.findById('Root:SubTreeNode1');
|
||||
expect(edge).not.to.be.undefined;
|
||||
expect(edge.get('source')).to.equal(graph.findById('Root'));
|
||||
expect(edge.get('target')).to.equal(graph.findById('SubTreeNode1'));
|
||||
expect(graph.save()).to.equal(data);
|
||||
expect(JSON.stringify(data)).not.to.throw;
|
||||
graph.on('layoutanimateend', () => { done(); });
|
||||
});
|
||||
it('changeData', done => {
|
||||
graph.removeEvent();
|
||||
const data = {
|
||||
isRoot: true,
|
||||
id: 'Root',
|
||||
children: [
|
||||
{
|
||||
id: 'SubTreeNode1',
|
||||
children: [
|
||||
{
|
||||
id: 'SubTreeNode1.1'
|
||||
},
|
||||
{
|
||||
id: 'SubTreeNode1.2'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'SubTreeNode3'
|
||||
}, {
|
||||
id: 'SubTreeNode4',
|
||||
children: [{ id: 'SubTreeNode4.1' }]
|
||||
}
|
||||
]
|
||||
};
|
||||
graph.changeData(data);
|
||||
expect(graph.save()).to.equal(data);
|
||||
graph.on('layoutanimateend', () => {
|
||||
expect(Object.keys(graph.get('itemMap')).length).to.equal(13);
|
||||
expect(graph.findById('SubTreeNode2')).to.be.undefined;
|
||||
expect(graph.findById('SubTreeNode3')).not.to.be.undefined;
|
||||
expect(graph.findById('SubTreeNode4')).not.to.be.undefined;
|
||||
const edge = graph.findById('SubTreeNode4:SubTreeNode4.1');
|
||||
expect(edge).not.to.be.undefined;
|
||||
expect(edge.get('source')).to.equal(graph.findById('SubTreeNode4'));
|
||||
expect(edge.get('target')).to.equal(graph.findById('SubTreeNode4.1'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('collapse & expand', done => {
|
||||
graph.removeEvent();
|
||||
const parent = graph.findById('SubTreeNode1');
|
||||
let child = graph.findById('SubTreeNode1.1');
|
||||
let collapsed = true;
|
||||
graph.on('layoutanimateend', () => {
|
||||
if (collapsed) {
|
||||
expect(parent.getModel().collapsed).to.be.true;
|
||||
expect(child.destroyed).to.be.true;
|
||||
} else {
|
||||
child = graph.findById('SubTreeNode1.1');
|
||||
expect(parent.getModel().collapsed).to.be.false;
|
||||
expect(child.get('model').x).not.to.equal(parent.get('model').x);
|
||||
expect(!!child.getModel().collapsed).to.be.false;
|
||||
expect(child.get('model').y).not.to.equal(parent.get('model').y);
|
||||
done();
|
||||
}
|
||||
});
|
||||
graph.data(treeData);
|
||||
graph.render();
|
||||
G6.Util.radialLayout(graph, 'TB');
|
||||
});*/
|
||||
graph.addBehaviors('collapse-expand', 'default');
|
||||
graph.emit('node:click', { item: parent });
|
||||
setTimeout(() => {
|
||||
collapsed = false;
|
||||
graph.emit('node:click', { item: parent });
|
||||
}, 600);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user