https://g6.antv.vision/zh/docs/api/Plugins#tooltip
效果:点击蓝色部分,显示蓝色部分内容(内容超出范围)
主要代码:
全部配置代码:
const itemHeight = 16;
const topHeight = 30;
registerBehavior(“dice-er-scroll”, {
getDefaultCfg() {
return {
multiple: true,
};
},
getEvents() {
return {
itemHeight: 16,
click: “click”,
};
},
click(e) {
const { graph } = this;
const { y } = e;
const item = e.item;
const shape = e.shape;
if (!item) {
return;
}
const model = item.getModel();
if (shape.get("name") === "collapse") {
graph.updateItem(item, {
collapsed: true,
size: [240, 50],
});
setTimeout(() => graph.layout(), 100);
} else if (shape.get("name") === "expand") {
graph.updateItem(item, {
collapsed: false,
size: [240, 180],
});
setTimeout(() => graph.layout(), 100);
}
},
});
registerEdge(“dice-er-edge”, {
draw(cfg, group) {
const edge = group.cfg.item;
const sourceNode = edge.getSource().getModel();
const targetNode = edge.getTarget().getModel();
const sourceIndex = sourceNode.attrs.findIndex((e) => e.key === cfg.sourceKey);
const sourceStartIndex = sourceNode.startIndex || 0;
let sourceY = 15;
if (!sourceNode.collapsed && sourceIndex > sourceStartIndex - 1) {
sourceY = topHeight + (sourceIndex - sourceStartIndex + 0.5) * itemHeight;
// 字段需要滚动条时设置
// sourceY = Math.min(sourceY, 300);
}
const targetIndex = targetNode.attrs.findIndex((e) => e.key === cfg.targetKey);
const targetStartIndex = targetNode.startIndex || 0;
let targetY = 15;
if (!targetNode.collapsed && targetIndex > targetStartIndex - 1) {
targetY = (targetIndex - targetStartIndex + 0.5) * itemHeight + topHeight;
// 字段需要滚动条时设置
// targetY = Math.min(targetY, 300);
}
const startPoint = {
...cfg.startPoint,
};
const endPoint = {
...cfg.endPoint,
};
startPoint.y = startPoint.y + sourceY;
endPoint.y = endPoint.y + targetY;
let shape;
if (sourceNode.id !== targetNode.id) {
shape = group.addShape("path", {
attrs: {
stroke: "#5B8FF9",
path: [
["M", startPoint.x, startPoint.y],
[
"C",
endPoint.x / 3 + (2 / 3) * startPoint.x,
startPoint.y,
endPoint.x / 3 + (2 / 3) * startPoint.x,
endPoint.y,
endPoint.x,
endPoint.y,
],
],
endArrow: true,
},
name: "path-shape",
});
} else if (!sourceNode.collapsed) {
let gap = Math.abs((startPoint.y - endPoint.y) / 3);
if (startPoint["index"] === 1) {
gap = -gap;
}
shape = group.addShape("path", {
attrs: {
stroke: "#5B8FF9",
path: [
["M", startPoint.x, startPoint.y],
[
"C",
startPoint.x - gap,
startPoint.y,
startPoint.x - gap,
endPoint.y,
startPoint.x,
endPoint.y,
],
],
endArrow: true,
},
name: "path-shape",
});
}
return shape;
},
afterDraw(cfg, group) {
const labelCfg = cfg.labelCfg || {};
const edge = group.cfg.item;
const sourceNode = edge.getSource().getModel();
const targetNode = edge.getTarget().getModel();
if (sourceNode.collapsed && targetNode.collapsed) {
return;
}
const path = group.find((element) => element.get(“name”) === “path-shape”);
const labelStyle = Util.getLabelPosition(path, 0.5, 0, 0, true);
const label = group.addShape("text", {
attrs: {
...labelStyle,
text: cfg.label || "",
fill: "#000",
textAlign: "center",
stroke: "#fff",
lineWidth: 1,
},
});
label.rotateAtStart(labelStyle.rotate);
},
});
registerNode(“dice-er-box”, {
draw(cfg, group) {
const boxStyle = {
stroke: “#096DD9”,
radius: 4,
};
const width = 200;
let height;
const length = cfg.attrs.length + 1;
height = length * itemHeight + topHeight;
const { attrs = [], startIndex = 0, selectedIndex, collapsed, icon } = cfg;
const list = attrs;
const afterList = attrs;
const offsetY = (0.5 - (startIndex % 1)) * itemHeight + topHeight;
// 渲染表名
group.addShape("rect", {
attrs: {
fill: boxStyle.stroke,
height: topHeight,
width,
radius: [boxStyle.radius, boxStyle.radius, 0, 0],
},
draggable: true,
});
let fontLeft = 12;
if (icon && icon.show !== false) {
group.addShape("image", {
attrs: {
x: 8,
y: 8,
height: 16,
width: 16,
...icon,
},
});
fontLeft += 18;
}
group.addShape("text", {
attrs: {
y: 22,
x: fontLeft,
fill: "#fff",
text: cfg.label,
fontSize: 12,
fontWeight: 500,
},
});
// 灰色区域
group.addShape("rect", {
attrs: {
x: 0,
y: collapsed ? topHeight : height - 15,
height: 15,
width,
fill: "#eee",
radius: [0, 0, boxStyle.radius, boxStyle.radius],
cursor: "pointer",
},
name: collapsed ? "expand" : "collapse",
});
// "+" 、"-" 号
group.addShape("text", {
attrs: {
x: width / 2 - 6,
y: collapsed ? topHeight + itemHeight : height,
text: collapsed ? "+" : "-",
width,
fill: "#000",
radius: [0, 0, boxStyle.radius, boxStyle.radius],
cursor: "pointer",
},
name: collapsed ? "expand" : "collapse",
});
const keyshape = group.addShape("rect", {
attrs: {
x: 0,
y: 0,
width,
height: collapsed ? 45 : height,
...boxStyle,
},
draggable: true,
});
if (collapsed) {
return keyshape;
}
const listContainer = group.addGroup({});
listContainer.setClip({
type: "rect",
attrs: {
x: -8,
y: topHeight,
width: width + 16,
height: height - topHeight - itemHeight,
},
});
listContainer.addShape({
type: "rect",
attrs: {
x: 1,
y: topHeight,
width: width - 2,
height: 300 - topHeight,
fill: "#fff",
},
draggable: true,
});
if (afterList) {
afterList.forEach((e, i) => {
const isSelected = Math.floor(startIndex) + i === Number(selectedIndex);
let { key = "", type } = e;
// 字段名
const label = type;
listContainer.addShape("rect", {
attrs: {
x: 1,
y: i * itemHeight - itemHeight / 2 + offsetY,
width: width - 4,
height: itemHeight,
radius: 2,
lineWidth: 1,
cursor: "pointer",
stroke: "#096dd9",
lineWidth: 0.2,
},
name: `item-${Math.floor(startIndex) + i}-content`,
draggable: true,
});
if (!cfg.hideDot) {
listContainer.addShape("circle", {
attrs: {
x: 0,
y: i * itemHeight + offsetY,
r: 3,
stroke: boxStyle.stroke,
fill: "white",
radius: 2,
lineWidth: 1,
cursor: "pointer",
},
});
listContainer.addShape("circle", {
attrs: {
x: width,
y: i * itemHeight + offsetY,
r: 3,
stroke: boxStyle.stroke,
fill: "white",
radius: 2,
lineWidth: 1,
cursor: "pointer",
},
});
}
listContainer.addShape("text", {
attrs: {
x: 12,
y: i * itemHeight + offsetY + 6,
text: label,
fontSize: 12,
fill: "#000",
fontFamily:
"Avenir,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol",
full: e,
fontWeight: isSelected ? 500 : 100,
cursor: "pointer",
},
// name: `item-${Math.floor(startIndex) + i}`,
// name: `${key}-${label}`,
// name: key,
name: cfg.label + "-" + key,
});
});
}
return keyshape;
},
getAnchorPoints() {
return [
[0, 0],
[1, 0],
];
},
});
const dataTransform = (data) => {
const nodes = [];
const edges = [];
data.map((node) => {
nodes.push({
…node,
});
if (node.attrs) {
node.attrs.forEach((attr) => {
if (attr.relation) {
attr.relation.forEach((relation) => {
edges.push({
source: node.id,
target: relation.nodeId,
sourceKey: attr.key,
targetKey: relation.key,
label: relation.label,
});
});
}
});
}
});
return {
nodes,
edges,
};
};
const tooltip = new G6.Tooltip({
getContent(e) {
return e.item.getModel().label;
},
trigger: ‘click’,
itemTypes: [‘node’],
shouldBegin: (evt) => {
return evt.shape.get(‘type’) === ‘text’ && !evt.shape?.get(‘name’)
},
})
const toolbar = new G6.ToolBar();
const container = document.getElementById(“container”);
const width = container.scrollWidth;
const height = (container.scrollHeight || 500) - 20;
let graph = new G6.Graph({
container,
width,
height,
defaultNode: {
size: [240, 180],
type: “dice-er-box”,
color: “#5B8FF9”,
style: {
fill: “#9EC9FF”,
lineWidth: 3,
},
labelCfg: {
style: {
fill: “red”,
fontSize: 20,
},
},
},
defaultEdge: {
type: “dice-er-edge”,
style: {
stroke: “#e2e2e2”,
lineWidth: 4,
endArrow: true,
},
},
plugins: [tooltip, toolbar],
// plugins: [toolbar],
modes: {
// default: [“dice-er-scroll”, “zoom-canvas”, “drag-node”, “drag-canvas”],
default: [“dice-er-scroll”, “drag-node”, “drag-canvas”],
},
layout: {
type: “dagre”,
rankdir: “LR”,
align: “UL”,
controlPoints: true,
nodesepFunc: () => 0.2,
ranksepFunc: () => 0.5,
},
animate: true,
});
graph.data(dataTransform(rawData));
graph.render();