背景
在项目中要实现拓扑图,翻看尝试了一些,比如使用echarts来实现拓扑图,但是好像结果不尽人意,然后就去用g6了,下面是一些使用的分享。
官网 g6.antv.antgroup.com/manual/intr…
安装&使用
项目用的技术栈是vue,还是先npm install 再 import,官网有线上引入的地址。后面会有基础的使用,前面先分享一下解决的案例,希望能帮到大家!
npm install --save @antv/g6
// 组件中引入
import G6 from "@antv/g6";
遇到的问题和解决分享
需求:要求边之间变虚线,并且要能运动起来,还要有目标箭头的指向,查阅官网,有边动画的效果:g6.antv.antgroup.com/examples/sc…
但是这个比较尴尬的是,如果你设置了箭头,那么这个箭头也会有动画,不太符合我们的效果图,如下:
那么我们就要考虑怎么去重新设置这个箭头,一开始想着网上可能会有类似的解决方案,然后各种找之后发现好像确实没有,ok,那就发散一下,g6提供了自定义边的效果,那我们是不是可以自定义一个箭头,然后让箭头的指向指向目标node,ok,尝试一下。
这是官网提供的虚线动画的效果:
//这是官网提供的虚线动画的效果
const lineDash = [12, 24]; // 12 为实线段长度,24 为虚线段长度
G6.registerEdge(
"line-dash",
{
afterDraw(cfg, group) {
// get the first shape in the group, it is the edge's path here
const shape = group.get("children")[0];
let index = 0;
// Define the animation
shape.animate(
() => {
index++;
const res = {
lineDash,
lineDashOffset: -index * 1, // 根据 index 动态调整 lineDashOffset
};
// returns the modified configurations here, lineDash and lineDashOffset here
return res;
},
{
repeat: true, // whether executes the animation repeatly
duration: 3000, // the duration for executing once
}
);
},
},
"line" // extend the built-in edge 'line'
);
我们自己去绘制一个箭头,然后画一个三角形,指向我们的目标节点:
function drawArrow(edge) {
const group = edge.getContainer(); // 获取边的容器组,用于添加或删除图形元素
const keyShape = edge.getKeyShape(); // 获取边的关键图形,即边的路径。
const endPoint = keyShape.getPoint(1); // 获取边的终点坐标。
const startPoint = keyShape.getPoint(0); // 获取边的起点坐标。
const isWarnEdge = edge.getModel().isSpecialEdge; // 检查边是否为特殊边(例如告警边)。
const angle = Math.atan2(endPoint.y - startPoint.y, endPoint.x - startPoint.x); // 计算边的角度,用于确定箭头方向。
const arrowLength = 10; // 箭头的长度。
const arrowWidth = 6; // 箭头的宽度。
// 定义箭头的路径,形成一个三角形。
const arrowPath = [
["M", endPoint.x, endPoint.y], // 移动到箭头的起点(边的终点)。
[
"L",
endPoint.x - arrowLength * Math.cos(angle) + arrowWidth * Math.sin(angle),
endPoint.y - arrowLength * Math.sin(angle) - arrowWidth * Math.cos(angle),
], // 绘制箭头的第一条边。
[
"L",
endPoint.x - arrowLength * Math.cos(angle) - arrowWidth * Math.sin(angle),
endPoint.y - arrowLength * Math.sin(angle) + arrowWidth * Math.cos(angle),
], // 绘制箭头的第二条边。
["Z"], // 关闭路径,形成一个三角形。
];
// 添加箭头图形到边的容器组中。
group.addShape("path", {
attrs: {
path: arrowPath, // 设置箭头的路径。
stroke: isWarnEdge ? "rgb(247, 59, 10)" : "#a0a0a0", // 设置箭头的边框颜色,根据是否为特殊边来决定颜色。
fill: isWarnEdge ? "rgb(247, 59, 10)" : "#a0a0a0", // 设置箭头的填充颜色,根据是否为特殊边来决定颜色。
},
name: "arrow", // 设置图形的名称为 "arrow"。
});
}
上面我们画了自定义箭头,使其可以正确的指向目标节点,那让其什么时候渲染呢,那就是等我们init初始化完成之后,再将我们的箭头给画上去,g6提供了对应的方法(afterrender),这里加个loading,因为两者的渲染时间是不一样的,等整个完整的图全部画出来之后,再展示出来更合理
// 等图渲染完成之后,展示动画和画三角形,最后将图展示出来
graph.on("afterrender", () => {
const edges = graph.getEdges();
edges.forEach((edge) => {
// drawArrow(edge);
});
loading.value = false;
});
OK,遇到了下一个问题,让画出来的箭头,要跟随着我们拖动拓扑图上的节点移动而移动,看看官网,有对node节点拖动的监听函数,那就好办了,对拖动的节点,找到其对应的边,重新绘制箭头就好了,查到了两个函数,怎样去选择使用,根据自己的需求来定就好了,我选了drag,感觉这样更符合视觉效果一些
// 拖动过程中持续触发,频率高。会跟随拖动逐步改变
graph.on("node:drag", (evt) => {
const node = evt.item;
const edges = graph.getEdges();
edges.forEach((edge) => {
const model = edge.getModel();
if (model.source === node.getID() || model.target === node.getID()) {
drawArrow(edge);
}
});
});
// 拖动结束时触发,仅触发一次。只会在拖动结束后更新一次
// graph.on("node:dragend", () => {
// graph.getEdges().forEach((edge) => {
// drawArrow(edge);
// }
这样其实就可以实现那个需求了,一些解决这个需求的思路,我也是第一次接触g6,所以记录一下。
认识一下g6的基本数据结构
看下准备的node,edge简单数据解释,具体还有别的属性,大家有需要查阅的移步官网吧,小建议,多看几遍文档,温故知新。
const data = {
// 点集
nodes: [
{
id: 'node1', // 节点的唯一标识
shape: "image", // 节点类型是图片
size: [30, 30], // 图片节点的尺寸
label: "名称", // 图片节点的描述
style: {...} // 图片节点的样式
x: 100, // 节点位置的 x 值
y: 200, // 可选,节点位置的 y 值
},
{
id: 'node2', // 节点的唯一标识
shape: "image", // 节点类型是图片
size: [30, 30], // 图片节点的尺寸
label: "名称", // 图片节点的描述
style: {...} // 图片节点的样式
x: 300, // 节点位置的 x 值
y: 200, // 节点位置的 y 值
},
],
// 边集
edges: [
{
source: 'node1', // 起始点 id
target: 'node2', // 目标点 id
label: 'test' // 边上的文案
labelStyle: { // 边的样式
cursor: "pointer",
},
style: {
stroke: "red", // 填充边的颜色
},
},
],
};
然后就是初始化整个图:
const graph = new G6.Graph({
container: container.value, // ref图的实例
width: width,
height: height,
plugins: [toolbar], // 内部提供的toobar,就是放大镜,放大类似功能的插件,可以自己配置,
toolbar: { // 对 toolbar的数据进行自定义配置/改写
menus: {
undo: false, // 隐藏撤销按钮
redo: false, // 隐藏重做按钮
// zoomOut: null, //
// zoomIn: null, //
// 其他工具保持默认配置或自定义配置
},
},
layout: {
type: "force", // 布局类型,
preventOverlap: true, //防止点重叠
// 防碰撞必须设置nodeSize或size,否则不生效,由于节点的size设置了40,虽然节点不碰撞了,但是节点之间的距离很近,label几乎都挤在一起,所以又重新设置了大一点的nodeSize,这样效果会好很多
nodeSize: 50,
linkDistance: 220,
// nodeSpacing: 10, // 节点间距
},
// 默认节点的属性合集
defaultNode: {
type: "image",
},
// 默认边的属性合集
defaultEdge: {
type: "line-dash",
style: {
lineWidth: 2, // 线宽
stroke: "#a0a0a0", // 填充色
cursor: "pointer",
lineDash: [24, 24], // 虚线实线 线段比
// endArrow: true, // 末尾添加箭头
// endArrow: {
// // d--> distance 离终点的距离
// path: G6.Arrow.triangle(10, 10, 15), // 使用内置箭头路径函数,参数为箭头的 宽度、长度、偏移量(默认为 0,与 d 对应)
// d: 15,
// },
// startArrow: {
// path: G6.Arrow.vee(0, 0, 15), // 使用内置箭头路径函数,参数为箭头的 宽度、长度、偏移量(默认为 0,与 d 对应)
// d: 15,
// },
},
labelCfg: {
// autoRotate: true, // 边上的标签文本根据边的方向旋转
style: {
cursor: "pointer",
},
},
},
modes: {
default: ["drag-canvas", "drag-node"], // 允许拖拽画布、放缩画布、拖拽节点
},
// fitCenter: true, //是否居中展示
// fitView: true, //是否自适应屏幕大小
});
// 填充数据
graph.data(data);
// 渲染
graph.render();
下面是一些自定义节点/事件/插件
G6.registerNode 自定义节点,
G6.registerEdge 自定义边
const tooltip = new G6.Tooltip 可以自定义tooltip
const toolbar = new G6.ToolBar 可以自定义toolbar
.....
节点事件汇总,如下
- node:click 鼠标左键单击节点时触发
- node:dblclick 鼠标双击左键节点时触发,同时会触发两次 node:click
- node:mouseenter 鼠标移入节点时触发
- node:mousemove 鼠标在节点内部移到时不断触发
- node:mouseout 鼠标移出节点后触发
- node:mouseover 鼠标移入节点上方时触发
- node:mouseleave 鼠标移出节点时触发
- node:mousedown 鼠标按钮在节点上按下(左键或者右键)时触发
- node:mouseup 节点上按下的鼠标按钮被释放弹起时触发
- node:dragstart 当节点开始被拖拽时触发,此事件作用在被拖拽节点上
- node:drag 当节点在拖动过程中时触发,此事件作用于被拖拽节点上
- node:dragend 当拖拽完成后触发,此事件作用在被拖拽节点上
- node:dragenter 当拖拽节点进入目标元素的时候触发,此事件作用在目标元素上
- node:dragleave 当拖拽节点离开目标元素的时候触发,此事件作用在目标元素上
- node:dragover 当拖拽节点在另一目标元素上移动时触发,此事件作用在目标元素上
- node:drop 被拖拽的节点在目标元素上同时鼠标放开触发,此事件作用在目标元素上
- node:contextmenu 用户在节点上右击鼠标时触发并打开右键菜单
参考博主的地址:https://juejin.cn/post/7063248501441822728?searchId=20240625193716533EA8A403A294DFA767
边事件其实也差不多类似,就不详细赘述了,有需求的移步就行。
总结
其实对于提供的布局类型,感觉也要花费一些时间,因为有些布局并不能很好地对大家所需要的关系图进行展示。
从初步认识g6到实现效果,难点主要集中在自定义边和点上,没有很详细对官方文档的东西做注释,主要分享一个案例给大家,希望大家看完能有帮助吧!