mirror of
https://gitee.com/antv/g6.git
synced 2024-12-05 05:09:07 +08:00
feat: add rect group
This commit is contained in:
parent
2239303b4f
commit
3af1b32d0d
@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>拖动群组</title>
|
<title>Circle节点分组</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="mountNode"></div>
|
<div id="mountNode"></div>
|
||||||
@ -46,101 +46,7 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
nodes: [
|
|
||||||
{
|
|
||||||
id: 'node6',
|
|
||||||
groupId: 'group3',
|
|
||||||
label: 'rect',
|
|
||||||
x: 100,
|
|
||||||
y: 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'node1',
|
|
||||||
label: 'fck',
|
|
||||||
groupId: 'group1',
|
|
||||||
x: 100,
|
|
||||||
y: 100
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'node9',
|
|
||||||
label: 'noGroup1',
|
|
||||||
groupId: 'p1',
|
|
||||||
x: 300,
|
|
||||||
y: 210
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'node2',
|
|
||||||
label: 'node2',
|
|
||||||
groupId: 'group1',
|
|
||||||
x: 150,
|
|
||||||
y: 200
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'node3',
|
|
||||||
label: 'node3',
|
|
||||||
groupId: 'group2',
|
|
||||||
x: 300,
|
|
||||||
y: 100
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'node7',
|
|
||||||
groupId: 'p1',
|
|
||||||
label: 'node7-p1',
|
|
||||||
x: 200,
|
|
||||||
y: 200
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'node10',
|
|
||||||
label: 'noGroup',
|
|
||||||
groupId: 'p2',
|
|
||||||
x: 300,
|
|
||||||
y: 210
|
|
||||||
}
|
|
||||||
],
|
|
||||||
edges: [
|
|
||||||
{
|
|
||||||
source: 'node1',
|
|
||||||
target: 'node2'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
source: 'node2',
|
|
||||||
target: 'node3'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
source: 'node1',
|
|
||||||
target: 'node3'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
groups: [
|
|
||||||
{
|
|
||||||
id: 'group1',
|
|
||||||
title: '1',
|
|
||||||
label: 'group1',
|
|
||||||
parentId: 'p1'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'group2',
|
|
||||||
title: '2',
|
|
||||||
parentId: 'p1'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'group3',
|
|
||||||
title: '2',
|
|
||||||
parentId: 'p2'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'p1',
|
|
||||||
title: '3'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'p2',
|
|
||||||
title: '3'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
const data1 = {
|
|
||||||
nodes: [
|
nodes: [
|
||||||
{
|
{
|
||||||
id: 'node6',
|
id: 'node6',
|
||||||
@ -201,6 +107,14 @@ title: '3'
|
|||||||
{
|
{
|
||||||
source: 'node2',
|
source: 'node2',
|
||||||
target: 'node3'
|
target: 'node3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: 'node1',
|
||||||
|
target: 'node3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: 'node6',
|
||||||
|
target: 'node1'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
groups: [
|
groups: [
|
134
demos/rect-group.html
Normal file
134
demos/rect-group.html
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Rect节点分组</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="mountNode"></div>
|
||||||
|
<script src="../build/g6.js"></script>
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* 该案例演示以下功能:
|
||||||
|
* 1、渲染群组所需要的数据结构;
|
||||||
|
* 2、如何拖动一个群组;
|
||||||
|
* 3、将节点从群组中拖出;
|
||||||
|
* 4、将节点拖入到某个群组中;
|
||||||
|
* 5、拖出拖入节点后动态改变群组大小。
|
||||||
|
*/
|
||||||
|
G6.registerNode('rectNode', {
|
||||||
|
draw(cfg, group) {
|
||||||
|
const keyShape = group.addShape('rect', {
|
||||||
|
attrs: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 80,
|
||||||
|
height: 50,
|
||||||
|
fill: '#87e8de'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const text = group.addShape('text', {
|
||||||
|
attrs: {
|
||||||
|
x: 35,
|
||||||
|
y: 25,
|
||||||
|
textAlign: 'center',
|
||||||
|
fill: 'blue',
|
||||||
|
text: cfg.label
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return keyShape;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const graph = new G6.Graph({
|
||||||
|
container: 'mountNode',
|
||||||
|
width: 800,
|
||||||
|
height: 600,
|
||||||
|
defaultNode: {
|
||||||
|
shape: 'rectNode',
|
||||||
|
labelCfg: {
|
||||||
|
position: 'center'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultEdge: {
|
||||||
|
color: '#bae7ff',
|
||||||
|
style: {
|
||||||
|
endArrow: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modes: {
|
||||||
|
default: [ 'drag-group', 'drag-node-with-group', 'collapse-expand-group' ]
|
||||||
|
},
|
||||||
|
groupType: 'rect'
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
id: 'node6',
|
||||||
|
groupId: 'group3',
|
||||||
|
label: 'node6-group3',
|
||||||
|
x: 100,
|
||||||
|
y: 300,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'node1',
|
||||||
|
label: 'node1-group1',
|
||||||
|
groupId: 'group1',
|
||||||
|
x: 100,
|
||||||
|
y: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'node2',
|
||||||
|
label: 'node2-group2',
|
||||||
|
groupId: 'p2',
|
||||||
|
x: 150,
|
||||||
|
y: 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'node3',
|
||||||
|
label: 'node3-group2',
|
||||||
|
groupId: 'group2',
|
||||||
|
x: 300,
|
||||||
|
y: 100
|
||||||
|
},
|
||||||
|
],
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
source: 'node1',
|
||||||
|
target: 'node2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: 'node2',
|
||||||
|
target: 'node3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: 'node1',
|
||||||
|
target: 'node3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: 'node6',
|
||||||
|
target: 'node1'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
id: 'group3',
|
||||||
|
title: '2',
|
||||||
|
parentId: 'p2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'p2',
|
||||||
|
title: '3'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
graph.data(data)
|
||||||
|
graph.render()
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -6,6 +6,7 @@
|
|||||||
* @Description: 拖动群组
|
* @Description: 拖动群组
|
||||||
*/
|
*/
|
||||||
const deepMix = require('@antv/util/lib/deep-mix');
|
const deepMix = require('@antv/util/lib/deep-mix');
|
||||||
|
const body = document.body;
|
||||||
|
|
||||||
const delegateStyle = {
|
const delegateStyle = {
|
||||||
fill: '#F3F9FF',
|
fill: '#F3F9FF',
|
||||||
@ -28,7 +29,8 @@ module.exports = {
|
|||||||
return {
|
return {
|
||||||
dragstart: 'onDragStart',
|
dragstart: 'onDragStart',
|
||||||
drag: 'onDrag',
|
drag: 'onDrag',
|
||||||
dragend: 'onDragEnd'
|
dragend: 'onDragEnd',
|
||||||
|
'canvas:mouseleave': 'onOutOfRange'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
onDragStart(evt) {
|
onDragStart(evt) {
|
||||||
@ -123,6 +125,7 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const graph = this.graph;
|
const graph = this.graph;
|
||||||
|
const groupType = graph.get('groupType');
|
||||||
|
|
||||||
// 更新群组里面节点和线的位置
|
// 更新群组里面节点和线的位置
|
||||||
this.updateItemPosition(evt);
|
this.updateItemPosition(evt);
|
||||||
@ -169,19 +172,25 @@ module.exports = {
|
|||||||
groupNodes[parentGroupId] = parentGroupNodes.filter(node => currentGroupNodes.indexOf(node) === -1);
|
groupNodes[parentGroupId] = parentGroupNodes.filter(node => currentGroupNodes.indexOf(node) === -1);
|
||||||
|
|
||||||
const { x: x1, y: y1, width, height } = customGroupControll.calculationGroupPosition(groupNodes[parentGroupId]);
|
const { x: x1, y: y1, width, height } = customGroupControll.calculationGroupPosition(groupNodes[parentGroupId]);
|
||||||
const groups = graph.get('groups');
|
const paddingValue = customGroupControll.getGroupPadding(parentGroupId);
|
||||||
const hasSubGroup = !!groups.filter(g => g.parentId === parentGroupId).length > 0;
|
|
||||||
const r = width > height ? width / 2 : height / 2 + (hasSubGroup ? 20 : 0);
|
|
||||||
|
|
||||||
|
if (groupType === 'circle') {
|
||||||
|
const r = width > height ? width / 2 : height / 2;
|
||||||
const cx = (width + 2 * x1) / 2;
|
const cx = (width + 2 * x1) / 2;
|
||||||
const cy = (height + 2 * y1) / 2;
|
const cy = (height + 2 * y1) / 2;
|
||||||
// groupKeyShape.attr('x', cx);
|
|
||||||
// groupKeyShape.attr('y', cy);
|
|
||||||
parentKeyShape.attr({
|
parentKeyShape.attr({
|
||||||
r: r + groupNodes[groupId].length * 10,
|
r: r + groupNodes[parentGroupId].length * 10 + paddingValue,
|
||||||
x: cx,
|
x: cx,
|
||||||
y: cy
|
y: cy
|
||||||
});
|
});
|
||||||
|
} else if (groupType === 'rect') {
|
||||||
|
parentKeyShape.attr({
|
||||||
|
x: x1 - paddingValue,
|
||||||
|
y: y1 - paddingValue,
|
||||||
|
width: width + paddingValue + groupNodes[parentGroupId].length * 10,
|
||||||
|
height: height + paddingValue + groupNodes[parentGroupId].length * 10
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -195,6 +204,7 @@ module.exports = {
|
|||||||
const groupId = evt.target.get('groupId');
|
const groupId = evt.target.get('groupId');
|
||||||
|
|
||||||
const graph = this.graph;
|
const graph = this.graph;
|
||||||
|
const groupType = graph.get('groupType');
|
||||||
|
|
||||||
// 获取群组对象
|
// 获取群组对象
|
||||||
const customGroupControll = graph.get('customGroupControll');
|
const customGroupControll = graph.get('customGroupControll');
|
||||||
@ -204,14 +214,15 @@ module.exports = {
|
|||||||
// step 1:先修改groupId中的节点位置
|
// step 1:先修改groupId中的节点位置
|
||||||
const nodeInGroup = groupNodes[groupId];
|
const nodeInGroup = groupNodes[groupId];
|
||||||
const groupOriginBBox = customGroupControll.getGroupOriginBBox(groupId);
|
const groupOriginBBox = customGroupControll.getGroupOriginBBox(groupId);
|
||||||
|
|
||||||
const delegateShapeBBoxs = this.delegateShapeBBoxs[groupId];
|
const delegateShapeBBoxs = this.delegateShapeBBoxs[groupId];
|
||||||
const otherGroupId = [];
|
const otherGroupId = [];
|
||||||
nodeInGroup.forEach((nodeId, index) => {
|
nodeInGroup.forEach((nodeId, index) => {
|
||||||
|
|
||||||
const node = graph.findById(nodeId);
|
const node = graph.findById(nodeId);
|
||||||
const model = node.getModel();
|
const model = node.getModel();
|
||||||
if (model.groupId && !otherGroupId.includes(model.groupId)) {
|
const nodeGroupId = model.groupId;
|
||||||
otherGroupId.push(model.groupId);
|
if (nodeGroupId && !otherGroupId.includes(nodeGroupId)) {
|
||||||
|
otherGroupId.push(nodeGroupId);
|
||||||
}
|
}
|
||||||
if (!this.nodePoint[index]) {
|
if (!this.nodePoint[index]) {
|
||||||
this.nodePoint[index] = {
|
this.nodePoint[index] = {
|
||||||
@ -246,10 +257,21 @@ module.exports = {
|
|||||||
const groupKeyShape = nodeGroup.get('keyShape');
|
const groupKeyShape = nodeGroup.get('keyShape');
|
||||||
|
|
||||||
const { x, y, width, height } = customGroupControll.calculationGroupPosition(groupNodes[id]);
|
const { x, y, width, height } = customGroupControll.calculationGroupPosition(groupNodes[id]);
|
||||||
|
|
||||||
|
if (groupType === 'circle') {
|
||||||
const cx = (width + 2 * x) / 2;
|
const cx = (width + 2 * x) / 2;
|
||||||
const cy = (height + 2 * y) / 2;
|
const cy = (height + 2 * y) / 2;
|
||||||
groupKeyShape.attr('x', cx);
|
groupKeyShape.attr({
|
||||||
groupKeyShape.attr('y', cy);
|
x: cx,
|
||||||
|
y: cy
|
||||||
|
});
|
||||||
|
} else if (groupType === 'rect') {
|
||||||
|
const paddingValue = customGroupControll.getGroupPadding(id);
|
||||||
|
groupKeyShape.attr({
|
||||||
|
x: x - paddingValue,
|
||||||
|
y: y - paddingValue
|
||||||
|
});
|
||||||
|
}
|
||||||
customGroupControll.setGroupOriginBBox(id, groupKeyShape.getBBox());
|
customGroupControll.setGroupOriginBBox(id, groupKeyShape.getBBox());
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -309,13 +331,30 @@ module.exports = {
|
|||||||
const x = deltaX + shapeOrigin.x;
|
const x = deltaX + shapeOrigin.x;
|
||||||
const y = deltaY + shapeOrigin.y;
|
const y = deltaY + shapeOrigin.y;
|
||||||
|
|
||||||
|
if (delegateType === 'circle') {
|
||||||
// 将Canvas坐标转成视口坐标
|
// 将Canvas坐标转成视口坐标
|
||||||
const point = graph.getPointByCanvas(x, y);
|
const point = graph.getPointByCanvas(x, y);
|
||||||
delegateShape.attr({ x: point.x, y: point.y });
|
delegateShape.attr({ x: point.x, y: point.y });
|
||||||
|
} else {
|
||||||
|
delegateShape.attr({ x, y });
|
||||||
|
}
|
||||||
self.delegateShapeBBoxs[groupId] = delegateShape.getBBox();
|
self.delegateShapeBBoxs[groupId] = delegateShape.getBBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
graph.paint();
|
graph.paint();
|
||||||
graph.setAutoPaint(autoPaint);
|
graph.setAutoPaint(autoPaint);
|
||||||
|
},
|
||||||
|
onOutOfRange(e) {
|
||||||
|
const self = this;
|
||||||
|
if (this.origin) {
|
||||||
|
const canvasElement = self.graph.get('canvas').get('el');
|
||||||
|
const fn = ev => {
|
||||||
|
if (ev.target !== canvasElement) {
|
||||||
|
self.onDragEnd(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.fn = fn;
|
||||||
|
body.addEventListener('mouseup', fn, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -33,7 +33,7 @@ module.exports = {
|
|||||||
const { target } = evt;
|
const { target } = evt;
|
||||||
const groupId = target.get('groupId');
|
const groupId = target.get('groupId');
|
||||||
const type = target.get('type');
|
const type = target.get('type');
|
||||||
if (type !== 'circle') {
|
if (type !== 'circle' || type !== 'rect') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (groupId && this.origin) {
|
if (groupId && this.origin) {
|
||||||
@ -209,12 +209,14 @@ module.exports = {
|
|||||||
const { groupId } = model;
|
const { groupId } = model;
|
||||||
|
|
||||||
const graph = this.graph;
|
const graph = this.graph;
|
||||||
|
const groupType = graph.get('groupType');
|
||||||
const customGroupControll = graph.get('customGroupControll');
|
const customGroupControll = graph.get('customGroupControll');
|
||||||
const groupNodes = graph.get('groupNodes');
|
const groupNodes = graph.get('groupNodes');
|
||||||
|
const nodes = groupNodes[groupId];
|
||||||
|
|
||||||
// 拖出节点后,根据最新的节点数量,重新计算群组大小
|
// 拖出节点后,根据最新的节点数量,重新计算群组大小
|
||||||
// 如果只有一个节点,拖出后,则删除该组
|
// 如果只有一个节点,拖出后,则删除该组
|
||||||
if (groupNodes[groupId].length === 0) {
|
if (nodes.length === 0) {
|
||||||
// step 1: 从groupNodes中删除
|
// step 1: 从groupNodes中删除
|
||||||
delete groupNodes[groupId];
|
delete groupNodes[groupId];
|
||||||
|
|
||||||
@ -225,19 +227,27 @@ module.exports = {
|
|||||||
// step 3: 删除原来的群组
|
// step 3: 删除原来的群组
|
||||||
currentGroup.remove();
|
currentGroup.remove();
|
||||||
} else {
|
} else {
|
||||||
const { x, y, width, height } = customGroupControll.calculationGroupPosition(groupNodes[groupId]);
|
const { x, y, width, height } = customGroupControll.calculationGroupPosition(nodes);
|
||||||
// 检测操作的群组中是否包括子群组
|
// 检测操作的群组中是否包括子群组
|
||||||
const groups = graph.get('groups');
|
const paddingValue = customGroupControll.getGroupPadding(groupId);
|
||||||
const hasSubGroup = !!groups.filter(g => g.parentId === groupId).length > 0;
|
|
||||||
const addR = hasSubGroup ? 20 : 10;
|
if (groupType === 'circle') {
|
||||||
const r = width > height ? width / 2 : height / 2;
|
const r = width > height ? width / 2 : height / 2;
|
||||||
const cx = (width + 2 * x) / 2;
|
const cx = (width + 2 * x) / 2;
|
||||||
const cy = (height + 2 * y) / 2;
|
const cy = (height + 2 * y) / 2;
|
||||||
keyShape.attr({
|
keyShape.attr({
|
||||||
r: r + groupNodes[groupId].length * 10 + addR,
|
r: r + nodes.length * 10 + paddingValue,
|
||||||
x: cx,
|
x: cx,
|
||||||
y: cy
|
y: cy
|
||||||
});
|
});
|
||||||
|
} else if (groupType === 'rect') {
|
||||||
|
keyShape.attr({
|
||||||
|
x: x - paddingValue,
|
||||||
|
y: y - paddingValue,
|
||||||
|
width: width + nodes.length * 10 + paddingValue,
|
||||||
|
height: height + nodes.length * 10 + paddingValue
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
customGroupControll.setGroupOriginBBox(groupId, keyShape.getBBox());
|
customGroupControll.setGroupOriginBBox(groupId, keyShape.getBBox());
|
||||||
}
|
}
|
||||||
@ -379,19 +389,20 @@ module.exports = {
|
|||||||
_updateDelegate(e, x, y) {
|
_updateDelegate(e, x, y) {
|
||||||
const { item } = e;
|
const { item } = e;
|
||||||
const graph = this.graph;
|
const graph = this.graph;
|
||||||
|
const groupType = graph.get('groupType');
|
||||||
const bbox = item.get('keyShape').getBBox();
|
const bbox = item.get('keyShape').getBBox();
|
||||||
if (!this.shape) {
|
if (!this.shape) {
|
||||||
// 拖动多个
|
|
||||||
const parent = graph.get('group');
|
const parent = graph.get('group');
|
||||||
const attrs = deepMix({}, delegateStyle, this.delegateStyle);
|
const attrs = deepMix({}, delegateStyle, this.delegateStyle);
|
||||||
|
// 拖动多个
|
||||||
if (this.targets.length > 0) {
|
if (this.targets.length > 0) {
|
||||||
const nodes = graph.findAllByState('node', 'selected');
|
const nodes = graph.findAllByState('node', 'selected');
|
||||||
if (nodes.length === 0) {
|
if (nodes.length === 0) {
|
||||||
nodes.push(item);
|
nodes.push(item);
|
||||||
}
|
}
|
||||||
const customGroupControll = graph.get('customGroupControll');
|
const customGroupControll = graph.get('customGroupControll');
|
||||||
const { x, y, width, height, minX, minY } = customGroupControll.calculationGroupPosition(nodes);
|
const { x, y, width, height } = customGroupControll.calculationGroupPosition(nodes);
|
||||||
this.originPoint = { x, y, width, height, minX, minY };
|
this.originPoint = { x, y, width, height };
|
||||||
// model上的x, y是相对于图形中心的,delegateShape是g实例,x,y是绝对坐标
|
// model上的x, y是相对于图形中心的,delegateShape是g实例,x,y是绝对坐标
|
||||||
this.shape = parent.addShape('rect', {
|
this.shape = parent.addShape('rect', {
|
||||||
attrs: {
|
attrs: {
|
||||||
@ -425,10 +436,17 @@ module.exports = {
|
|||||||
y: clientY
|
y: clientY
|
||||||
});
|
});
|
||||||
} else if (this.target) {
|
} else if (this.target) {
|
||||||
|
if (groupType === 'circle') {
|
||||||
this.shape.attr({
|
this.shape.attr({
|
||||||
x: x - bbox.width / 2,
|
x: x - bbox.width / 2,
|
||||||
y: y - bbox.height / 2
|
y: y - bbox.height / 2
|
||||||
});
|
});
|
||||||
|
} else if (groupType === 'rect') {
|
||||||
|
this.shape.attr({
|
||||||
|
x,
|
||||||
|
y
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.graph.paint();
|
this.graph.paint();
|
||||||
}
|
}
|
||||||
|
@ -228,40 +228,45 @@ module.exports = {
|
|||||||
const graph = this.graph;
|
const graph = this.graph;
|
||||||
|
|
||||||
const nodes = graph.findAllByState('node', 'selected');
|
const nodes = graph.findAllByState('node', 'selected');
|
||||||
const minx = [];
|
|
||||||
const maxx = [];
|
let minx = Infinity;
|
||||||
const miny = [];
|
let maxx = -Infinity;
|
||||||
const maxy = [];
|
let miny = Infinity;
|
||||||
|
let maxy = -Infinity;
|
||||||
|
|
||||||
// 获取已节点的所有最大最小x y值
|
// 获取已节点的所有最大最小x y值
|
||||||
for (const id of nodes) {
|
for (const id of nodes) {
|
||||||
const element = isString(id) ? graph.findById(id) : id;
|
const element = isString(id) ? graph.findById(id) : id;
|
||||||
const bbox = element.getBBox();
|
const bbox = element.getBBox();
|
||||||
const { minX, minY, maxX, maxY } = bbox;
|
const { minX, minY, maxX, maxY } = bbox;
|
||||||
minx.push(minX);
|
if (minX < minx) {
|
||||||
miny.push(minY);
|
minx = minX;
|
||||||
maxx.push(maxX);
|
|
||||||
maxy.push(maxY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从上一步获取的数组中,筛选出最小和最大值
|
if (minY < miny) {
|
||||||
const minX = Math.floor(Math.min(...minx));
|
miny = minY;
|
||||||
const maxX = Math.floor(Math.max(...maxx));
|
}
|
||||||
const minY = Math.floor(Math.min(...miny));
|
|
||||||
const maxY = Math.floor(Math.max(...maxy));
|
|
||||||
|
|
||||||
const x = minX - 20;
|
if (maxX > maxx) {
|
||||||
const y = minY + 10;
|
maxx = maxX;
|
||||||
const width = maxX - minX;
|
}
|
||||||
const height = maxY - minY;
|
|
||||||
|
if (maxY > maxy) {
|
||||||
|
maxy = maxY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const x = Math.floor(minx) - 20;
|
||||||
|
const y = Math.floor(miny) + 10;
|
||||||
|
const width = Math.ceil(maxx) - x;
|
||||||
|
const height = Math.ceil(maxy) - y;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
minX,
|
minX: minx,
|
||||||
minY
|
minY: miny
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -18,7 +18,9 @@ class CustomGroup {
|
|||||||
strokeOpacity: 0.9,
|
strokeOpacity: 0.9,
|
||||||
fill: '#F3F9FF',
|
fill: '#F3F9FF',
|
||||||
fillOpacity: 0.8,
|
fillOpacity: 0.8,
|
||||||
opacity: 0.8
|
opacity: 0.8,
|
||||||
|
minDis: 20,
|
||||||
|
maxDis: 40
|
||||||
},
|
},
|
||||||
hover: {
|
hover: {
|
||||||
stroke: '#faad14',
|
stroke: '#faad14',
|
||||||
@ -30,6 +32,8 @@ class CustomGroup {
|
|||||||
// 收起状态样式
|
// 收起状态样式
|
||||||
collapseStyle: {
|
collapseStyle: {
|
||||||
r: 30,
|
r: 30,
|
||||||
|
width: 80,
|
||||||
|
height: 40,
|
||||||
// lineDash: [ 5, 5 ],
|
// lineDash: [ 5, 5 ],
|
||||||
stroke: '#A3B1BF',
|
stroke: '#A3B1BF',
|
||||||
lineWidth: 3,
|
lineWidth: 3,
|
||||||
@ -92,6 +96,7 @@ class CustomGroup {
|
|||||||
|
|
||||||
// 计算群组左上角左边、宽度、高度及x轴方向上的最大值
|
// 计算群组左上角左边、宽度、高度及x轴方向上的最大值
|
||||||
const { x, y, width, height, maxX } = this.calculationGroupPosition(nodes);
|
const { x, y, width, height, maxX } = this.calculationGroupPosition(nodes);
|
||||||
|
const paddingValue = this.getGroupPadding(groupId);
|
||||||
|
|
||||||
const groupBBox = graph.get('groupBBoxs');
|
const groupBBox = graph.get('groupBBoxs');
|
||||||
groupBBox[groupId] = { x, y, width, height, maxX };
|
groupBBox[groupId] = { x, y, width, height, maxX };
|
||||||
@ -108,7 +113,7 @@ class CustomGroup {
|
|||||||
...defaultStyle,
|
...defaultStyle,
|
||||||
x: cx,
|
x: cx,
|
||||||
y: cy,
|
y: cy,
|
||||||
r: r + nodes.length * 10
|
r: r + nodes.length * 10 + paddingValue
|
||||||
},
|
},
|
||||||
capture: true,
|
capture: true,
|
||||||
zIndex,
|
zIndex,
|
||||||
@ -121,10 +126,10 @@ class CustomGroup {
|
|||||||
keyShape = nodeGroup.addShape('rect', {
|
keyShape = nodeGroup.addShape('rect', {
|
||||||
attrs: {
|
attrs: {
|
||||||
...defaultStyle,
|
...defaultStyle,
|
||||||
x,
|
x: x - paddingValue,
|
||||||
y,
|
y: y - paddingValue,
|
||||||
width,
|
width: width + nodes.length * 10 + paddingValue,
|
||||||
height
|
height: height + nodes.length * 10 + paddingValue
|
||||||
},
|
},
|
||||||
capture: true,
|
capture: true,
|
||||||
zIndex,
|
zIndex,
|
||||||
@ -177,46 +182,61 @@ class CustomGroup {
|
|||||||
calculationGroupPosition(nodes) {
|
calculationGroupPosition(nodes) {
|
||||||
const graph = this.graph;
|
const graph = this.graph;
|
||||||
|
|
||||||
const minx = [];
|
let minx = Infinity;
|
||||||
const maxx = [];
|
let maxx = -Infinity;
|
||||||
const miny = [];
|
let miny = Infinity;
|
||||||
const maxy = [];
|
let maxy = -Infinity;
|
||||||
|
|
||||||
// 获取已节点的所有最大最小x y值
|
// 获取已节点的所有最大最小x y值
|
||||||
for (const id of nodes) {
|
for (const id of nodes) {
|
||||||
const element = isString(id) ? graph.findById(id) : id;
|
const element = isString(id) ? graph.findById(id) : id;
|
||||||
const bbox = element.getBBox();
|
const bbox = element.getBBox();
|
||||||
const { minX, minY, maxX, maxY } = bbox;
|
const { minX, minY, maxX, maxY } = bbox;
|
||||||
minx.push(minX);
|
if (minX < minx) {
|
||||||
miny.push(minY);
|
minx = minX;
|
||||||
maxx.push(maxX);
|
|
||||||
maxy.push(maxY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从上一步获取的数组中,筛选出最小和最大值
|
if (minY < miny) {
|
||||||
const minX = Math.floor(Math.min(...minx));
|
miny = minY;
|
||||||
const maxX = Math.floor(Math.max(...maxx));
|
}
|
||||||
const minY = Math.floor(Math.min(...miny));
|
|
||||||
const maxY = Math.floor(Math.max(...maxy));
|
|
||||||
|
|
||||||
// const x = minX - 20;
|
if (maxX > maxx) {
|
||||||
// const y = minY - 30;
|
maxx = maxX;
|
||||||
// const width = maxX - minX + 40;
|
}
|
||||||
// const height = maxY - minY + 40;
|
|
||||||
const x = minX;
|
if (maxY > maxy) {
|
||||||
const y = minY;
|
maxy = maxY;
|
||||||
const width = maxX - minX;
|
}
|
||||||
const height = maxY - minY;
|
}
|
||||||
|
const x = Math.floor(minx);
|
||||||
|
const y = Math.floor(miny);
|
||||||
|
const width = Math.ceil(maxx) - x;
|
||||||
|
const height = Math.ceil(maxy) - y;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
maxX
|
maxX: Math.ceil(maxx)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当group中含有group时,获取padding值
|
||||||
|
* @param {string} groupId 节点分组ID
|
||||||
|
* @return {number} 在x和y方向上的偏移值
|
||||||
|
*/
|
||||||
|
getGroupPadding(groupId) {
|
||||||
|
const graph = this.graph;
|
||||||
|
const { default: defaultStyle } = this.styles;
|
||||||
|
// 检测操作的群组中是否包括子群组
|
||||||
|
const groups = graph.get('groups');
|
||||||
|
const hasSubGroup = !!groups.filter(g => g.parentId === groupId).length > 0;
|
||||||
|
const paddingValue = hasSubGroup ? defaultStyle.maxDis : defaultStyle.minDis;
|
||||||
|
return paddingValue;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 拖动群组里面的节点,更新群组属性样式
|
* 拖动群组里面的节点,更新群组属性样式
|
||||||
*
|
*
|
||||||
@ -399,34 +419,72 @@ class CustomGroup {
|
|||||||
collapseGroup(id) {
|
collapseGroup(id) {
|
||||||
const self = this;
|
const self = this;
|
||||||
const customGroup = this.getDeletageGroupById(id);
|
const customGroup = this.getDeletageGroupById(id);
|
||||||
const { nodeGroup, groupStyle } = customGroup;
|
const { nodeGroup } = customGroup;
|
||||||
|
|
||||||
// 收起群组后的默认样式
|
// 收起群组后的默认样式
|
||||||
const { collapseStyle } = this.styles;
|
const { collapseStyle } = this.styles;
|
||||||
const graph = this.graph;
|
const graph = this.graph;
|
||||||
|
const groupType = graph.get('groupType');
|
||||||
|
|
||||||
const autoPaint = graph.get('autoPaint');
|
const autoPaint = graph.get('autoPaint');
|
||||||
graph.setAutoPaint(false);
|
graph.setAutoPaint(false);
|
||||||
|
|
||||||
const nodesInGroup = graph.get('groupNodes')[id];
|
const nodesInGroup = graph.get('groupNodes')[id];
|
||||||
|
const { width: w, height: h } = this.calculationGroupPosition(nodesInGroup);
|
||||||
|
|
||||||
// 更新Group的大小
|
// 更新Group的大小
|
||||||
const keyShape = nodeGroup.get('keyShape');
|
const keyShape = nodeGroup.get('keyShape');
|
||||||
const { r, ...otherStyle } = collapseStyle;
|
const { r, width, height, ...otherStyle } = collapseStyle;
|
||||||
for (const style in otherStyle) {
|
for (const style in otherStyle) {
|
||||||
keyShape.attr(style, otherStyle[style]);
|
keyShape.attr(style, otherStyle[style]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let options = {
|
||||||
|
groupId: id,
|
||||||
|
id: `${id}-custom-node`,
|
||||||
|
x: keyShape.attr('x'),
|
||||||
|
y: keyShape.attr('y'),
|
||||||
|
style: {
|
||||||
|
r
|
||||||
|
},
|
||||||
|
shape: 'circle'
|
||||||
|
};
|
||||||
// 收起群组时候动画
|
// 收起群组时候动画
|
||||||
|
if (groupType === 'circle') {
|
||||||
|
const radius = w > h ? w / 2 : h / 2;
|
||||||
|
// const radius = wh + nodesInGroup.length * 10
|
||||||
keyShape.animate({
|
keyShape.animate({
|
||||||
onFrame(ratio) {
|
onFrame(ratio) {
|
||||||
if (ratio === 1) {
|
if (ratio === 1) {
|
||||||
self.setGroupOriginBBox(id, keyShape.getBBox());
|
self.setGroupOriginBBox(id, keyShape.getBBox());
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
r: groupStyle.r - ratio * (groupStyle.r - r)
|
r: radius - ratio * (radius - r)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, 500, 'easeCubic');
|
}, 500, 'easeCubic');
|
||||||
|
} else if (groupType === 'rect') {
|
||||||
|
keyShape.animate({
|
||||||
|
onFrame(ratio) {
|
||||||
|
if (ratio === 1) {
|
||||||
|
self.setGroupOriginBBox(id, keyShape.getBBox());
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
width: w - ratio * (w - width),
|
||||||
|
height: h - ratio * (h - height)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, 500, 'easeCubic');
|
||||||
|
|
||||||
|
options = {
|
||||||
|
groupId: id,
|
||||||
|
id: `${id}-custom-node`,
|
||||||
|
x: keyShape.attr('x') + width / 2,
|
||||||
|
y: keyShape.attr('y') + height / 2,
|
||||||
|
size: [ width, height ],
|
||||||
|
shape: 'rect'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const edges = graph.getEdges();
|
const edges = graph.getEdges();
|
||||||
// 获取所有source在群组外,target在群组内的边
|
// 获取所有source在群组外,target在群组内的边
|
||||||
@ -441,32 +499,6 @@ class CustomGroup {
|
|||||||
return nodesInGroup.includes(model.source) && !nodesInGroup.includes(model.target);
|
return nodesInGroup.includes(model.source) && !nodesInGroup.includes(model.target);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 群组中存在source和target其中有一个在群组内,一个在群组外的情况
|
|
||||||
if (sourceOutTargetInEdges.length > 0 || sourceInTargetOutEdges.length > 0) {
|
|
||||||
const options = {
|
|
||||||
groupId: id,
|
|
||||||
id: `${id}-custom-node`,
|
|
||||||
x: keyShape.attr('x'),
|
|
||||||
y: keyShape.attr('y'),
|
|
||||||
style: {
|
|
||||||
r: 30
|
|
||||||
},
|
|
||||||
shape: 'circle'
|
|
||||||
};
|
|
||||||
const delegateNode = graph.add('node', options);
|
|
||||||
delegateNode.set('capture', false);
|
|
||||||
delegateNode.hide();
|
|
||||||
this.delegateInGroup[id] = {
|
|
||||||
delegateNode
|
|
||||||
};
|
|
||||||
|
|
||||||
// 将临时添加的节点加入到群组中,以便拖动节点时候线跟着拖动
|
|
||||||
// nodesInGroup.push(`${id}-custom-node`);
|
|
||||||
this.setGroupTmpNode(id, `${id}-custom-node`);
|
|
||||||
|
|
||||||
this.updateEdgeInGroupLinks(id, sourceOutTargetInEdges, sourceInTargetOutEdges);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取群组中节点之间的所有边
|
// 获取群组中节点之间的所有边
|
||||||
const edgeAllInGroup = edges.filter(edge => {
|
const edgeAllInGroup = edges.filter(edge => {
|
||||||
const model = edge.getModel();
|
const model = edge.getModel();
|
||||||
@ -497,6 +529,21 @@ class CustomGroup {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 群组中存在source和target其中有一个在群组内,一个在群组外的情况
|
||||||
|
if (sourceOutTargetInEdges.length > 0 || sourceInTargetOutEdges.length > 0) {
|
||||||
|
const delegateNode = graph.add('node', options);
|
||||||
|
delegateNode.set('capture', false);
|
||||||
|
delegateNode.hide();
|
||||||
|
this.delegateInGroup[id] = {
|
||||||
|
delegateNode
|
||||||
|
};
|
||||||
|
|
||||||
|
// 将临时添加的节点加入到群组中,以便拖动节点时候线跟着拖动
|
||||||
|
this.setGroupTmpNode(id, `${id}-custom-node`);
|
||||||
|
|
||||||
|
this.updateEdgeInGroupLinks(id, sourceOutTargetInEdges, sourceInTargetOutEdges);
|
||||||
|
}
|
||||||
|
|
||||||
graph.paint();
|
graph.paint();
|
||||||
graph.setAutoPaint(autoPaint);
|
graph.setAutoPaint(autoPaint);
|
||||||
}
|
}
|
||||||
@ -554,42 +601,54 @@ class CustomGroup {
|
|||||||
*/
|
*/
|
||||||
expandGroup(id) {
|
expandGroup(id) {
|
||||||
const graph = this.graph;
|
const graph = this.graph;
|
||||||
|
const groupType = graph.get('groupType');
|
||||||
const self = this;
|
const self = this;
|
||||||
const autoPaint = graph.get('autoPaint');
|
const autoPaint = graph.get('autoPaint');
|
||||||
graph.setAutoPaint(false);
|
graph.setAutoPaint(false);
|
||||||
|
|
||||||
// 显示之前隐藏的节点和群组
|
// 显示之前隐藏的节点和群组
|
||||||
const nodesInGroup = graph.get('groupNodes')[id];
|
const nodesInGroup = graph.get('groupNodes')[id];
|
||||||
const { nodeGroup } = this.getDeletageGroupById(id);
|
|
||||||
const { width, height } = this.calculationGroupPosition(nodesInGroup);
|
const { width, height } = this.calculationGroupPosition(nodesInGroup);
|
||||||
// 检测操作的群组中是否包括子群组
|
const noCustomNodes = nodesInGroup.filter(node => node.indexOf('custom-node') === -1);
|
||||||
const groups = graph.get('groups');
|
const { nodeGroup } = this.getDeletageGroupById(id);
|
||||||
const hasSubGroup = !!groups.filter(g => g.parentId === id).length > 0;
|
|
||||||
const r = width > height ? width / 2 : height / 2 + (hasSubGroup ? 20 : 0);
|
|
||||||
|
|
||||||
// const cx = (width + 2 * x) / 2;
|
|
||||||
// const cy = (height + 2 * y) / 2;
|
|
||||||
const keyShape = nodeGroup.get('keyShape');
|
const keyShape = nodeGroup.get('keyShape');
|
||||||
|
|
||||||
const { default: defaultStyle } = this.styles;
|
const { default: defaultStyle, collapseStyle } = this.styles;
|
||||||
|
|
||||||
// const styles = deepMix({}, defaultStyle, { x: cx, y: cy });
|
|
||||||
for (const style in defaultStyle) {
|
for (const style in defaultStyle) {
|
||||||
keyShape.attr(style, defaultStyle[style]);
|
keyShape.attr(style, defaultStyle[style]);
|
||||||
}
|
}
|
||||||
// keyShape.attr('r', groupStyle.r + nodesInGroup.length * 10);
|
|
||||||
|
// 检测操作的群组中是否包括子群组
|
||||||
|
const paddingValue = this.getGroupPadding(id);
|
||||||
|
if (groupType === 'circle') {
|
||||||
|
const r = width > height ? width / 2 : height / 2;
|
||||||
keyShape.animate({
|
keyShape.animate({
|
||||||
onFrame(ratio) {
|
onFrame(ratio) {
|
||||||
if (ratio === 1) {
|
if (ratio === 1) {
|
||||||
self.setGroupOriginBBox(id, keyShape.getBBox());
|
self.setGroupOriginBBox(id, keyShape.getBBox());
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
r: 30 + ratio * (r + nodesInGroup.length * 10 - 30)
|
r: collapseStyle.r + ratio * (r + nodesInGroup.length * 10 - collapseStyle.r + paddingValue)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, 500, 'easeCubic');
|
}, 500, 'easeCubic');
|
||||||
|
} else if (groupType === 'rect') {
|
||||||
|
const { width: w, height: h } = collapseStyle;
|
||||||
|
keyShape.animate({
|
||||||
|
onFrame(ratio) {
|
||||||
|
if (ratio === 1) {
|
||||||
|
self.setGroupOriginBBox(id, keyShape.getBBox());
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
width: w + ratio * (width - w + paddingValue + noCustomNodes.length * 10),
|
||||||
|
height: h + ratio * (height - h + paddingValue + noCustomNodes.length * 10)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, 500, 'easeCubic');
|
||||||
|
}
|
||||||
|
|
||||||
// this.setGroupOriginBBox(id, keyShape.getBBox());
|
|
||||||
// 群组动画一会后再显示节点和边
|
// 群组动画一会后再显示节点和边
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
nodesInGroup.forEach(nodeId => {
|
nodesInGroup.forEach(nodeId => {
|
||||||
@ -624,7 +683,7 @@ class CustomGroup {
|
|||||||
edge.show();
|
edge.show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, 400);
|
}, 300);
|
||||||
|
|
||||||
const delegates = this.delegateInGroup[id];
|
const delegates = this.delegateInGroup[id];
|
||||||
if (delegates) {
|
if (delegates) {
|
||||||
|
@ -29,9 +29,9 @@ G6.registerNode('circleNode', {
|
|||||||
}
|
}
|
||||||
}, 'circle');
|
}, 'circle');
|
||||||
|
|
||||||
describe.only('signle layer group', () => {
|
describe('signle layer group', () => {
|
||||||
|
|
||||||
it('render signle group test', () => {
|
it.only('render signle group test', () => {
|
||||||
const graph = new G6.Graph({
|
const graph = new G6.Graph({
|
||||||
container: div,
|
container: div,
|
||||||
width: 1500,
|
width: 1500,
|
||||||
|
Loading…
Reference in New Issue
Block a user