@antv/g6-pc
Version:
A Graph Visualization Framework in JavaScript
655 lines (654 loc) • 20.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _tslib = require("tslib");
var _hierarchy = _interopRequireDefault(require("@antv/hierarchy"));
var _util = require("@antv/util");
var _util2 = _interopRequireDefault(require("../util"));
var _deepClone = require("../util/deepClone");
var _graph = _interopRequireDefault(require("./graph"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
var radialLayout = _util2.default.radialLayout,
traverseTree = _util2.default.traverseTree;
var TreeGraph = /** @class */function (_super) {
(0, _tslib.__extends)(TreeGraph, _super);
function TreeGraph(cfg) {
var _this = _super.call(this, cfg) || this;
_this.layoutAnimating = false;
// 用于缓存动画结束后需要删除的节点
_this.set('removeList', []);
_this.set('layoutMethod', _this.getLayout());
return _this;
}
/**
* 通过 Layout 配置获取布局配置
*/
TreeGraph.prototype.getLayout = function () {
var 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 = layout.type === 'indented' ? 'LR' : 'TB';
}
if (layout.radial) {
return function (data) {
var layoutData = _hierarchy.default[layout.type](data, layout);
radialLayout(layoutData);
return layoutData;
};
}
return function (data) {
return _hierarchy.default[layout.type](data, layout);
};
};
/**
* 返回指定节点在树图数据中的索引
* @param children 树图数据
* @param child 树图中某一个 Item 的数据
*/
TreeGraph.indexOfChild = function (children, id) {
var index = -1;
// eslint-disable-next-line consistent-return
(0, _util.each)(children, function (former, i) {
if (id === former.id) {
index = i;
return false;
}
});
return index;
};
TreeGraph.prototype.getDefaultCfg = function () {
var cfg = _super.prototype.getDefaultCfg.call(this);
// 树图默认打开动画
cfg.animate = true;
return cfg;
};
/**
* 向🌲树中添加数据
* @param treeData 树图数据
* @param parent 父节点实例
* @param animate 是否开启动画
*/
TreeGraph.prototype.innerAddChild = function (treeData, parent, animate) {
var self = this;
var model = treeData.data;
if (model) {
// model 中应存储真实的数据,特别是真实的 children
model.x = treeData.x;
model.y = treeData.y;
model.depth = treeData.depth;
}
var node = self.addItem('node', model, false);
if (parent) {
node.set('parent', parent);
if (animate) {
var origin_1 = parent.get('originAttrs');
if (origin_1) {
node.set('originAttrs', origin_1);
} else {
var parentModel = parent.getModel();
node.set('originAttrs', {
x: parentModel.x,
y: parentModel.y
});
}
}
var childrenList = parent.get('children');
if (!childrenList) {
parent.set('children', [node]);
} else {
childrenList.push(node);
}
self.addItem('edge', {
source: parent.get('id'),
target: node.get('id'),
id: "".concat(parent.get('id'), ":").concat(node.get('id'))
}, false);
}
// 渲染到视图上应参考布局的children, 避免多绘制了收起的节点
(0, _util.each)(treeData.children || [], function (child) {
self.innerAddChild(child, node, animate);
});
self.emit('afteraddchild', {
item: node,
parent: parent
});
return node;
};
/**
* 将数据上的变更转换到视图上
* @param data
* @param parent
* @param animate
*/
TreeGraph.prototype.innerUpdateChild = function (data, parent, animate) {
var self = this;
var current = self.findById(data.id);
// 若子树不存在,整体添加即可
if (!current) {
self.innerAddChild(data, parent, animate);
return;
}
// 更新新节点下所有子节点
(0, _util.each)(data.children || [], function (child) {
self.innerUpdateChild(child, current, animate);
});
// 用现在节点的children实例来删除移除的子节点
var children = current.get('children');
if (children) {
var len = children.length;
if (len > 0) {
for (var i = children.length - 1; i >= 0; i--) {
var child = children[i].getModel();
if (TreeGraph.indexOfChild(data.children || [], child.id) === -1) {
self.innerRemoveChild(child.id, {
x: data.x,
y: data.y
}, animate);
// 更新父节点下缓存的子节点 item 实例列表
children.splice(i, 1);
}
}
}
}
var oriX;
var oriY;
if (current.get('originAttrs')) {
oriX = current.get('originAttrs').x;
oriY = current.get('originAttrs').y;
}
var model = current.getModel();
if (animate) {
// 如果有动画,先缓存节点运动再更新节点
current.set('originAttrs', {
x: model.x,
y: model.y
});
}
current.set('model', Object.assign(model, data.data));
if (oriX !== data.x || oriY !== data.y) {
current.updatePosition({
x: data.x,
y: data.y
});
}
};
/**
* 删除子节点Item对象
* @param id
* @param to
* @param animate
*/
TreeGraph.prototype.innerRemoveChild = function (id, to, animate) {
var self = this;
var node = self.findById(id);
if (!node) {
return;
}
(0, _util.each)(node.get('children'), function (child) {
self.innerRemoveChild(child.getModel().id, to, animate);
});
if (animate) {
var model = node.getModel();
node.set('to', to);
node.set('originAttrs', {
x: model.x,
y: model.y
});
self.get('removeList').push(node);
} else {
self.removeItem(node, false);
}
};
/**
* 更新数据模型,差量更新并重新渲染
* @param {object} data 数据模型
*/
TreeGraph.prototype.changeData = function (data, stack) {
if (stack === void 0) {
stack = true;
}
var self = this;
// 更改数据源后,取消所有状态
this.getNodes().map(function (node) {
return self.clearItemStates(node);
});
this.getEdges().map(function (edge) {
return self.clearItemStates(edge);
});
if (stack && this.get('enabledStack')) {
this.pushStack('changedata', {
before: self.get('originData'),
after: data || self.get('data')
});
}
if (data) {
self.data(data);
self.render(false);
} else {
self.layout(this.get('fitView'));
}
};
/**
* 已更名为 updateLayout,为保持兼容暂且保留。
* 更改并应用树布局算法
* @param {object} layout 布局算法
*/
TreeGraph.prototype.changeLayout = function (layout) {
// eslint-disable-next-line no-console
console.warn('Please call updateLayout instead of changeLayout. changeLayout will be discarded soon');
var self = this;
self.updateLayout(layout);
};
/**
* 更改并应用树布局算法
* @param {object} layout 布局算法
*/
TreeGraph.prototype.updateLayout = function (layout, align, alignPoint, stack) {
if (stack === void 0) {
stack = true;
}
var self = this;
if (!layout) {
// eslint-disable-next-line no-console
console.warn('layout cannot be null');
return;
}
if (stack && this.get('enabledStack')) {
this.pushStack('layout', {
before: self.get('layout'),
after: layout
});
}
self.set('layout', layout);
self.set('layoutMethod', self.getLayout());
self.layout();
// align the graph after layout
if (align) {
var toPoint = alignPoint;
if (!toPoint) {
if (align === 'begin') toPoint = {
x: 0,
y: 0
};else toPoint = {
x: this.getWidth() / 2,
y: this.getHeight() / 2
};
}
// translate to point coordinate system
toPoint = this.getPointByCanvas(toPoint.x, toPoint.y);
var matrix = this.getGroup().getMatrix() || [1, 0, 0, 0, 1, 0, 0, 0, 1];
toPoint.x = toPoint.x * matrix[0] + matrix[6];
toPoint.y = toPoint.y * matrix[0] + matrix[7];
var _a = this.getGroup().getCanvasBBox(),
minX = _a.minX,
maxX = _a.maxX,
minY = _a.minY,
maxY = _a.maxY;
var bboxPoint = {
x: (minX + maxX) / 2,
y: (minY + maxY) / 2
};
if (align === 'begin') {
bboxPoint.x = minX;
bboxPoint.y = minY;
}
this.translate(toPoint.x - bboxPoint.x, toPoint.y - bboxPoint.y);
}
};
/**
* 已更名为 layout,为保持兼容暂且保留。
* 根据目前的 data 刷新布局,更新到画布上。用于变更数据之后刷新视图。
* @param {boolean} fitView 更新布局时是否需要适应窗口
*/
TreeGraph.prototype.refreshLayout = function (fitView) {
// eslint-disable-next-line no-console
console.warn('Please call layout instead of refreshLayout. refreshLayout will be discarded soon');
var self = this;
self.layout(fitView);
};
/**
* 根据目前的 data 刷新布局,更新到画布上。用于变更数据之后刷新视图。
* @param {boolean} fitView 更新布局时是否需要适应窗口
*/
TreeGraph.prototype.layout = function (fitView) {
var _this = this;
var self = this;
var data = self.get('data');
var layoutMethod = self.get('layoutMethod');
var layoutConfig = self.get('layout');
var layoutData = data;
if (layoutConfig === null || layoutConfig === void 0 ? void 0 : layoutConfig.excludeInvisibles) {
data = (0, _util.clone)(self.get('data'));
var cacheChidMap_1 = {};
traverseTree(data, function (subTree) {
var siblings = subTree.children;
if (!(siblings === null || siblings === void 0 ? void 0 : siblings.length)) return true;
for (var i = siblings.length - 1; i >= 0; i--) {
var node = _this.findById(siblings[i].id);
var isHidden = node ? !node.isVisible() : siblings[i].visible === false;
if (isHidden) {
cacheChidMap_1[subTree.id] = cacheChidMap_1[subTree.id] || [];
cacheChidMap_1[subTree.id].push({
idx: i,
child: siblings.splice(i, 1)[0]
});
}
}
});
layoutData = layoutMethod ? layoutMethod(data, self.get('layout')) : data;
traverseTree(layoutData, function (subTree) {
var cachedItems = cacheChidMap_1[subTree.id];
if (cachedItems === null || cachedItems === void 0 ? void 0 : cachedItems.length) {
for (var i = cachedItems.length - 1; i >= 0; i--) {
var _a = cachedItems[i],
idx = _a.idx,
child = _a.child;
subTree.children.splice(idx, 0, child);
}
}
});
} else {
layoutData = layoutMethod ? layoutMethod(data, self.get('layout')) : data;
}
var animate = self.get('animate');
self.emit('beforerefreshlayout', {
data: data,
layoutData: layoutData
});
self.emit('beforelayout');
self.innerUpdateChild(layoutData, undefined, animate);
if (fitView) {
var viewController = self.get('viewController');
viewController.fitView();
}
if (!animate) {
// 如果没有动画,目前仅更新了节点的位置,刷新一下边的样式
self.refresh();
self.paint();
} else {
self.layoutAnimate(layoutData);
}
self.emit('afterrefreshlayout', {
data: data,
layoutData: layoutData
});
self.emit('afterlayout');
};
/**
* 添加子树到对应 id 的节点
* @param {TreeGraphData} data 子树数据模型
* @param {string} parent 子树的父节点id
*/
TreeGraph.prototype.addChild = function (data, parent, stack) {
if (stack === void 0) {
stack = true;
}
var self = this;
self.emit('beforeaddchild', {
model: data,
parent: parent
});
// 将数据添加到源数据中,走changeData方法
if (!(0, _util.isString)(parent)) {
parent = parent.get('id');
}
var parentData = self.findDataById(parent);
if (parentData) {
if (!parentData.children) {
parentData.children = [];
}
parentData.children.push(data);
var parentItem = self.findById(parent);
parentItem.refresh();
self.changeData(undefined, stack);
}
};
/**
* 更新某个节点下的所有子节点
* @param {TreeGraphData[]} data 子树数据模型集合
* @param {string} parent 子树的父节点id
*/
TreeGraph.prototype.updateChildren = function (data, parentId, stack) {
if (stack === void 0) {
stack = true;
}
var self = this;
// 如果没有父节点或找不到该节点,是全量的更新,直接重置data
var parentItem = self.findById(parentId);
if (!parentId || !parentItem) {
console.warn("Update children failed! There is no node with id '".concat(parentId, "'"));
return;
}
var parentModel = self.findDataById(parentId);
parentModel.children = data;
parentItem.refresh();
self.changeData(undefined, stack);
};
/**
* 更新源数据,差量更新子树
* @param {TreeGraphData} data 子树数据模型
* @param {string} parentId 子树的父节点id
*/
TreeGraph.prototype.updateChild = function (data, parentId, stack) {
if (stack === void 0) {
stack = true;
}
var self = this;
// 如果没有父节点或找不到该节点,是全量的更新,直接重置data
if (!parentId || !self.findById(parentId)) {
self.changeData(data, stack);
return;
}
var parentModel = self.findDataById(parentId);
var current = self.findById(data.id);
if (!parentModel.children) {
// 当 current 不存在时,children 为空数组
parentModel.children = [];
}
// 如果不存在该节点,则添加
if (!current) {
parentModel.children.push(data);
} else {
var index = TreeGraph.indexOfChild(parentModel.children, data.id);
if (index > -1) parentModel.children[index] = data;
}
var parentItem = self.findById(parentId);
parentItem === null || parentItem === void 0 ? void 0 : parentItem.refresh();
self.changeData(undefined, stack);
};
/**
* 删除子树
* @param {string} id 子树根节点id
*/
TreeGraph.prototype.removeChild = function (id, stack) {
if (stack === void 0) {
stack = true;
}
var self = this;
var node = self.findById(id);
var parent;
if (!node) {
parent = self.getNodes().find(function (node) {
var children = node.getModel().children || [];
return !!children.find(function (child) {
return child.id === id;
});
});
} else {
parent = node === null || node === void 0 ? void 0 : node.get('parent');
}
if (parent && !parent.destroyed) {
var parentId = parent.get('id');
var parentNode = self.findDataById(parentId);
var siblings = parentNode && parentNode.children || [];
var index = TreeGraph.indexOfChild(siblings, id);
siblings.splice(index, 1);
parent.refresh();
}
self.changeData(undefined, stack);
};
/**
* 根据id获取对应的源数据
* @param {string} id 元素id
* @param {TreeGraphData | undefined} parent 从哪个节点开始寻找,为空时从根节点开始查找
* @return {TreeGraphData} 对应源数据
*/
TreeGraph.prototype.findDataById = function (id, parent) {
var self = this;
if (!parent) {
parent = self.get('data');
}
if (id === parent.id) {
return parent;
}
var result = null;
// eslint-disable-next-line consistent-return
(0, _util.each)(parent.children || [], function (child) {
if (child.id === id) {
result = child;
return false;
}
result = self.findDataById(id, child);
if (result) {
return false;
}
});
return result;
};
/**
* 布局动画接口,用于数据更新时做节点位置更新的动画
* @param {TreeGraphData} data 更新的数据
* @param {function} onFrame 定义节点位置更新时如何移动
*/
TreeGraph.prototype.layoutAnimate = function (data, onFrame) {
var self = this;
var animateCfg = this.get('animateCfg');
self.emit('beforeanimate', {
data: data
});
// 如果边中没有指定锚点,但是本身有锚点控制,在动画过程中保持锚点不变
self.getEdges().forEach(function (edge) {
var model = edge.get('model');
if (!model.sourceAnchor) {
model.sourceAnchor = edge.get('sourceAnchorIndex');
}
});
this.get('canvas').animate(function (ratio) {
traverseTree(data, function (child) {
var node = self.findById(child.id);
// 只有当存在node的时候才执行
if (node) {
var origin_2 = node.get('originAttrs');
var model = node.get('model');
if (!origin_2) {
origin_2 = {
x: model.x,
y: model.y
};
node.set('originAttrs', origin_2);
}
if (onFrame) {
var attrs = onFrame(node, ratio, origin_2, data);
node.set('model', Object.assign(model, attrs));
} else {
model.x = origin_2.x + (child.x - origin_2.x) * ratio;
model.y = origin_2.y + (child.y - origin_2.y) * ratio;
}
}
return true;
});
(0, _util.each)(self.get('removeList'), function (node) {
var model = node.getModel();
var from = node.get('originAttrs');
var 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: animateCfg.duration,
easing: animateCfg.ease,
callback: function callback() {
(0, _util.each)(self.getNodes(), function (node) {
node.set('originAttrs', null);
});
(0, _util.each)(self.get('removeList'), function (node) {
self.removeItem(node, false);
});
self.set('removeList', []);
if (animateCfg.callback) {
animateCfg.callback();
}
self.emit('afteranimate', {
data: data
});
},
delay: animateCfg.delay
});
};
/**
* 立即停止布局动画
*/
TreeGraph.prototype.stopLayoutAnimate = function () {
this.get('canvas').stopAnimate();
this.emit('layoutanimateend', {
data: this.get('data')
});
this.layoutAnimating = false;
};
/**
* 是否在布局动画
* @return {boolean} 是否有布局动画
*/
TreeGraph.prototype.isLayoutAnimating = function () {
return this.layoutAnimating;
};
/**
* 根据data接口的数据渲染视图
*/
TreeGraph.prototype.render = function (clearStack) {
if (clearStack === void 0) {
clearStack = true;
}
var self = this;
var data = self.get('data');
if (!data || !(0, _util.isObject)(data) || !Object.keys(data).length) {
throw new Error('data must be defined first');
}
self.clear();
if (clearStack && this.get('enabledStack')) {
// render 之前清空 redo 和 undo 栈
this.clearStack();
}
self.emit('beforerender');
self.layout(this.get('fitView'));
self.emit('afterrender');
};
/**
* 导出图数据
* @return {object} data
*/
TreeGraph.prototype.save = function () {
return this.get('data');
};
/**
* 设置视图初始化数据
* @param {TreeGraphData} data 初始化数据
*/
TreeGraph.prototype.data = function (data) {
_super.prototype.data.call(this, data);
this.set('originData', (0, _deepClone.deepClone)(data));
};
return TreeGraph;
}(_graph.default);
var _default = exports.default = TreeGraph;