chore: add demo with complicated node

This commit is contained in:
elaine 2019-04-28 16:16:57 +08:00
parent 66148319a0
commit 81371eebfc

998
demos/cloudatlas.html Normal file
View File

@ -0,0 +1,998 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<div id="mountNode"></div>
</head>
<body>
<script src="../build/g6.js"></script>
<script src="../build/minimap.js"></script>
<script src="assets/hierarchy.js"></script>
<script src="./assets/jquery-3.2.1.min.js"></script>
<script>
const ERROR_COLOR = "#F5222D";
const SIMPLE_TREE_NODE = "simple-tree-node";
const TREE_NODE = "tree-node";
const SOFAROUTER_TEXT_CLASS = "sofarouter-text-class";
const SOFAROUTER_RECT_CLASS = "sofarouter-rect-class";
const CANVAS_WIDTH = 1000;
const CANVAS_HEIGHT = 600;
const LIMIT_OVERFLOW_WIDTH = CANVAS_WIDTH - 100;
const LIMIT_OVERFLOW_HEIGHT = CANVAS_HEIGHT - 100;
const TIP_HEIGHT = 28;
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;
}
case "httpclient":
case "rest":
case "mvc":
case "rpc":
case "rpc2jvm":
config = {
basicColor: "#2F54EB",
fontColor: "#2F54EB",
borderColor: "#2F54EB",
bgColor: "#F3F6FD"
};
break;
case "db":
config = {
basicColor: "#52C41A",
fontColor: "#52C41A",
borderColor: "#52C41A",
bgColor: "#F4FCEB"
};
break;
case "msgPub":
case "msgSub":
case "zqmsgSend":
case "zqmsgRecv":
case "antqPub":
case "antqSub":
config = {
basicColor: "#FA8C16",
fontColor: "#FA8C16",
borderColor: "#FA8C16",
bgColor: "#FCF4E3"
};
break;
case "zdalTair":
case "zdalOcs":
case "zdalOss":
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
// fill: '#FFF',
// stroke: '#000',
}
});
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);
}
};
/* 精简节点 */
G6.registerNode(
SIMPLE_TREE_NODE,
{
drawShape: (cfg, group) => {
const config = getNodeConfig(cfg);
const isRoot = cfg.type === "root";
const nodeError = cfg.nodeError;
const container = nodeBasicMethod.createNodeBox(
group,
config,
171,
38,
isRoot
);
/* 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"
}
});
/* 修复 nameText 超长 */
fittingString(nameText, cfg.name, 133);
if (nodeError) {
group.addShape("image", {
attrs: {
x: 119,
y: 5,
height: 35,
width: 35,
img: "/static/images/warning-circle.svg"
}
});
}
const hasChildren = cfg.children && cfg.children.length > 0;
if (hasChildren) {
nodeBasicMethod.createNodeMarker(group, cfg.collapsed, 164, 19);
}
return container;
},
afterDraw: nodeBasicMethod.afterDraw,
setState: nodeBasicMethod.setState
},
"single-shape"
);
/* 复杂节点 */
G6.registerNode(
TREE_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,
});
/* 根据 IP 的长度计算出 剩下的 留给 name 的长度! */
/* 修复 nameText 超长 */
fittingString(nameText, cfg.name, 224 - ipWidth - 20);
/* 下面的文字 */
const remarkText = group.addShape("text", {
attrs: {
text: cfg.keyInfo,
x: 19,
y: 45,
fontSize: 14,
textAlign: "left",
textBaseline: "middle",
fill: config.fontColor,
cursor: "pointer"
}
// className: 'keyInfo',
// tooltip: cfg.keyInfo,
});
fittingString(remarkText, cfg.keyInfo, 204);
if (nodeError) {
group.addShape("image", {
attrs: {
x: 191,
y: 32,
height: 35,
width: 35,
img: "/static/images/warning-circle.svg"
}
});
}
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"
);
/* 是否显示 sofarouter通过透明度来控制 */
G6.registerEdge(
"tree-edge",
{
draw(cfg, group) {
const targetNode = cfg.targetNode.getModel();
const edgeError = !!targetNode.edgeError;
const startPoint = cfg.startPoint;
const endPoint = cfg.endPoint;
const controlPoints = this.getControlPoints(cfg);
let points = [startPoint]; // 添加起始点
// 添加控制点
if (controlPoints) {
points = points.concat(controlPoints);
}
// 添加结束点
points.push(endPoint);
const path = this.getPath(points);
group.addShape("path", {
attrs: {
path,
lineWidth: 12,
stroke: edgeError
? "rgba(245,34,45,0.05)"
: "rgba(47,84,235,0.05)",
opacity: 0,
zIndex: 0
},
className: "line-bg"
});
const keyShape = group.addShape("path", {
attrs: {
path,
lineWidth: 1,
stroke: edgeError ? "#FF7875" : "rgba(0,0,0,0.25)",
zIndex: 1,
lineAppendWidth: 12
},
edgeError: !!edgeError
});
/* 连接线的中间点 */
const centerPoint = {
x: startPoint.x + (endPoint.x - startPoint.x) / 2,
y: startPoint.y + (endPoint.y - startPoint.y) / 2
};
const textRect = group.addShape("rect", {
attrs: {
fill: "#FFF1F0",
radius: 2,
cursor: "pointer",
opacity: 1
},
/* sofarouter 需要 class以便控制 显示隐藏*/
className: SOFAROUTER_RECT_CLASS
});
const text = group.addShape("text", {
attrs: {
text: "Sofarouter",
x: 0,
y: 0,
fontSize: 12,
textAlign: "left",
textBaseline: "middle",
fill: "#F5222D",
opacity: 1
},
/* sofarouter 需要 class以便控制 显示隐藏*/
className: SOFAROUTER_TEXT_CLASS
});
const textBBox = text.getBBox();
/* text 的位置 */
text.attr({
x: centerPoint.x - textBBox.width / 2,
y: centerPoint.y
});
/* text 的框框 */
textRect.attr({
x: centerPoint.x - textBBox.width / 2 - 4,
y: centerPoint.y - textBBox.height / 2 - 5,
width: textBBox.width + 8,
height: textBBox.height + 10
});
return keyShape;
},
/* 操作 线 的背景色显示隐藏 */
afterDraw: (cfg, group) => {
/* 背景色 */
const lineBG = group.get("children")[0]; // 顺序根据 draw 时确定
/* 线条 */
const line = group.get("children")[1];
line.on("mouseenter", () => {
lineBG.attr("opacity", "1");
/* 线条如果在没有错误的情况下,在 hover 时候,是需要变成蓝色的 */
if (!line.get("edgeError")) {
line.attr("stroke", "#2F54EB");
}
graph.get("canvas").draw();
});
line.on("mouseleave", () => {
lineBG.attr("opacity", "0");
if (!line.get("edgeError")) {
line.attr("stroke", "rgba(0,0,0,0.25)");
}
graph.get("canvas").draw();
});
},
setState: (name, value, item) => {
const group = item.getContainer();
const childrens = group.get("children");
graph.setAutoPaint(true);
if (name === "emptiness") {
if (value) {
childrens.forEach(shape => {
if (shape.get("className") === "line-bg") {
return;
}
shape.attr("opacity", 0.4);
});
} else {
childrens.forEach(shape => {
if (shape.get("className") === "line-bg") {
return;
}
shape.attr("opacity", 1);
});
}
}
graph.setAutoPaint(true);
},
update: null
},
"cubic-horizontal"
);
G6.registerBehavior("three-finger-drag-canvas", {
getEvents() {
return {
"canvas:dragstart": "onDragStart",
"canvas:drag": "onDrag",
"canvas:dragend": "onDragEnd"
};
},
onDragStart: ev => {
ev.preventDefault();
this.dragDx = ev.x;
this.dragDy = ev.y;
},
onDrag: ev => {
ev.preventDefault();
translate(this.dragDx - ev.x, this.dragDy - ev.y);
},
onDragEnd: ev => {
ev.preventDefault();
}
});
G6.registerBehavior("double-finger-drag-canvas", {
getEvents() {
return {
wheel: "onWheel"
};
},
onWheel: ev => {
if (ev.ctrlKey) {
const canvas = graph.get("canvas");
const pixelRatio = canvas.get("pixelRatio");
const point = canvas.getPointByClient(ev.clientX, ev.clientY);
let ratio = graph.getZoom();
if (ev.wheelDelta > 0) {
ratio = ratio + ratio * 0.05;
} else {
ratio = ratio - ratio * 0.05;
}
graph.zoomTo(ratio, {
x: point.x / pixelRatio,
y: point.y / pixelRatio
});
} else {
const x = ev.deltaX || ev.movementX;
const y = ev.deltaY || ev.movementY;
translate(x, y);
}
ev.preventDefault();
}
});
/*G6.registerBehavior("tooltip", {
getEvents() {
return {
"node:mousemove": "onMouseMove",
"node:mouseeleave": "onMouseLeave"
};
},
onMouseMove: ev => {
const {
tooltip: { visible }
} = this.state;
const tooltip = ev.target.get("tooltip");
const group = ev.item.get("group");
if (tooltip && !visible) {
const bbox = ev.target.getBBox();
const matrix = group.getMatrix();
const zoom = graph.getZoom();
this.showTooltip(
tooltip,
matrix[6] + bbox.x + bbox.width / 2,
matrix[7] + bbox.y - TIP_HEIGHT / zoom
);
}
if (!tooltip && visible) {
this.closeTooptip();
}
},
onMouseLeave: () => {
const {
tooltip: { visible }
} = this.state;
if (visible) {
this.closeTooptip();
}
}
});*/
const minimap = new Minimap({
size: [184, 124],
className: "minimap",
type: 'delegate'
});
let selectedItem;
graph = new G6.TreeGraph({
container: "mountNode",
width: CANVAS_WIDTH,
height: CANVAS_HEIGHT,
plugins: [minimap],
modes: {
default: [
{
type: "collapse-expand",
shouldUpdate: function shouldUpdate(e) {
/* 点击 node 禁止展开收缩 */
if (e.target.get("className") !== "collapse-icon") {
return false;
}
return true;
},
onChange: function onChange(item, collapsed) {
selectedItem = item;
const icon = item.get("group").findByClassName("collapse-icon");
if (collapsed) {
icon.attr("symbol", EXPAND_ICON);
} else {
icon.attr("symbol", COLLAPSE_ICON);
}
},
animate: {
callback: () => {
debugger;
graph.focusItem(selectedItem);
}
}
},
"double-finger-drag-canvas",
"three-finger-drag-canvas",
"tooltip",
{
type: "drag-canvas",
shouldUpdate: function shouldUpdate() {
return false;
},
shouldEnd: function shouldUpdate() {
return false;
}
}
]
},
defaultNode: {
shape: TREE_NODE,
anchorPoints: [[0, 0.5], [1, 0.5]]
},
defaultEdge: {
shape: "tree-edge"
},
edgeStyle: {
default: {
stroke: "#A3B1BF"
}
},
layout: data => {
const result = Hierarchy.compactBox(data, {
direction: "LR",
getId: function getId(d) {
return d.id;
},
getWidth: () => {
return 243;
},
getVGap: function getVGap() {
return 24;
},
getHGap: function getHGap() {
return 50;
}
});
return result;
}
});
function strLen(str) {
var len = 0;
for (var i = 0; i < str.length; i ++) {
if(str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) {
len ++;
} else {
len += 2;
}
}
return len;
}
function fittingString (c, str, maxWidth) {
const width = strLen(str) * 8;
const ellipsis = "…";
if (width > maxWidth) {
const actualLen = Math.floor((maxWidth - 10) / 8);
c.attr('text', str.substring(0, actualLen) + ellipsis);
c._cfg.tooltip = str;
}
};
function translate(x, y) {
// graph.translate(-x, -y);
let moveX = x;
let moveY = y;
const containerWidth = graph.get("width");
const containerHeight = graph.get("height");
/* 获得当前偏移量*/
const group = graph.get("group");
const bbox = group.getBBox();
const leftTopPoint = graph.getCanvasByPoint(bbox.minX, bbox.minY);
const rightBottomPoint = graph.getCanvasByPoint(bbox.maxX, bbox.maxY);
/* 如果 x 轴在区域内不允许左右超过100 */
if (x < 0 && leftTopPoint.x - x > LIMIT_OVERFLOW_WIDTH) {
moveX = 0;
}
if (
x > 0 &&
rightBottomPoint.x - x < containerWidth - LIMIT_OVERFLOW_WIDTH
) {
moveX = 0;
}
if (y < 0 && leftTopPoint.y - y > LIMIT_OVERFLOW_HEIGHT) {
moveY = 0;
}
if (
y > 0 &&
rightBottomPoint.y - y < containerHeight - LIMIT_OVERFLOW_HEIGHT
) {
moveY = 0;
}
graph.translate(-moveX, -moveY);
};
function fitView() {
const group = graph.get("group");
const width = graph.get("width"); // 视窗的宽度
const height = graph.get("height"); // 视窗的高度
group.resetMatrix();
// 内容
const bbox = group.getBBox();
// 视窗中间
const viewCenter = {
x: width / 2,
y: height / 2
};
/* 内容的中点 */
const groupCenter = {
x: bbox.x + bbox.width / 2,
y: bbox.y + bbox.height / 2
};
graph.translate(-bbox.x + 16, viewCenter.y - groupCenter.y);
};
function zoomTo(ratio, center) {
const width = graph.get("width"); // 视窗的宽度
const height = graph.get("height"); // 视窗的高度
const viewCenter = {
x: width / 2,
y: height / 2
};
graph.zoomTo(ratio, center || viewCenter);
};
function formatData(data) {
const recursiveTraverse = (node, level = 0) => {
// TODO 贤月 目前 keyInfo 没有
const appName = "testappName";
const keyInfo = "testkeyinfo";
const ip = "111.22.33.44";
const targetNode = {
id: node.key + "",
rpcId: node.rpcId,
level,
type: node.appName === "USER" ? "root" : node.type,
name: appName,
keyInfo: keyInfo || "-",
ip,
nodeError: false,
edgeError: false,
sofarouter: true,
sofarouterError: true,
asyn: false,
origin: node
};
if (node.children) {
targetNode.children = [];
node.children.forEach(item => {
targetNode.children.push(recursiveTraverse(item, level + 1));
});
}
return targetNode;
};
const result = recursiveTraverse(data);
return result;
}
graph.on('beforepaint', () => {
const topLeft = graph.getPointByCanvas(0, 0);
const bottomRight = graph.getPointByCanvas(1000, 600);
graph.getNodes().forEach(node => {
const model = node.getModel();
if (model.x < topLeft.x - 200 || model.x > bottomRight.x || model.y < topLeft.y || model.y > bottomRight.y) {
node.getContainer().hide();
} else {
node.getContainer().show();
}
});
const edges = graph.getEdges();
edges.forEach(edge => {
const sourceNode = edge.get('sourceNode');
const targetNode = edge.get('targetNode');
if (!sourceNode.get('visible') && !targetNode.get('visible')) {
edge.hide();
} else {
edge.show();
}
})
});
$.getJSON('./assets/sourceData.json', data => {
data = formatData(data);
graph.data(data);
graph.render();
graph.fitView();
});
</script>
</body>
</html>