@antv/g6-pc
Version:
A Graph Visualization Framework in JavaScript
659 lines (658 loc) • 20.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _tslib = require("tslib");
var _util = require("@antv/util");
var _global = _interopRequireDefault(require("../global"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
var _default = exports.default = {
getDefaultCfg: function getDefaultCfg() {
return {
updateEdge: true,
delegateStyle: {},
// 是否开启delegate
enableDelegate: false,
// 拖动节点过程中是否只改变 Combo 的大小,而不改变其结构
onlyChangeComboSize: false,
// 拖动过程中目标 combo 状态样式
comboActiveState: '',
selectedState: 'selected',
enableOptimize: false,
enableDebounce: false,
enableStack: true
};
},
getEvents: function getEvents() {
return {
'node:mousedown': 'onMouseDown',
drag: 'onDragMove',
dragend: 'onDragEnd',
'combo:dragenter': 'onDragEnter',
'combo:dragleave': 'onDragLeave',
'combo:drop': 'onDropCombo',
'node:drop': 'onDropNode',
'canvas:drop': 'onDropCanvas',
touchstart: 'onTouchStart',
touchmove: 'onTouchMove',
touchend: 'onDragEnd',
afterchangedata: 'onDragEnd'
};
},
validationCombo: function validationCombo(item) {
if (!this.origin || !item || item.destroyed) {
return false;
}
var type = item.getType();
if (type !== 'combo') {
return false;
}
return true;
},
onTouchStart: function onTouchStart(evt) {
if (!evt.item) return;
var self = this;
try {
var touches = evt.originalEvent.touches;
var event1 = touches[0];
var event2 = touches[1];
if (event1 && event2) {
return;
}
evt.preventDefault();
} catch (e) {
console.warn('Touch original event not exist!');
}
this.mousedown = {
item: evt.item,
target: evt.target,
origin: {
x: evt.x,
y: evt.y
}
};
this.dragstart = true;
self.onDragStart(evt);
},
onTouchMove: function onTouchMove(e) {
var self = this;
try {
var touches = e.originalEvent.touches;
var event1 = touches[0];
var event2 = touches[1];
if (event1 && event2) {
self.onDragEnd(e);
return;
}
e.preventDefault();
} catch (e) {
console.warn('Touch original event not exist!');
}
self.onDrag(e);
},
/**
* cache the manipulated item and target, since drag and dragend are global events but not node:*
* @param evt event param
*/
onMouseDown: function onMouseDown(evt) {
this.mousedown = {
item: evt.item,
target: evt.target,
origin: {
x: evt.x,
y: evt.y
}
};
// 绑定浏览器监听,触发拖拽结束,结束拖拽时移除
if (typeof window !== 'undefined' && !this.windowEventBinded) {
this.windowEventBinded = true;
document.body.addEventListener('contextmenu', this.onDragEnd.bind(this));
document.body.addEventListener('mouseup', this.onDragEnd.bind(this));
}
},
/**
* trigger dragstart/drag by mousedown and drag events
* @param evt event param
*/
onDragMove: function onDragMove(evt) {
var _a, _b;
if (((_b = (_a = evt.item) === null || _a === void 0 ? void 0 : _a.getType) === null || _b === void 0 ? void 0 : _b.call(_a)) !== 'node') {
this.onDragEnd();
return;
}
if (!this.mousedown) return;
if (!this.dragstart) {
// dragstart
this.dragstart = true;
this.onDragStart(evt);
} else {
// drag
this.onDrag((0, _tslib.__assign)((0, _tslib.__assign)({}, evt), this.mousedown));
}
},
/**
* 开始拖动节点
* @param evt
*/
onDragStart: function onDragStart(evt) {
var _this = this;
this.currentShouldEnd = true;
if (!this.shouldBegin((0, _tslib.__assign)((0, _tslib.__assign)({}, evt), this.mousedown), this)) {
return;
}
var _a = this.mousedown,
item = _a.item,
target = _a.target;
if (!item || item.destroyed || item.hasLocked()) {
return;
}
// 拖动时,设置拖动元素的 capture 为false,则不拾取拖动的元素
var group = item.getContainer();
group.set('capture', false);
if (!this.cachedCaptureItems) this.cachedCaptureItems = [];
this.cachedCaptureItems.push(item);
// 如果拖动的target 是linkPoints / anchorPoints 则不允许拖动
if (target) {
var isAnchorPoint = target.get('isAnchorPoint');
if (isAnchorPoint) {
return;
}
}
var graph = this.graph;
this.targets = [];
// 将节点拖入到指定的 Combo
this.targetCombo = null;
// 获取所有选中的元素
var nodes = graph.findAllByState('node', this.selectedState);
var currentNodeId = item.get('id');
// 当前拖动的节点是否是选中的节点
var dragNodes = nodes.filter(function (node) {
var nodeId = node.get('id');
return currentNodeId === nodeId;
});
// 只拖动当前节点
if (dragNodes.length === 0) {
this.targets.push(item);
} else if (nodes.length > 1) {
// 拖动多个节点
nodes.forEach(function (node) {
var locked = node.hasLocked();
if (!locked) {
_this.targets.push(node);
}
});
} else {
this.targets.push(item);
}
if (this.graph.get('enabledStack') && this.enableStack) {
var beforeDragNodes_1 = [];
this.targets.forEach(function (t) {
var _a = t.getModel(),
x = _a.x,
y = _a.y,
id = _a.id;
beforeDragNodes_1.push({
x: x,
y: y,
id: id
});
});
this.set('beforeDragNodes', beforeDragNodes_1);
}
this.hidenEdge = {};
if (this.get('updateEdge') && this.enableOptimize && !this.enableDelegate) {
this.targets.forEach(function (node) {
var edges = node.getEdges();
edges.forEach(function (edge) {
if (!edge.isVisible()) return;
_this.hidenEdge[edge.getID()] = true;
edge.hide();
});
});
}
this.origin = this.mousedown.origin;
this.point = {};
this.originPoint = {};
},
/**
* 持续拖动节点
* @param evt
*/
onDrag: function onDrag(evt) {
var _this = this;
if (!this.mousedown || !this.origin) return;
if (!this.shouldUpdate(evt, this)) return;
if (this.get('enableDelegate')) {
this.updateDelegate(evt);
} else {
if (this.enableDebounce) {
this.debounceUpdate({
targets: this.targets,
graph: this.graph,
point: this.point,
origin: this.origin,
evt: evt,
updateEdge: this.get('updateEdge'),
onlyChangeComboSize: this.onlyChangeComboSize,
updateParentCombos: this.updateParentCombos
});
} else {
var parentComboMap_1 = {};
this.targets.map(function (target) {
_this.update(target, evt);
var parentComboId = target.getModel().comboId;
if (parentComboId) parentComboMap_1[parentComboId] = _this.graph.findById(parentComboId);
});
if (this.onlyChangeComboSize) {
// 拖动节点过程中,动态改变 Combo 的大小
this.updateParentCombos();
}
}
}
},
/**
* 拖动结束,设置拖动元素capture为true,更新元素位置,如果是拖动涉及到 combo,则更新 combo 结构
* @param evt
*/
onDragEnd: function onDragEnd(evt) {
var _this = this;
var _a;
this.mousedown = false;
this.dragstart = false;
// 移除浏览器监听
if (typeof window !== 'undefined' && this.windowEventBinded) {
this.windowEventBinded = false;
document.body.removeEventListener('contextmenu', this.onDragEnd.bind(this));
document.body.removeEventListener('mouseup', this.onDragEnd.bind(this));
}
if (!this.origin) {
return;
}
// 拖动结束后,设置拖动元素 group 的 capture 为 true,允许拾取拖动元素
(_a = this.cachedCaptureItems) === null || _a === void 0 ? void 0 : _a.forEach(function (item) {
var group = item.getContainer();
group.set('capture', true);
});
this.cachedCaptureItems = [];
if (this.delegateRect) {
this.delegateRect.remove();
this.delegateRect = null;
}
if (this.get('updateEdge') && this.enableOptimize && !this.enableDelegate) {
this.targets.forEach(function (node) {
var edges = node.getEdges();
edges.forEach(function (edge) {
if (_this.hidenEdge[edge.getID()]) edge.show();
edge.refresh();
});
});
}
this.hidenEdge = {};
var graph = this.graph;
// 拖动结束后,入栈
if (graph.get('enabledStack') && this.enableStack) {
var stackData_1 = {
before: {
nodes: [],
edges: [],
combos: []
},
after: {
nodes: [],
edges: [],
combos: []
}
};
this.get('beforeDragNodes').forEach(function (model) {
stackData_1.before.nodes.push(model);
});
this.targets.forEach(function (target) {
var _a = target.getModel(),
x = _a.x,
y = _a.y,
id = _a.id;
stackData_1.after.nodes.push({
x: x,
y: y,
id: id
});
});
graph.pushStack('update', (0, _util.clone)(stackData_1));
}
// 拖动结束后emit事件,将当前操作的节点抛出去,目标节点为null
graph.emit('dragnodeend', {
items: this.targets,
targetItem: null
});
this.point = {};
this.origin = null;
this.originPoint = {};
this.targets.length = 0;
this.targetCombo = null;
},
/**
* 拖动过程中将节点放置到 combo 上
* @param evt
*/
onDropCombo: function onDropCombo(evt) {
var item = evt.item;
this.currentShouldEnd = this.shouldEnd(evt, item, this);
// 若不允许结束,则将节点位置设置回初识位置。后面的逻辑仍需要执行
this.updatePositions(evt, !this.currentShouldEnd);
if (!this.currentShouldEnd || !this.validationCombo(item)) return;
var graph = this.graph;
if (this.comboActiveState) {
graph.setItemState(item, this.comboActiveState, false);
}
this.targetCombo = item;
// 拖动结束后是动态改变 Combo 大小还是将节点从 Combo 中删除
if (this.onlyChangeComboSize) {
// 拖动节点结束后,动态改变 Combo 的大小
graph.updateCombos();
} else {
var targetComboModel_1 = item.getModel();
this.targets.map(function (node) {
var nodeModel = node.getModel();
if (nodeModel.comboId !== targetComboModel_1.id) {
graph.updateComboTree(node, targetComboModel_1.id);
}
});
graph.updateCombo(item);
}
// 将节点拖动到 combo 上面,emit事件抛出当前操作的节点及目标 combo
graph.emit('dragnodeend', {
items: this.targets,
targetItem: this.targetCombo
});
},
onDropCanvas: function onDropCanvas(evt) {
var graph = this.graph;
this.currentShouldEnd = this.shouldEnd(evt, undefined, this);
// 若不允许结束,则将节点位置设置回初识位置。后面的逻辑仍需要执行
this.updatePositions(evt, !this.currentShouldEnd);
if (!this.targets || this.targets.length === 0 || !this.currentShouldEnd) return;
if (this.onlyChangeComboSize) {
this.updateParentCombos();
} else {
this.targets.map(function (node) {
// 拖动的节点有 comboId,即是从其他 combo 中拖出时才处理
var model = node.getModel();
if (model.comboId) {
graph.updateComboTree(node);
}
});
}
},
/**
* 拖动放置到某个 combo 中的子 node 上
* @param evt
*/
onDropNode: function onDropNode(evt) {
if (!this.targets || this.targets.length === 0) return;
var self = this;
var item = evt.item;
var graph = self.graph;
var comboId = item.getModel().comboId;
var newParentCombo = comboId ? graph.findById(comboId) : undefined;
this.currentShouldEnd = this.shouldEnd(evt, newParentCombo, this);
// 若不允许结束,则将节点位置设置回初识位置。后面的逻辑仍需要执行
this.updatePositions(evt, !this.currentShouldEnd);
if (!this.currentShouldEnd) return;
if (this.onlyChangeComboSize) {
this.updateParentCombos();
} else if (comboId) {
var combo = graph.findById(comboId);
if (self.comboActiveState) {
graph.setItemState(combo, self.comboActiveState, false);
}
this.targets.map(function (node) {
var nodeModel = node.getModel();
if (comboId !== nodeModel.comboId) {
graph.updateComboTree(node, comboId);
}
});
graph.updateCombo(combo);
} else {
this.targets.map(function (node) {
var model = node.getModel();
if (model.comboId) {
graph.updateComboTree(node);
}
});
}
// 将节点拖动到另外个节点上面,emit 事件抛出当前操作的节点及目标节点
graph.emit('dragnodeend', {
items: this.targets,
targetItem: item
});
},
/**
* 将节点拖入到 Combo 中
* @param evt
*/
onDragEnter: function onDragEnter(evt) {
var item = evt.item;
if (!this.validationCombo(item)) return;
var graph = this.graph;
if (this.comboActiveState) {
graph.setItemState(item, this.comboActiveState, true);
}
},
/**
* 将节点从 Combo 中拖出
* @param evt
*/
onDragLeave: function onDragLeave(evt) {
var item = evt.item;
if (!this.validationCombo(item)) return;
var graph = this.graph;
if (this.comboActiveState) {
graph.setItemState(item, this.comboActiveState, false);
}
},
updatePositions: function updatePositions(evt, restore) {
var _this = this;
if (!this.targets || this.targets.length === 0) return;
// 当开启 delegate 时,拖动结束后需要更新所有已选中节点的位置
if (this.get('enableDelegate')) {
if (this.enableDebounce) this.debounceUpdate({
targets: this.targets,
graph: this.graph,
point: this.point,
origin: this.origin,
evt: evt,
updateEdge: this.get('updateEdge'),
onlyChangeComboSize: this.onlyChangeComboSize,
updateParentCombos: this.updateParentCombos
});else if (!restore) this.targets.map(function (node) {
return _this.update(node, evt);
});
} else this.targets.map(function (node) {
return _this.update(node, evt, restore);
});
},
/**
* 更新节点
* @param item 拖动的节点实例
* @param evt
*/
update: function update(item, evt, restore) {
var origin = this.origin;
var model = item.get('model');
var nodeId = item.get('id');
if (!this.point[nodeId]) {
this.point[nodeId] = {
x: model.x || 0,
y: model.y || 0
};
}
var x = evt.x - origin.x + this.point[nodeId].x;
var y = evt.y - origin.y + this.point[nodeId].y;
if (restore) {
x += origin.x - evt.x;
y += origin.y - evt.y;
}
var pos = {
x: x,
y: y
};
if (this.get('updateEdge')) {
this.graph.updateItem(item, pos, false);
} else {
item.updatePosition(pos);
}
},
/**
* 限流更新节点
* @param item 拖动的节点实例
* @param evt
*/
debounceUpdate: (0, _util.debounce)(function (event) {
var targets = event.targets,
graph = event.graph,
point = event.point,
origin = event.origin,
evt = event.evt,
updateEdge = event.updateEdge,
onlyChangeComboSize = event.onlyChangeComboSize,
updateParentCombos = event.updateParentCombos;
targets.map(function (item) {
var model = item.get('model');
var nodeId = item.get('id');
if (!point[nodeId]) {
point[nodeId] = {
x: model.x || 0,
y: model.y || 0
};
}
var x = evt.x - origin.x + point[nodeId].x;
var y = evt.y - origin.y + point[nodeId].y;
var pos = {
x: x,
y: y
};
if (updateEdge) {
graph.updateItem(item, pos, false);
} else {
item.updatePosition(pos);
}
});
if (onlyChangeComboSize) {
updateParentCombos(graph, targets);
}
}, 50, true),
/**
* 更新拖动元素时的delegate
* @param {Event} evt 事件句柄
* @param {number} x 拖动单个元素时候的x坐标
* @param {number} y 拖动单个元素时候的y坐标
*/
updateDelegate: function updateDelegate(evt) {
var graph = this.graph;
if (!this.delegateRect) {
// 拖动多个
var parent_1 = graph.get('group');
var attrs = (0, _util.deepMix)({}, _global.default.delegateStyle, this.delegateStyle);
var _a = this.calculationGroupPosition(evt),
cx = _a.x,
cy = _a.y,
width = _a.width,
height = _a.height,
minX = _a.minX,
minY = _a.minY;
this.originPoint = {
x: cx,
y: cy,
width: width,
height: height,
minX: minX,
minY: minY
};
// model上的x, y是相对于图形中心的,delegateShape是g实例,x,y是绝对坐标
this.delegateRect = parent_1.addShape('rect', {
attrs: (0, _tslib.__assign)({
width: width,
height: height,
x: cx,
y: cy
}, attrs),
name: 'rect-delegate-shape'
});
this.delegate = this.delegateRect;
this.delegateRect.set('capture', false);
} else {
var clientX = evt.x - this.origin.x + this.originPoint.minX;
var clientY = evt.y - this.origin.y + this.originPoint.minY;
this.delegateRect.attr({
x: clientX,
y: clientY
});
}
},
/**
* 计算delegate位置,包括左上角左边及宽度和高度
* @memberof ItemGroup
* @return {object} 计算出来的delegate坐标信息及宽高
*/
calculationGroupPosition: function calculationGroupPosition(evt) {
var nodes = this.targets;
if (nodes.length === 0) {
nodes.push(evt.item);
}
var minx = Infinity;
var maxx = -Infinity;
var miny = Infinity;
var maxy = -Infinity;
// 获取已节点的所有最大最小x y值
for (var i = 0; i < nodes.length; i++) {
var element = nodes[i];
var bbox = element.getBBox();
var minX = bbox.minX,
minY = bbox.minY,
maxX = bbox.maxX,
maxY = bbox.maxY;
if (minX < minx) {
minx = minX;
}
if (minY < miny) {
miny = minY;
}
if (maxX > maxx) {
maxx = maxX;
}
if (maxY > maxy) {
maxy = maxY;
}
}
var x = Math.floor(minx);
var y = Math.floor(miny);
var width = Math.ceil(maxx) - Math.floor(minx);
var height = Math.ceil(maxy) - Math.floor(miny);
return {
x: x,
y: y,
width: width,
height: height,
minX: minx,
minY: miny
};
},
/**
* updates the parent combos' size and position
* @param paramGraph param for debounce function, where 'this' is not available
* @param paramTargets param for debounce function, where 'this' is not available
*/
updateParentCombos: function updateParentCombos(paramGraph, paramTargets) {
var graph = paramGraph || this.graph;
var targets = paramTargets || this.targets;
var comboParentMap = {};
targets === null || targets === void 0 ? void 0 : targets.forEach(function (target) {
var comboId = target.getModel().comboId;
if (comboId) comboParentMap[comboId] = graph.findById(comboId);
});
Object.values(comboParentMap).forEach(function (combo) {
if (combo) graph.updateCombo(combo);
});
}
};