这个知识点可能比较小众,先上一下例子吧:
官方是有相关的例子的,点击去查看。
下面给大家省去查看官方例子的过程,直接告诉你如何写:
<script setup>
import G6 from '@antv/g6';
const {
Util,
registerBehavior,
registerEdge,
registerNode
} = G6;
let group = null;
const isInBBox = (point, bbox) => {
const { x,y } = point;
const { minX, minY, maxX, maxY } = bbox;
return x < maxX && x > minX && y > minY && y < maxY;
};
const itemHeight = 30;
// 滚动
registerBehavior("dice-er-scroll", {
getDefaultCfg() {
return {
multiple: true,
};
},
getEvents() {
return {
itemHeight: 50,
wheel: "scorll",
click: "click",
"node:mousemove": "move",
};
},
scorll(e) {
e.preventDefault();
const nodes = graph.getNodes().filter((n) => {
const bbox = n.getBBox();
return isInBBox(graph.getPointByClient(e.clientX, e.clientY), bbox);
});
if (nodes) {
nodes.forEach((node) => {
const model = node.getModel();
if (model.attrs.length < 5) {
return;
}
const idx = model.startIndex || 0;
let startX = model.startX || 0.5;
let startIndex = idx + e.deltaY * 0.02;
startX -= e.deltaX;
if (startIndex < 0) {
startIndex = 0;
}
if (startX > 0) {
startX = 0;
}
if (startIndex > model.attrs.length - 1) {
startIndex = model.attrs.length - 1;
}
graph.update(node, {
startIndex,
startX,
});
});
}
},
click(e) {
const item = e.item;
const shape = e.shape;
if (!item) {
return;
}
if (shape.get("name") === "collapse") {
graph.updateItem(item, {
collapsed: true,
size: [300, 0],
});
} else if (shape.get("name") === "expand") {
graph.updateItem(item, {
collapsed: false,
size: [300, 0],
});
}
},
move(e) {
const name = e.shape.get("name");
const item = e.item;
if (name && name.startsWith("item")) {
graph.updateItem(item, {
selectedIndex: Number(name.split("-")[1]),
});
} else {
graph.updateItem(item, {
selectedIndex: NaN,
});
}
},
});
// 连线
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.name === cfg.sourceKey
);
const sourceStartIndex = sourceNode.startIndex || 0;
let sourceY = 15;
if (!sourceNode.collapsed && sourceIndex > sourceStartIndex - 1) {
sourceY = 30 + (sourceIndex - sourceStartIndex + 0.5) * 30;
sourceY = Math.min(sourceY, 200);
}
const targetIndex = targetNode.attrs.findIndex(
(e) => e.name === cfg.targetKey
);
const targetStartIndex = targetNode.startIndex || 0;
let targetY = 15;
if (!targetNode.collapsed && targetIndex > targetStartIndex - 1) {
targetY = (targetIndex - targetStartIndex + 0.5) * 30 + 30;
targetY = Math.min(targetY, 200);
}
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,
],
],
cursor: 'pointer',
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,
],
],
cursor: 'pointer',
endArrow: true,
},
name: "path-shape",
});
}
shape.on('click', () => textFunc(cfg));
return shape;
},
afterDraw(cfg, group) {
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,
cursor: 'pointer'
},
});
label.rotateAtStart(labelStyle.rotate);
label.on('click', () => textFunc(cfg));
},
});
// 盒子
registerNode("dice-er-box", {
draw(cfg, group) {
const boxStyle = {
stroke: "#096DD9",
radius: 4,
};
const width = 240;
// 每个盒子list总高度
const height = 200;
const itemCount = 10;
const {
attrs = [],
startIndex = 0,
selectedIndex,
collapsed,
icon,
} = cfg;
const list = attrs;
const afterList = list.slice(
Math.floor(startIndex),
Math.floor(startIndex + itemCount - 1)
);
const offsetY = (0.5 - (startIndex % 1)) * itemHeight + 30;
group.addShape("rect", {
attrs: {
fill: boxStyle.stroke,
height: 30,
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;
}
// 每个矩形框的title
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 ? 30 : height,
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 ? 30 : height) + 12,
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 + 15,
...boxStyle,
},
draggable: true,
});
if (collapsed) {
return keyshape;
}
const listContainer = group.addGroup({});
listContainer.setClip({
type: "rect",
attrs: {
x: -8,
y: 30,
width: width + 16,
height: height - 30,
},
});
listContainer.addShape({
type: "rect",
attrs: {
x: 1,
y: 30,
width: width - 2,
height: height - 30,
fill: "#fff",
},
draggable: true,
});
if (list.length > itemCount) {
const barStyle = {
width: 4,
padding: 0,
boxStyle: {
stroke: "#00000022",
},
innerStyle: {
fill: "#00000022",
},
};
listContainer.addShape("rect", {
attrs: {
y: 30,
x: width - barStyle.padding - barStyle.width,
width: barStyle.width,
height: height - 30,
...barStyle.boxStyle,
},
});
const indexHeight =
afterList.length > itemCount ?
(afterList.length / list.length) * height :
10;
listContainer.addShape("rect", {
attrs: {
y: 30 +
barStyle.padding +
(startIndex / list.length) * (height - 30),
x: width - barStyle.padding - barStyle.width,
width: barStyle.width,
height: Math.min(height, indexHeight),
...barStyle.innerStyle,
},
});
}
if (afterList) {
afterList.forEach((e, i) => {
const isSelected = Math.floor(startIndex) + i === Number(selectedIndex);
const { count, name } = e;
const label = `${name}(${count})` //key.length > 26 ? key.slice(0, 24) + "..." : key;
listContainer.addShape("rect", {
attrs: {
x: 1,
y: i * itemHeight - itemHeight / 2 + offsetY,
width: width - 4,
height: itemHeight,
radius: 2,
lineWidth: 1,
cursor: "pointer",
},
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",
},
});
}
const textClick = 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}`,
});
textClick.on('click', () => textFunc(e));
});
}
return keyshape;
},
getAnchorPoints() {
return [
[0, 0],
[1, 0],
];
},
});
const textFunc = (vals) => {
console.log(vals)
}
const dataTransform = (data) => {
const nodes = [];
const edges = [];
if(data.length){
data.map((item) => {
nodes.push({
...item
});
if (item.attrs) {
item.attrs.forEach((attr) => {
if (attr.relation && attr.relation.length) {
attr.relation.forEach((relation) => {
edges.push({
source: item.id, //矩形框的唯一值
sourceKey: attr.name, // 矩形框里每条数据的唯一值
target: relation.node, // 指向的矩形框的唯一值(relation.node就是node.id)
targetKey: relation.name, // 指向的矩形框里每条数据的唯一值(relation.name就是attr.name)
label: relation.count,
items: relation
});
});
}
});
}
});
}
return {
nodes,
edges,
};
const getList = () => {
获取数据的方法(传入你的参数).then(res=>{
obj.datas = res.code === 200 ? res.data : [];
graph.changeData(dataTransform(obj.datas));
});
}
onMounted(() => {
if(document.getElementById('excavateContainer')){
const container = document.getElementById('excavateContainer');
const width = container.scrollWidth;
const height = container.scrollHeight || 300;
if(graph){
graph.destroy();
}
graph = null
graph = new G6.Graph({
container: 'excavateContainer',
width,
height,
defaultNode: {
size: [240, 0],
type: 'dice-er-box',
color: '#5B8FF9',
style: {
fill: '#9EC9FF',
lineWidth: 3,
},
labelCfg: {
style: {
fill: 'black',
fontSize: 20,
},
},
},
defaultEdge: {
type: 'dice-er-edge',
style: {
stroke: '#e2e2e2',
lineWidth: 4,
endArrow: true,
},
},
modes: {
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(obj.datas));
graph.render();
}
});
</script>
<template>
<div id="excavateContainer" />
</template>