前言:需求是要做一个oa系统的组织权限架构流程图,支持点击添加串行&并行节点,图要看上去直观,故找到dagre-d3这个库比较适合也好看,以下是我前期调研写的demo;
1.首先安装依赖
import dagreD3 from "dagre-d3";
import * as d3 from "d3";
2.源码说明(复制到vue项目即可运行,我是用了ts)
<template> <div class="flow-chart"> <svg width="2400" height="1600" > <g /> <rect /> </svg> <div class="btn-position"> <button @click="isStrand = 1">串行</button> <button @click="isStrand = 2">并行</button> </div> </div> </template> <script> import dagreD3 from "dagre-d3"; import * as d3 from "d3"; // rect,circle,ellipse,diamond,默认值为rect export default { data () { return { isStrand: 1, // 1为串行 2位并行 list: { nodeInfos: [ { id: "node1", label: "节点1", shape: "circle", }, { id: "node2", label: "节点2", }, { id: "node3", label: "节点3", rank: 2 }, { id: "node4", label: "节点4", // rank: 2, // shape: "ellipse", // class:‘empty‘ }, { id: "node5", label: "节点5", }, { id: "node6", label: "节点6", }, { id: "node7", label: "节点7", }, { id: "node8", label: "节点8", }, { id: "node9", label: "节点9", shape: "circle", }, ], edges: [ { source: "node1", target: "node2", }, { source: "node2", target: "node3", }, { source: "node3", target: "node4", }, { source: "node4", target: "node5", }, { source: "node5", target: "node6", }, { source: "node6", target: "node7", }, { source: "node7", target: "node8", }, { source: "node8", target: "node9", } ] }, nextNode: ‘‘, gGraph: new dagreD3.graphlib.Graph().setGraph({ rankdir: ‘LR‘, //默认‘TB‘ // align: ‘DL‘, nodesep: 40, edgesep: 80, ranksep: 60, marginx: 140, marginy: 140, // ranker: ‘tight-tree‘ }) }; }, methods: { // 删除节点 removeNode (item) { this.gGraph.removeNode(item.id,); }, setNodeFun () { this.list.nodeInfos && this.list.nodeInfos.forEach((item, index) => { item.rx = item.ry = 5;//圆角 console.log(item, ‘------‘) if (item.class === ‘empty‘) { console.log(88888) this.gGraph.setNode(item.id, { style: "stroke: #ccc; ;stroke-width:0.2px", width: -19, //线条颜色 ...item, }); } else { this.gGraph.setNode(item.id, { // style: "stroke: #ccc; fill: #666;stroke-width:2px", ...item, }); } }) }, setEdgeFun () { this.list.edges.forEach(item => { this.gGraph.setEdge(item.source, item.target, { style: "stroke: #ccc; fill: none;stroke-width:2px", //线条颜色 arrowheadStyle: "fill: #ccc;stroke: #ccc", //箭头颜色 arrowhead: ‘undirected‘, // normal,vee,undirected 三种样式 labelType: ‘‘,//可以设置文本以及 html 格式,默认为文本格式 // labelpos:‘r‘, // labeloffset:‘80‘ }); }); }, renderFun () { //绘制图形 var svgAb = d3.select("svg"), innerAb = svgAb.select("g"); console.log(innerAb, ‘innerAb‘) //缩放 // var zoom = d3.zoom().on("zoom", function () { // inner.attr("transform", d3.event.transform); // }); // svg.call(zoom); var render = new dagreD3.render(); render(innerAb, this.gGraph); console.log(innerAb, ‘inner111‘) }, selectEvent () { var svg = d3.select("svg"), inner = svg.select("g"); let code; inner.selectAll("g.node").on("mousedown", e => { // e.preventDefault(); console.log(e,‘鼠标右键点击了‘) }) inner.selectAll("g.node").on("click", e => { console.log(e, ‘单击了‘) // isStrand 1为串行 2位并行 if (this.isStrand == 1) { //点击事件-串行 this.list.nodeInfos = this.list.nodeInfos.concat({ id: e + ‘11‘, label: "节点" + e + ‘11‘, }) this.list.edges = this.list.edges.map(q => { if (q.source == e) { console.log(q, ‘q------1‘) this.nextNode = q.target q.target = e + ‘11‘ } return q }) this.list.edges = this.list.edges && this.list.edges.concat({ source: e + ‘11‘, target: this.nextNode, }) console.log(this.list, ‘this.list‘) localStorage.setItem(‘list‘, JSON.stringify(this.list)) window.location.reload() } else { // 并行 // 创建空节点 this.list.nodeInfos = this.list.nodeInfos.concat({ id: e + ‘0‘, label: ‘‘, shape: "ellipse", class: ‘empty‘ }) this.list.nodeInfos = this.list.nodeInfos.concat({ id: e + ‘21‘, label: "节点" + e + ‘21‘, }, { id: e + ‘22‘, label: "节点" + e + ‘22‘, }) this.list.edges = this.list.edges.map(q => { if (q.source == e) { console.log(q, ‘q------1‘) this.nextNode = q.target q.target = e + ‘21‘ } return q }) // 空节点 this.list.edges = this.list.edges && this.list.edges.concat({ source: e + ‘0‘, target: this.nextNode, }) // end this.list.edges = this.list.edges && this.list.edges.concat({ source: e, target: e + ‘22‘, }, { source: e + ‘21‘, // target: this.nextNode, target: e + ‘0‘, }, { source: e + ‘22‘, // target: this.nextNode, target: e + ‘0‘, }) // console.log(this.list, ‘this.list‘) localStorage.setItem(‘list‘, JSON.stringify(this.list)) window.location.reload() } code = this.list.nodeInfos.filter(item => { return item.id == e; }); console.log(code, ‘12212121‘); this.setNodeFun() this.setEdgeFun() setTimeout(() => { this.renderFun() }, 1000) }); }, // 缩放 scale () { var initialScale = 0.75; svg.call( zoom.transform, d3.zoomIdentity .translate( (svg.attr("width") - g.graph().width * initialScale) / 2, 20 ) .scale(initialScale) ); svg.attr("height", g.graph().height * initialScale + 40); }, rightEvent () { var svgCanvas = document.getElementById(‘svg-canvas‘); //svg var myMenu = document.getElementById("myMenu"); //右键菜单 svgCanvas.addEventListener(‘mouseover‘, function (e) {//监听鼠标右键 e.preventDefault(); if (e.target.tagName === ‘rect‘) { myMenu.style.top = event.clientY + "px"; //获取鼠标位置 myMenu.style.left = event.clientX + "px"; myMenu.style.display = ‘block‘; //显示相应右键内容 } }) document.addEventListener("click", (event) => { myMenu.style.display = ‘none‘; }); }, // 线条汇集合并 lineMerge () { const arr = [] this.list.edges = this.list.edges.map(q => { if (arr.includes(q.target)) { this.list.nodeInfos = this.list.nodeInfos.map(j => { console.log(j, ‘j==-=-=-=‘) if (j.id == q.target) { j.class = ‘empty‘ j.shape = "ellipse" } return j }) } arr.push(q.target) return q }) } }, created () { this.list = JSON.parse(localStorage.getItem(‘list‘)) || this.list // this.lineMerge() console.log(this.list, ‘this.list-get‘) }, mounted () { this.setNodeFun() this.setEdgeFun() this.renderFun() this.selectEvent() // g.nodes().forEach(function (v) { // console.log("Node " + v + ": " + JSON.stringify(g.node(v))); // }); // g.edges().forEach(function (e) { // console.log("Edge " + e.v + " -> " + e.w + ": " + JSON.stringify(g.edge(e))); // }); } }; </script> <style lang="less"> .flow-chart { width: 2400px; height: 800px; border: solid 1px #666; } svg { font-size: 14px; } .node rect { stroke: #606266; fill: #fff; } .edgePath path { stroke: #606266; fill: #333; stroke-width: 1.5px; } .node circle { stroke: #606266; fill: #fff; stroke-width: 0.5px; } .node ellipse { fill: #606266; opacity: 0.2; stroke-width: 1px; } // .nodeNo{ // width: 3px!important; // height: 3px!important; // stroke-width: 0.5px; // } // .empty{ // width: 42px!important; // height: 42px!important; // stroke-width: 1.5px; // fill: #606266; // stroke: #606266; // } .btn-position { position: fixed; top: 20px; left: 20px; button { margin-left: 16px; } } </style>
3.效果图
a.串行效果图
b.并行效果图
4.注意
我把操作生成的流程图数据存在了缓存中,若想刷新初始化删掉缓存即可
还有一个重要问题,做出demo后还是不够美观,所以我改了dagre-d3库源码让线条链接更美观了(主要是让线条90度折线) ,修改node_modules中如下位置代码
添加红色框中的优化代码即可
// 节点连接线90度角优化 if(points.length > 0 ){ var point1 = points[0]; var point2 = points[1]; var point3 = points[2]; var stepX = point3.x - point1.x; var stepY = point3.y - point1.y; if(stepX > 0 && stepY > 0){ // point a to c && b to d if(point3.y - point2.y > 0){ point2.x = point3.x point2.y = point1.y } else { point2.x = point1.x point2.y = point3.y } }else if(stepX > 0 && stepY < 0){ // point a to b if(point3.y - point2.y == 0){ point2.x = point1.x point2.y = point3.y } else{ point2.x = point3.x point2.y = point1.y } } } // --end--
tips:还有不懂的可以加微信交流:844271163
原文:https://www.cnblogs.com/xiaohuizhang/p/14754630.html