g6/demos/custom-node-card.html

496 lines
13 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>