feat: add rect group

This commit is contained in:
zhanning.bzn 2019-09-09 17:16:41 +08:00
parent 2239303b4f
commit 3af1b32d0d
7 changed files with 421 additions and 252 deletions

View File

@ -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
View 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>

View File

@ -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);
}
} }
}; };

View File

@ -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();
} }

View File

@ -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
}; };
} }
}; };

View File

@ -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) {

View File

@ -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,