mirror of
https://gitee.com/antv/g6.git
synced 2024-12-03 20:28:36 +08:00
491 lines
14 KiB
HTML
491 lines
14 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
|
<title>自定义卡片节点</title>
|
|
</head>
|
|
<body>
|
|
<div id="mountNode"></div>
|
|
<script src="../build/g6.js"></script>
|
|
<script>
|
|
const ERROR_COLOR = '#F5222D';
|
|
const getNodeConfig = node => {
|
|
if (node.nodeError) {
|
|
return {
|
|
basicColor: ERROR_COLOR,
|
|
fontColor: '#FFF',
|
|
borderColor: ERROR_COLOR,
|
|
bgColor: '#E66A6C',
|
|
};
|
|
}
|
|
let config = {
|
|
basicColor: '#722ED1',
|
|
fontColor: '#722ED1',
|
|
borderColor: '#722ED1',
|
|
bgColor: '#F6EDFC',
|
|
};
|
|
switch (node.type) {
|
|
case 'root': {
|
|
config = {
|
|
basicColor: '#E3E6E8',
|
|
fontColor: 'rgba(0,0,0,0.85)',
|
|
borderColor: '#E3E6E8',
|
|
bgColor: '#F7F9FA',
|
|
};
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
return config;
|
|
};
|
|
|
|
const COLLAPSE_ICON = function COLLAPSE_ICON(x, y, r) {
|
|
return [
|
|
['M', x - r, y],
|
|
['a', r, r, 0, 1, 0, r * 2, 0],
|
|
['a', r, r, 0, 1, 0, -r * 2, 0],
|
|
['M', x - r + 4, y],
|
|
['L', x - r + 2 * r - 4, y],
|
|
];
|
|
};
|
|
const EXPAND_ICON = function EXPAND_ICON(x, y, r) {
|
|
return [
|
|
['M', x - r, y],
|
|
['a', r, r, 0, 1, 0, r * 2, 0],
|
|
['a', r, r, 0, 1, 0, -r * 2, 0],
|
|
['M', x - r + 4, y],
|
|
['L', x - r + 2 * r - 4, y],
|
|
['M', x - r + r, y - r + 4],
|
|
['L', x, y + r - 4],
|
|
];
|
|
};
|
|
|
|
const nodeBasicMethod = {
|
|
createNodeBox: (group, config, width, height, isRoot) => {
|
|
/* 最外面的大矩形 */
|
|
const container = group.addShape('rect', {
|
|
attrs: {
|
|
x: 0,
|
|
y: 0,
|
|
width,
|
|
height,
|
|
},
|
|
});
|
|
if (!isRoot) {
|
|
/* 左边的小圆点 */
|
|
group.addShape('circle', {
|
|
attrs: {
|
|
x: 3,
|
|
y: height / 2,
|
|
r: 6,
|
|
fill: config.basicColor,
|
|
},
|
|
});
|
|
}
|
|
/* 矩形 */
|
|
group.addShape('rect', {
|
|
attrs: {
|
|
x: 3,
|
|
y: 0,
|
|
width: width - 19,
|
|
height,
|
|
fill: config.bgColor,
|
|
stroke: config.borderColor,
|
|
radius: 2,
|
|
cursor: 'pointer',
|
|
},
|
|
});
|
|
|
|
/* 左边的粗线 */
|
|
group.addShape('rect', {
|
|
attrs: {
|
|
x: 3,
|
|
y: 0,
|
|
width: 3,
|
|
height,
|
|
fill: config.basicColor,
|
|
radius: 1.5,
|
|
},
|
|
});
|
|
return container;
|
|
},
|
|
/* 生成树上的 marker */
|
|
createNodeMarker: (group, collapsed, x, y) => {
|
|
group.addShape('circle', {
|
|
attrs: {
|
|
x,
|
|
y,
|
|
r: 13,
|
|
fill: 'rgba(47, 84, 235, 0.05)',
|
|
opacity: 0,
|
|
zIndex: -2,
|
|
},
|
|
className: 'collapse-icon-bg',
|
|
});
|
|
group.addShape('marker', {
|
|
attrs: {
|
|
x,
|
|
y,
|
|
radius: 7,
|
|
symbol: collapsed ? EXPAND_ICON : COLLAPSE_ICON,
|
|
stroke: 'rgba(0,0,0,0.25)',
|
|
fill: 'rgba(0,0,0,0)',
|
|
lineWidth: 1,
|
|
cursor: 'pointer',
|
|
},
|
|
className: 'collapse-icon',
|
|
});
|
|
},
|
|
afterDraw: (cfg, group) => {
|
|
/* 操作 marker 的背景色显示隐藏 */
|
|
const icon = group.findByClassName('collapse-icon');
|
|
if (icon) {
|
|
const bg = group.findByClassName('collapse-icon-bg');
|
|
icon.on('mouseenter', () => {
|
|
bg.attr('opacity', 1);
|
|
graph.get('canvas').draw();
|
|
});
|
|
icon.on('mouseleave', () => {
|
|
bg.attr('opacity', 0);
|
|
graph.get('canvas').draw();
|
|
});
|
|
}
|
|
/* ip 显示 */
|
|
const ipBox = group.findByClassName('ip-box');
|
|
if (ipBox) {
|
|
/* ip 复制的几个元素 */
|
|
const ipLine = group.findByClassName('ip-cp-line');
|
|
const ipBG = group.findByClassName('ip-cp-bg');
|
|
const ipIcon = group.findByClassName('ip-cp-icon');
|
|
const ipCPBox = group.findByClassName('ip-cp-box');
|
|
|
|
const onMouseEnter = () => {
|
|
this.ipHideTimer && clearTimeout(this.ipHideTimer);
|
|
ipLine.attr('opacity', 1);
|
|
ipBG.attr('opacity', 1);
|
|
ipIcon.attr('opacity', 1);
|
|
graph.get('canvas').draw();
|
|
};
|
|
const onMouseLeave = () => {
|
|
this.ipHideTimer = setTimeout(() => {
|
|
ipLine.attr('opacity', 0);
|
|
ipBG.attr('opacity', 0);
|
|
ipIcon.attr('opacity', 0);
|
|
graph.get('canvas').draw();
|
|
}, 100);
|
|
};
|
|
ipBox.on('mouseenter', () => {
|
|
onMouseEnter();
|
|
});
|
|
ipBox.on('mouseleave', () => {
|
|
onMouseLeave();
|
|
});
|
|
ipCPBox.on('mouseenter', () => {
|
|
onMouseEnter();
|
|
});
|
|
ipCPBox.on('mouseleave', () => {
|
|
onMouseLeave();
|
|
});
|
|
ipCPBox.on('click', () => {});
|
|
}
|
|
},
|
|
setState: (name, value, item) => {
|
|
const hasOpacityClass = [
|
|
'ip-cp-line',
|
|
'ip-cp-bg',
|
|
'ip-cp-icon',
|
|
'ip-cp-box',
|
|
'ip-box',
|
|
'collapse-icon-bg',
|
|
];
|
|
const group = item.getContainer();
|
|
const childrens = group.get('children');
|
|
graph.setAutoPaint(false);
|
|
if (name === 'emptiness') {
|
|
if (value) {
|
|
childrens.forEach(shape => {
|
|
if (hasOpacityClass.indexOf(shape.get('className')) > -1) {
|
|
return;
|
|
}
|
|
shape.attr('opacity', 0.4);
|
|
});
|
|
} else {
|
|
childrens.forEach(shape => {
|
|
if (hasOpacityClass.indexOf(shape.get('className')) > -1) {
|
|
return;
|
|
}
|
|
shape.attr('opacity', 1);
|
|
});
|
|
}
|
|
}
|
|
graph.setAutoPaint(true);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* 计算字符串的长度
|
|
* @param {string} str 指定的字符串
|
|
*/
|
|
const calcStrLen = str => {
|
|
let len = 0;
|
|
for (let i = 0; i < str.length; i++) {
|
|
if (str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) {
|
|
len++;
|
|
} else {
|
|
len += 2;
|
|
}
|
|
}
|
|
return len;
|
|
};
|
|
|
|
G6.registerNode(
|
|
'card-node',
|
|
{
|
|
drawShape: (cfg, group) => {
|
|
const config = getNodeConfig(cfg);
|
|
const isRoot = cfg.type === 'root';
|
|
const nodeError = cfg.nodeError;
|
|
/* 最外面的大矩形 */
|
|
const container = nodeBasicMethod.createNodeBox(group, config, 243, 64, isRoot);
|
|
|
|
if (cfg.type !== 'root') {
|
|
/* 上边的 type */
|
|
group.addShape('text', {
|
|
attrs: {
|
|
text: cfg.type,
|
|
x: 3,
|
|
y: -10,
|
|
fontSize: 12,
|
|
textAlign: 'left',
|
|
textBaseline: 'middle',
|
|
fill: 'rgba(0,0,0,0.65)',
|
|
},
|
|
});
|
|
}
|
|
|
|
let ipWidth = 0;
|
|
if (cfg.ip) {
|
|
/* ip start */
|
|
/* ipBox */
|
|
const ipRect = group.addShape('rect', {
|
|
attrs: {
|
|
fill: nodeError ? null : '#FFF',
|
|
stroke: nodeError ? 'rgba(255,255,255,0.65)' : null,
|
|
radius: 2,
|
|
cursor: 'pointer',
|
|
},
|
|
});
|
|
|
|
/* ip */
|
|
const ipText = group.addShape('text', {
|
|
attrs: {
|
|
text: cfg.ip,
|
|
x: 0,
|
|
y: 19,
|
|
fontSize: 12,
|
|
textAlign: 'left',
|
|
textBaseline: 'middle',
|
|
fill: nodeError ? 'rgba(255,255,255,0.85)' : 'rgba(0,0,0,0.65)',
|
|
cursor: 'pointer',
|
|
},
|
|
});
|
|
|
|
const ipBBox = ipText.getBBox();
|
|
/* ip 的文字总是距离右边 12px */
|
|
ipText.attr({
|
|
x: 224 - 12 - ipBBox.width,
|
|
});
|
|
/* ipBox */
|
|
ipRect.attr({
|
|
x: 224 - 12 - ipBBox.width - 4,
|
|
y: ipBBox.minY - 5,
|
|
width: ipBBox.width + 8,
|
|
height: ipBBox.height + 10,
|
|
});
|
|
|
|
/* 在 IP 元素上面覆盖一层透明层,方便监听 hover 事件 */
|
|
group.addShape('rect', {
|
|
attrs: {
|
|
stroke: '',
|
|
cursor: 'pointer',
|
|
x: 224 - 12 - ipBBox.width - 4,
|
|
y: ipBBox.minY - 5,
|
|
width: ipBBox.width + 8,
|
|
height: ipBBox.height + 10,
|
|
fill: '#fff',
|
|
opacity: 0,
|
|
},
|
|
className: 'ip-box',
|
|
});
|
|
|
|
/* copyIpLine */
|
|
group.addShape('rect', {
|
|
attrs: {
|
|
x: 194,
|
|
y: 7,
|
|
width: 1,
|
|
height: 24,
|
|
fill: '#E3E6E8',
|
|
opacity: 0,
|
|
},
|
|
className: 'ip-cp-line',
|
|
});
|
|
/* copyIpBG */
|
|
group.addShape('rect', {
|
|
attrs: {
|
|
x: 195,
|
|
y: 8,
|
|
width: 22,
|
|
height: 22,
|
|
fill: '#FFF',
|
|
cursor: 'pointer',
|
|
opacity: 0,
|
|
},
|
|
className: 'ip-cp-bg',
|
|
});
|
|
/* copyIpIcon */
|
|
group.addShape('image', {
|
|
attrs: {
|
|
x: 200,
|
|
y: 13,
|
|
height: 12,
|
|
width: 10,
|
|
img: 'https://os.alipayobjects.com/rmsportal/DFhnQEhHyPjSGYW.png',
|
|
cursor: 'pointer',
|
|
opacity: 0,
|
|
},
|
|
className: 'ip-cp-icon',
|
|
});
|
|
/* 放一个透明的矩形在 icon 区域上,方便监听点击 */
|
|
group.addShape('rect', {
|
|
attrs: {
|
|
x: 195,
|
|
y: 8,
|
|
width: 22,
|
|
height: 22,
|
|
fill: '#FFF',
|
|
cursor: 'pointer',
|
|
opacity: 0,
|
|
},
|
|
className: 'ip-cp-box',
|
|
tooltip: '复制IP',
|
|
});
|
|
|
|
const ipRectBBox = ipRect.getBBox();
|
|
ipWidth = ipRectBBox.width;
|
|
/* ip end */
|
|
}
|
|
|
|
/* name */
|
|
const nameText = group.addShape('text', {
|
|
attrs: {
|
|
text: cfg.name,
|
|
x: 19,
|
|
y: 19,
|
|
fontSize: 14,
|
|
fontWeight: 700,
|
|
textAlign: 'left',
|
|
textBaseline: 'middle',
|
|
fill: config.fontColor,
|
|
cursor: 'pointer',
|
|
},
|
|
// tooltip: cfg.name,
|
|
});
|
|
|
|
/* 下面的文字 */
|
|
const remarkText = group.addShape('text', {
|
|
attrs: {
|
|
text: cfg.keyInfo,
|
|
x: 19,
|
|
y: 45,
|
|
fontSize: 14,
|
|
textAlign: 'left',
|
|
textBaseline: 'middle',
|
|
fill: config.fontColor,
|
|
cursor: 'pointer',
|
|
},
|
|
});
|
|
|
|
if (nodeError) {
|
|
group.addShape('text', {
|
|
attrs: {
|
|
x: 191,
|
|
y: 62,
|
|
text: '⚠️',
|
|
fill: '#000',
|
|
fontSize: 18,
|
|
},
|
|
});
|
|
}
|
|
|
|
const hasChildren = cfg.children && cfg.children.length > 0;
|
|
if (hasChildren) {
|
|
nodeBasicMethod.createNodeMarker(group, cfg.collapsed, 236, 32);
|
|
}
|
|
return container;
|
|
},
|
|
afterDraw: nodeBasicMethod.afterDraw,
|
|
setState: nodeBasicMethod.setState,
|
|
},
|
|
'single-shape',
|
|
);
|
|
|
|
const data = {
|
|
nodes: [
|
|
{
|
|
name: 'cardNodeApp',
|
|
ip: '127.0.0.1',
|
|
nodeError: true,
|
|
type: 'root',
|
|
keyInfo: 'this is a card node info',
|
|
x: 100,
|
|
y: 50,
|
|
},
|
|
{
|
|
name: 'cardNodeApp',
|
|
ip: '127.0.0.1',
|
|
nodeError: false,
|
|
type: 'subRoot',
|
|
keyInfo: 'this is sub root',
|
|
x: 100,
|
|
y: 150,
|
|
},
|
|
{
|
|
name: 'cardNodeApp',
|
|
ip: '127.0.0.1',
|
|
nodeError: false,
|
|
type: 'subRoot',
|
|
keyInfo: 'this is sub root',
|
|
x: 100,
|
|
y: 250,
|
|
children: [
|
|
{
|
|
name: 'sub',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
edges: [],
|
|
};
|
|
|
|
const graph = new G6.Graph({
|
|
container: 'mountNode',
|
|
width: 800,
|
|
height: 600,
|
|
modes: {
|
|
default: ['drag-node'],
|
|
},
|
|
defaultNode: {
|
|
shape: 'card-node',
|
|
},
|
|
});
|
|
|
|
graph.data(data);
|
|
graph.render();
|
|
</script>
|
|
</body>
|
|
</html>
|