mirror of
https://gitee.com/antv/g6.git
synced 2024-12-04 20:59:15 +08:00
feat: add rect group
This commit is contained in:
parent
2239303b4f
commit
3af1b32d0d
@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>拖动群组</title>
|
||||
<title>Circle节点分组</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mountNode"></div>
|
||||
@ -46,101 +46,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
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 = {
|
||||
const data = {
|
||||
nodes: [
|
||||
{
|
||||
id: 'node6',
|
||||
@ -201,6 +107,14 @@ title: '3'
|
||||
{
|
||||
source: 'node2',
|
||||
target: 'node3'
|
||||
},
|
||||
{
|
||||
source: 'node1',
|
||||
target: 'node3'
|
||||
},
|
||||
{
|
||||
source: 'node6',
|
||||
target: 'node1'
|
||||
}
|
||||
],
|
||||
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: 拖动群组
|
||||
*/
|
||||
const deepMix = require('@antv/util/lib/deep-mix');
|
||||
const body = document.body;
|
||||
|
||||
const delegateStyle = {
|
||||
fill: '#F3F9FF',
|
||||
@ -28,7 +29,8 @@ module.exports = {
|
||||
return {
|
||||
dragstart: 'onDragStart',
|
||||
drag: 'onDrag',
|
||||
dragend: 'onDragEnd'
|
||||
dragend: 'onDragEnd',
|
||||
'canvas:mouseleave': 'onOutOfRange'
|
||||
};
|
||||
},
|
||||
onDragStart(evt) {
|
||||
@ -123,6 +125,7 @@ module.exports = {
|
||||
}
|
||||
|
||||
const graph = this.graph;
|
||||
const groupType = graph.get('groupType');
|
||||
|
||||
// 更新群组里面节点和线的位置
|
||||
this.updateItemPosition(evt);
|
||||
@ -169,19 +172,25 @@ module.exports = {
|
||||
groupNodes[parentGroupId] = parentGroupNodes.filter(node => currentGroupNodes.indexOf(node) === -1);
|
||||
|
||||
const { x: x1, y: y1, width, height } = customGroupControll.calculationGroupPosition(groupNodes[parentGroupId]);
|
||||
const groups = graph.get('groups');
|
||||
const hasSubGroup = !!groups.filter(g => g.parentId === parentGroupId).length > 0;
|
||||
const r = width > height ? width / 2 : height / 2 + (hasSubGroup ? 20 : 0);
|
||||
const paddingValue = customGroupControll.getGroupPadding(parentGroupId);
|
||||
|
||||
if (groupType === 'circle') {
|
||||
const r = width > height ? width / 2 : height / 2;
|
||||
const cx = (width + 2 * x1) / 2;
|
||||
const cy = (height + 2 * y1) / 2;
|
||||
// groupKeyShape.attr('x', cx);
|
||||
// groupKeyShape.attr('y', cy);
|
||||
parentKeyShape.attr({
|
||||
r: r + groupNodes[groupId].length * 10,
|
||||
r: r + groupNodes[parentGroupId].length * 10 + paddingValue,
|
||||
x: cx,
|
||||
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 graph = this.graph;
|
||||
const groupType = graph.get('groupType');
|
||||
|
||||
// 获取群组对象
|
||||
const customGroupControll = graph.get('customGroupControll');
|
||||
@ -204,14 +214,15 @@ module.exports = {
|
||||
// step 1:先修改groupId中的节点位置
|
||||
const nodeInGroup = groupNodes[groupId];
|
||||
const groupOriginBBox = customGroupControll.getGroupOriginBBox(groupId);
|
||||
|
||||
const delegateShapeBBoxs = this.delegateShapeBBoxs[groupId];
|
||||
const otherGroupId = [];
|
||||
nodeInGroup.forEach((nodeId, index) => {
|
||||
|
||||
const node = graph.findById(nodeId);
|
||||
const model = node.getModel();
|
||||
if (model.groupId && !otherGroupId.includes(model.groupId)) {
|
||||
otherGroupId.push(model.groupId);
|
||||
const nodeGroupId = model.groupId;
|
||||
if (nodeGroupId && !otherGroupId.includes(nodeGroupId)) {
|
||||
otherGroupId.push(nodeGroupId);
|
||||
}
|
||||
if (!this.nodePoint[index]) {
|
||||
this.nodePoint[index] = {
|
||||
@ -246,10 +257,21 @@ module.exports = {
|
||||
const groupKeyShape = nodeGroup.get('keyShape');
|
||||
|
||||
const { x, y, width, height } = customGroupControll.calculationGroupPosition(groupNodes[id]);
|
||||
|
||||
if (groupType === 'circle') {
|
||||
const cx = (width + 2 * x) / 2;
|
||||
const cy = (height + 2 * y) / 2;
|
||||
groupKeyShape.attr('x', cx);
|
||||
groupKeyShape.attr('y', cy);
|
||||
groupKeyShape.attr({
|
||||
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());
|
||||
});
|
||||
|
||||
@ -309,13 +331,30 @@ module.exports = {
|
||||
const x = deltaX + shapeOrigin.x;
|
||||
const y = deltaY + shapeOrigin.y;
|
||||
|
||||
if (delegateType === 'circle') {
|
||||
// 将Canvas坐标转成视口坐标
|
||||
const point = graph.getPointByCanvas(x, y);
|
||||
delegateShape.attr({ x: point.x, y: point.y });
|
||||
} else {
|
||||
delegateShape.attr({ x, y });
|
||||
}
|
||||
self.delegateShapeBBoxs[groupId] = delegateShape.getBBox();
|
||||
}
|
||||
|
||||
graph.paint();
|
||||
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 groupId = target.get('groupId');
|
||||
const type = target.get('type');
|
||||
if (type !== 'circle') {
|
||||
if (type !== 'circle' || type !== 'rect') {
|
||||
return;
|
||||
}
|
||||
if (groupId && this.origin) {
|
||||
@ -209,12 +209,14 @@ module.exports = {
|
||||
const { groupId } = model;
|
||||
|
||||
const graph = this.graph;
|
||||
const groupType = graph.get('groupType');
|
||||
const customGroupControll = graph.get('customGroupControll');
|
||||
const groupNodes = graph.get('groupNodes');
|
||||
const nodes = groupNodes[groupId];
|
||||
|
||||
// 拖出节点后,根据最新的节点数量,重新计算群组大小
|
||||
// 如果只有一个节点,拖出后,则删除该组
|
||||
if (groupNodes[groupId].length === 0) {
|
||||
if (nodes.length === 0) {
|
||||
// step 1: 从groupNodes中删除
|
||||
delete groupNodes[groupId];
|
||||
|
||||
@ -225,19 +227,27 @@ module.exports = {
|
||||
// step 3: 删除原来的群组
|
||||
currentGroup.remove();
|
||||
} else {
|
||||
const { x, y, width, height } = customGroupControll.calculationGroupPosition(groupNodes[groupId]);
|
||||
const { x, y, width, height } = customGroupControll.calculationGroupPosition(nodes);
|
||||
// 检测操作的群组中是否包括子群组
|
||||
const groups = graph.get('groups');
|
||||
const hasSubGroup = !!groups.filter(g => g.parentId === groupId).length > 0;
|
||||
const addR = hasSubGroup ? 20 : 10;
|
||||
const paddingValue = customGroupControll.getGroupPadding(groupId);
|
||||
|
||||
if (groupType === 'circle') {
|
||||
const r = width > height ? width / 2 : height / 2;
|
||||
const cx = (width + 2 * x) / 2;
|
||||
const cy = (height + 2 * y) / 2;
|
||||
keyShape.attr({
|
||||
r: r + groupNodes[groupId].length * 10 + addR,
|
||||
r: r + nodes.length * 10 + paddingValue,
|
||||
x: cx,
|
||||
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());
|
||||
}
|
||||
@ -379,19 +389,20 @@ module.exports = {
|
||||
_updateDelegate(e, x, y) {
|
||||
const { item } = e;
|
||||
const graph = this.graph;
|
||||
const groupType = graph.get('groupType');
|
||||
const bbox = item.get('keyShape').getBBox();
|
||||
if (!this.shape) {
|
||||
// 拖动多个
|
||||
const parent = graph.get('group');
|
||||
const attrs = deepMix({}, delegateStyle, this.delegateStyle);
|
||||
// 拖动多个
|
||||
if (this.targets.length > 0) {
|
||||
const nodes = graph.findAllByState('node', 'selected');
|
||||
if (nodes.length === 0) {
|
||||
nodes.push(item);
|
||||
}
|
||||
const customGroupControll = graph.get('customGroupControll');
|
||||
const { x, y, width, height, minX, minY } = customGroupControll.calculationGroupPosition(nodes);
|
||||
this.originPoint = { x, y, width, height, minX, minY };
|
||||
const { x, y, width, height } = customGroupControll.calculationGroupPosition(nodes);
|
||||
this.originPoint = { x, y, width, height };
|
||||
// model上的x, y是相对于图形中心的,delegateShape是g实例,x,y是绝对坐标
|
||||
this.shape = parent.addShape('rect', {
|
||||
attrs: {
|
||||
@ -425,10 +436,17 @@ module.exports = {
|
||||
y: clientY
|
||||
});
|
||||
} else if (this.target) {
|
||||
if (groupType === 'circle') {
|
||||
this.shape.attr({
|
||||
x: x - bbox.width / 2,
|
||||
y: y - bbox.height / 2
|
||||
});
|
||||
} else if (groupType === 'rect') {
|
||||
this.shape.attr({
|
||||
x,
|
||||
y
|
||||
});
|
||||
}
|
||||
}
|
||||
this.graph.paint();
|
||||
}
|
||||
|
@ -228,40 +228,45 @@ module.exports = {
|
||||
const graph = this.graph;
|
||||
|
||||
const nodes = graph.findAllByState('node', 'selected');
|
||||
const minx = [];
|
||||
const maxx = [];
|
||||
const miny = [];
|
||||
const maxy = [];
|
||||
|
||||
let minx = Infinity;
|
||||
let maxx = -Infinity;
|
||||
let miny = Infinity;
|
||||
let maxy = -Infinity;
|
||||
|
||||
// 获取已节点的所有最大最小x y值
|
||||
for (const id of nodes) {
|
||||
const element = isString(id) ? graph.findById(id) : id;
|
||||
const bbox = element.getBBox();
|
||||
const { minX, minY, maxX, maxY } = bbox;
|
||||
minx.push(minX);
|
||||
miny.push(minY);
|
||||
maxx.push(maxX);
|
||||
maxy.push(maxY);
|
||||
if (minX < minx) {
|
||||
minx = minX;
|
||||
}
|
||||
|
||||
// 从上一步获取的数组中,筛选出最小和最大值
|
||||
const minX = Math.floor(Math.min(...minx));
|
||||
const maxX = Math.floor(Math.max(...maxx));
|
||||
const minY = Math.floor(Math.min(...miny));
|
||||
const maxY = Math.floor(Math.max(...maxy));
|
||||
if (minY < miny) {
|
||||
miny = minY;
|
||||
}
|
||||
|
||||
const x = minX - 20;
|
||||
const y = minY + 10;
|
||||
const width = maxX - minX;
|
||||
const height = maxY - minY;
|
||||
if (maxX > maxx) {
|
||||
maxx = maxX;
|
||||
}
|
||||
|
||||
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 {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
minX,
|
||||
minY
|
||||
minX: minx,
|
||||
minY: miny
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@ -18,7 +18,9 @@ class CustomGroup {
|
||||
strokeOpacity: 0.9,
|
||||
fill: '#F3F9FF',
|
||||
fillOpacity: 0.8,
|
||||
opacity: 0.8
|
||||
opacity: 0.8,
|
||||
minDis: 20,
|
||||
maxDis: 40
|
||||
},
|
||||
hover: {
|
||||
stroke: '#faad14',
|
||||
@ -30,6 +32,8 @@ class CustomGroup {
|
||||
// 收起状态样式
|
||||
collapseStyle: {
|
||||
r: 30,
|
||||
width: 80,
|
||||
height: 40,
|
||||
// lineDash: [ 5, 5 ],
|
||||
stroke: '#A3B1BF',
|
||||
lineWidth: 3,
|
||||
@ -92,6 +96,7 @@ class CustomGroup {
|
||||
|
||||
// 计算群组左上角左边、宽度、高度及x轴方向上的最大值
|
||||
const { x, y, width, height, maxX } = this.calculationGroupPosition(nodes);
|
||||
const paddingValue = this.getGroupPadding(groupId);
|
||||
|
||||
const groupBBox = graph.get('groupBBoxs');
|
||||
groupBBox[groupId] = { x, y, width, height, maxX };
|
||||
@ -108,7 +113,7 @@ class CustomGroup {
|
||||
...defaultStyle,
|
||||
x: cx,
|
||||
y: cy,
|
||||
r: r + nodes.length * 10
|
||||
r: r + nodes.length * 10 + paddingValue
|
||||
},
|
||||
capture: true,
|
||||
zIndex,
|
||||
@ -121,10 +126,10 @@ class CustomGroup {
|
||||
keyShape = nodeGroup.addShape('rect', {
|
||||
attrs: {
|
||||
...defaultStyle,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height
|
||||
x: x - paddingValue,
|
||||
y: y - paddingValue,
|
||||
width: width + nodes.length * 10 + paddingValue,
|
||||
height: height + nodes.length * 10 + paddingValue
|
||||
},
|
||||
capture: true,
|
||||
zIndex,
|
||||
@ -177,46 +182,61 @@ class CustomGroup {
|
||||
calculationGroupPosition(nodes) {
|
||||
const graph = this.graph;
|
||||
|
||||
const minx = [];
|
||||
const maxx = [];
|
||||
const miny = [];
|
||||
const maxy = [];
|
||||
let minx = Infinity;
|
||||
let maxx = -Infinity;
|
||||
let miny = Infinity;
|
||||
let maxy = -Infinity;
|
||||
|
||||
// 获取已节点的所有最大最小x y值
|
||||
for (const id of nodes) {
|
||||
const element = isString(id) ? graph.findById(id) : id;
|
||||
const bbox = element.getBBox();
|
||||
const { minX, minY, maxX, maxY } = bbox;
|
||||
minx.push(minX);
|
||||
miny.push(minY);
|
||||
maxx.push(maxX);
|
||||
maxy.push(maxY);
|
||||
if (minX < minx) {
|
||||
minx = minX;
|
||||
}
|
||||
|
||||
// 从上一步获取的数组中,筛选出最小和最大值
|
||||
const minX = Math.floor(Math.min(...minx));
|
||||
const maxX = Math.floor(Math.max(...maxx));
|
||||
const minY = Math.floor(Math.min(...miny));
|
||||
const maxY = Math.floor(Math.max(...maxy));
|
||||
if (minY < miny) {
|
||||
miny = minY;
|
||||
}
|
||||
|
||||
// const x = minX - 20;
|
||||
// const y = minY - 30;
|
||||
// const width = maxX - minX + 40;
|
||||
// const height = maxY - minY + 40;
|
||||
const x = minX;
|
||||
const y = minY;
|
||||
const width = maxX - minX;
|
||||
const height = maxY - minY;
|
||||
if (maxX > maxx) {
|
||||
maxx = maxX;
|
||||
}
|
||||
|
||||
if (maxY > maxy) {
|
||||
maxy = maxY;
|
||||
}
|
||||
}
|
||||
const x = Math.floor(minx);
|
||||
const y = Math.floor(miny);
|
||||
const width = Math.ceil(maxx) - x;
|
||||
const height = Math.ceil(maxy) - y;
|
||||
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
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) {
|
||||
const self = this;
|
||||
const customGroup = this.getDeletageGroupById(id);
|
||||
const { nodeGroup, groupStyle } = customGroup;
|
||||
const { nodeGroup } = customGroup;
|
||||
|
||||
// 收起群组后的默认样式
|
||||
const { collapseStyle } = this.styles;
|
||||
const graph = this.graph;
|
||||
const groupType = graph.get('groupType');
|
||||
|
||||
const autoPaint = graph.get('autoPaint');
|
||||
graph.setAutoPaint(false);
|
||||
|
||||
const nodesInGroup = graph.get('groupNodes')[id];
|
||||
const { width: w, height: h } = this.calculationGroupPosition(nodesInGroup);
|
||||
|
||||
// 更新Group的大小
|
||||
const keyShape = nodeGroup.get('keyShape');
|
||||
const { r, ...otherStyle } = collapseStyle;
|
||||
const { r, width, height, ...otherStyle } = collapseStyle;
|
||||
for (const style in otherStyle) {
|
||||
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({
|
||||
onFrame(ratio) {
|
||||
if (ratio === 1) {
|
||||
self.setGroupOriginBBox(id, keyShape.getBBox());
|
||||
}
|
||||
return {
|
||||
r: groupStyle.r - ratio * (groupStyle.r - r)
|
||||
r: radius - ratio * (radius - r)
|
||||
};
|
||||
}
|
||||
}, 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();
|
||||
// 获取所有source在群组外,target在群组内的边
|
||||
@ -441,32 +499,6 @@ class CustomGroup {
|
||||
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 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.setAutoPaint(autoPaint);
|
||||
}
|
||||
@ -554,42 +601,54 @@ class CustomGroup {
|
||||
*/
|
||||
expandGroup(id) {
|
||||
const graph = this.graph;
|
||||
const groupType = graph.get('groupType');
|
||||
const self = this;
|
||||
const autoPaint = graph.get('autoPaint');
|
||||
graph.setAutoPaint(false);
|
||||
|
||||
// 显示之前隐藏的节点和群组
|
||||
const nodesInGroup = graph.get('groupNodes')[id];
|
||||
const { nodeGroup } = this.getDeletageGroupById(id);
|
||||
const { width, height } = this.calculationGroupPosition(nodesInGroup);
|
||||
// 检测操作的群组中是否包括子群组
|
||||
const groups = graph.get('groups');
|
||||
const hasSubGroup = !!groups.filter(g => g.parentId === id).length > 0;
|
||||
const r = width > height ? width / 2 : height / 2 + (hasSubGroup ? 20 : 0);
|
||||
const noCustomNodes = nodesInGroup.filter(node => node.indexOf('custom-node') === -1);
|
||||
const { nodeGroup } = this.getDeletageGroupById(id);
|
||||
|
||||
// const cx = (width + 2 * x) / 2;
|
||||
// const cy = (height + 2 * y) / 2;
|
||||
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) {
|
||||
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({
|
||||
onFrame(ratio) {
|
||||
if (ratio === 1) {
|
||||
self.setGroupOriginBBox(id, keyShape.getBBox());
|
||||
}
|
||||
return {
|
||||
r: 30 + ratio * (r + nodesInGroup.length * 10 - 30)
|
||||
r: collapseStyle.r + ratio * (r + nodesInGroup.length * 10 - collapseStyle.r + paddingValue)
|
||||
};
|
||||
}
|
||||
}, 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(() => {
|
||||
nodesInGroup.forEach(nodeId => {
|
||||
@ -624,7 +683,7 @@ class CustomGroup {
|
||||
edge.show();
|
||||
}
|
||||
});
|
||||
}, 400);
|
||||
}, 300);
|
||||
|
||||
const delegates = this.delegateInGroup[id];
|
||||
if (delegates) {
|
||||
|
@ -29,9 +29,9 @@ G6.registerNode('circleNode', {
|
||||
}
|
||||
}, '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({
|
||||
container: div,
|
||||
width: 1500,
|
||||
|
Loading…
Reference in New Issue
Block a user