业务需求
如上图所示,前端需要根据后端返回的数据动态形成树状结构图,以完成流程图展示。经过几天的了解和学习,最终决定使用G6-AntV。下面就如何使用及使用中遇到的问题进行总结。
代码实现
<template>
<div id="container" class="container"></div>
</template>
<script>
// 引入antv-G6
import G6 from "@antv/g6";
// G6的配置项,注册新的边
G6.registerEdge("flow-line", {
// 绘制后的附加操作
draw(cfg, group) {
// 边两端与起始节点和结束节点的交点;
const startPoint = cfg.startPoint;
const endPoint = cfg.endPoint;
// 边的配置
const { style } = cfg;
const shape = group.addShape("path", {
attrs: {
stroke: style.stroke, // 边框的样式
endArrow: style.endArrow, // 结束箭头
// 路径
path: [
["M", startPoint.x, startPoint.y],
["L", startPoint.x, (startPoint.y + endPoint.y) / 2],
["L", endPoint.x, (startPoint.y + endPoint.y) / 2],
["L", endPoint.x, endPoint.y]
]
}
});
return shape;
}
});
// 注册新的node,注意:不要继承其他节点,否则更新时会走继承的draw方法
// 这里是个坑,当时查阅官方文档的时候,registerNode注册DomNode的示例中传了第三个参数;
// 作为一名合格的CV,必然是原样复制了,但是在点击事件中,更新DomNode时会无效;在求助大佬后,大佬看了看
// 源码,说不要传第三个参数就好了;下班回家我也仔细看了看,简单来说就是传了第三个参数就会走传的参数类的
// 绘制方法,不会走自己写的绘制方法。
// const lastItem = graph.find("node", node => {
// return node.get("model").isSelected == true;
// });
// graph.updateItem(lastItem, { isSelected: false });
// const item = graph.findById(id);
// graph.updateItem(item, { isSelected: true });
G6.registerNode("dom-node", {
draw: (cfg, group) => {
return group.addShape("dom", {
attrs: {
width: cfg.size[0],
height: cfg.size[1],
// 传入 DOM 的 html
html: `
<div
class="catalog-node ${cfg.isSelected && !cfg.dataIsDeleted ? "selected-catalog" : ""}
${cfg.dataIsDeleted ? "is-deleted" : ""}"
style="
background-color: #fff;
border: 1px solid #C4C7D6;
border-radius: ${(cfg.size[1] - 5) / 2}px;
width: ${cfg.size[0] - 5}px;
height: ${cfg.size[1] - 5}px;
display: flex;
cursor:pointer;
user-select:none;"
id="${cfg.id}">
<div
class="left ${cfg.isSelected ? "is-selected" : ""}"
style="
height: 100%;
line-height:100%;
width: 45%;
color: #606266;
font-size: 8px;
padding: 15px 5px 10px 28px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap">
${cfg.name}
</div>
<div class="line" style="height: 100%; width: 1px; background-color: #c4c7d6"></div>
<div class="right" style="
font-size:4px;
color:#C4C7D6;
padding-left:10px;
padding-top:6px;">
<p style="color:#606266;">${cfg.handleType}</p>
<p style="color:#8D929B;">${cfg.utime}</p>
<span class='icon ${cfg.handleType == "数据清洗" ? "icon-clean" : ""}'></span>
</div>
</div>
`
}
});
}
});
// 默认边的颜色 末尾箭头
const defaultEdgeStyle = {
stroke: "#C4C7D6"
};
// 默认布局
// compactBox 紧凑树布局
// 从根节点开始,同一深度的节点在同一层,并且布局时会将节点大小考虑进去。
const defaultLayout = {
type: "compactBox", // 布局类型树
direction: "TB", // TB 根节点在上,往下布局
getId: function getId(d) {
// 节点 id 的回调函数
return d.id;
},
getHeight: function getHeight() {
// 节点高度的回调函数
return 16;
},
getWidth: function getWidth() {
// 节点宽度的回调函数
return 16;
},
getVGap: function getVGap() {
// 节点纵向间距的回调函数
return 40;
},
getHGap: function getHGap() {
// 节点横向间距的回调函数
return 150;
}
};
let graph;
// vue部分
export default {
name: "catalogInShow",
data() {
return {
directoryManagement: this.datasetInfo.directoryManagement,
dataForShow: {},
selectedId: ""
};
},
methods: {
G6init(data) {
if (!this.directoryManagement) return;
if (typeof window !== "undefined") {
window.onresize = () => {
if (!graph || graph.get("destroyed")) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight);
};
}
// 获取容器
const container = document.getElementById("container");
// 获取容器的宽高
const width = container.clientWidth;
const height = container.clientHeight || 500;
// Graph 是 G6 图表的载体-实例化
graph = new G6.TreeGraph({
container: "container", // 图的 DOM 容器
width,
height,
linkCenter: true, // 指定边是否连入节点的中心
modes: {
// 交互模式
// default 模式中包含点击选中节点行为和拖拽画布行为;
default: ["drag-canvas", "zoom-canvas"]
},
// 使用 Dom node 的时候需要使用 svg 的渲染形式 **重要
renderer: "svg",
// 默认状态下节点的配置
defaultNode: {
type: "dom-node",
size: [280, 50]
},
// 默认状态下边的配置,
defaultEdge: {
type: "flow-line",
style: defaultEdgeStyle
},
// 布局配置项
layout: defaultLayout
});
graph.data(data);
// 根据提供的数据渲染视图。
graph.render();
graph.stopAnimate();
graph.fitView();
},
changeDataAdaptFront(params, selectedId) {
params.id = params.id + "";
if (!selectedId) {
params.isSelected = params.parentId ? false : true;
} else {
params.isSelected = params.id == selectedId ? true : false;
}
let handleType = null;
switch (params.handleType) {
case 0:
handleType = "数据预处理";
break;
case 1:
handleType = "数据清洗";
break;
case 2:
handleType = "数据校验";
break;
case 3:
handleType = "数据增强";
break;
case 4:
handleType = "数据均衡";
break;
default:
return;
}
params.handleType = handleType;
if (params.children && params.children.length !== 0) {
params.children.forEach(item => {
this.changeDataAdaptFront(item);
});
}
}
},
props: {
datasetInfo: {}
},
created() {
if (!this.directoryManagement) return;
this.selectedId = this.directoryManagement.id;
this.$emit("getSelectedId", this.selectedId);
this.dataForShow = JSON.parse(JSON.stringify(this.directoryManagement).replace(/childs/g, "children"));
this.dataForShow = JSON.parse(JSON.stringify(this.dataForShow).replace(/type/g, "handleType"));
this.changeDataAdaptFront(this.dataForShow);
// console.log(this.dataForShow);
},
mounted() {
let data = this.dataForShow;
this.changeDataAdaptFront(data);
this.G6init(data);
let container = document.getElementById("container");
container.onclick = e => {
let id = "";
let catalogNode;
function findCatalogNode(target) {
if (target.nodeName == "svg") return;
if ([].indexOf.call(target.classList, "catalog-node") !== -1) {
catalogNode = target;
return;
} else {
findCatalogNode(target.parentNode);
}
}
findCatalogNode(e.target);
// 如果已删除,则点击事件无效
if (catalogNode && catalogNode.classList.contains("is-deleted")) return;
if (!catalogNode || !catalogNode.id) return;
id = catalogNode.id ? catalogNode.id : "";
this.selectedId = id;
this.$emit("getSelectedId", this.selectedId);
// 获取当前isSelected为true的节点,并改为false
const lastItem = graph.find("node", node => {
return node.get("model").isSelected == true;
});
graph.updateItem(lastItem, { isSelected: false });
// 根据id获取当前点击的元素,并更新其样式
const item = graph.findById(id);
graph.updateItem(item, { isSelected: true });
};
}
};
</script>
<style lang="scss">
#container {
height: 700px;
width: 100%;
.catalog-node {
.left {
position: relative;
&::before {
content: "";
position: absolute;
background: url("~@workbench/assets/images/datasets/Data-analysis-platform-contents @2x.png") center center
no-repeat;
background-size: 100%;
width: 12px;
height: 12px;
left: 10px;
top: 50%;
transform: translateY(-60%);
}
}
.is-selected {
&::before {
content: "";
position: absolute;
background: url("~@workbench/assets/images/datasets/Data-analysis-platform-contents-click@2x.png") center center
no-repeat;
background-size: 100%;
width: 12px;
height: 12px;
left: 10px;
top: 50%;
transform: translateY(-60%);
}
}
.icon-clean {
&::after {
content: "";
position: absolute;
background: url("~@workbench/assets/images/datasets/Data-analysis-platform-label@2x.png") center center no-repeat;
background-size: 100%;
width: 12px;
height: 12px;
right: 15px;
top: 50%;
transform: translateY(-60%);
}
}
}
.selected-catalog {
border: 1px solid #475aff !important;
.line {
background-color: #475aff !important;
}
}
// 删除状态下样式
.is-deleted {
border: 1px dashed #eee !important;
.line {
background-color: #eee !important;
}
}
}
.show-whole-title {
position: fixed;
line-height: 20px;
font-size: 12px;
padding: 2px 5px;
color: #fff;
background: rgba(37, 37, 54, 0.6);
border-radius: 4px;
z-index: 100;
text-align: justify;
}
</style>
最终效果:
以上就是全部代码了,借鉴了网上好多大佬的文章,在此表示感谢!不足的地方还望朋友们能够指出!感激不尽!