g6/demos/custom-node-card.html
2020-02-14 11:30:12 +08:00

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>