【前端开发】流程图设计库dagre-d3前端开发教程

前言:需求是要做一个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>
      <button @click="isStrand = 3">会签</button>
    </div>
  </div>

</template>

<script>
import dagreD3 from "dagre-d3";
import * as d3 from "d3";

export default {
  data () {
    return {
      isStrand: 1, // 1为串行  2位并行 3为会签
      list: {
        nodeInfos: [  // 节点数组
          {
            id: "node1",
            label: "节点1",
            shape: "circle", // rect,circle,ellipse,diamond,默认值为rect
          },
          {
            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,
      })
    };
  },
  methods: {
    // 删除节点
    removeNode (item) {
      this.gGraph.removeNode(item.id,);
    },
    // 生成节点
    setNodeFun () {
      this.list.nodeInfos && this.list.nodeInfos.forEach((item, index) => {
        item.rx = item.ry = 5;//圆角
        if (item.class === 'empty') {
          this.gGraph.setNode(item.id, {
            style: "stroke: #ccc; ;stroke-0.2px",
             -19, //线条颜色
            ...item,
          });
        } else {
          this.gGraph.setNode(item.id, {
            //  style: "stroke: #ccc; fill: #666;stroke-2px",
            ...item,
          });
        }

      })
    },
    // 生成链接线
    setEdgeFun () {
      this.list.edges.forEach(item => {
        this.gGraph.setEdge(item.source, item.target, {
          style: "stroke: #ccc; fill: none;stroke-2px", //线条颜色
          arrowheadStyle: "fill: #ccc;stroke: #ccc", //箭头颜色
          arrowhead: 'undirected', // normal,vee,undirected 三种样式
          labelType: '',//可以设置文本以及 html 格式,默认为文本格式
        });
      });
    },
    //绘制图形
    renderFun () {
      var svgAb = d3.select("svg"),
        innerAb = svgAb.select("g");
      //缩放
      // 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);
    },
    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, k, n) => {
        // isStrand 1为串行 2为并行 3为会签(给当并行叠加并行)
        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) {
              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,
          })

          // 存储并重新渲染
          localStorage.setItem('list', JSON.stringify(this.list))
          window.location.reload()

        } else if (this.isStrand == 2) {
          // 并行

          // 添加空节点(创建空节点视觉优化链接线汇交点)
          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',
          })

          //添加并行节点链接关系1
          this.list.edges = this.list.edges.map(q => {
            if (q.source == e) {
              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,
          })
          // 添加并行节点链接关系2
          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',
          })

          // 存储并重新渲染
          localStorage.setItem('list', JSON.stringify(this.list))
          window.location.reload()

        } else {

          // 会签
          const hqStartObj = this.list.edges.filter(q => {
            return q.target == e
          })
          const hqStartId = hqStartObj[0].source
          const hqEndObj = this.list.edges.filter(q => {
            return q.source == e
          })
          const hqEndId = hqEndObj[0].target

          // 添加会签节点
          this.list.nodeInfos = this.list.nodeInfos.concat({
            id: e + '31',
            label: "节点" + e + '31',
          })

          // 添加会签链接关系
          this.list.edges = this.list.edges && this.list.edges.concat({
            source: hqStartId,
            target: e + '31',
          }, {
            source: e + '31',
            target: hqEndId,
          })

          //  存储并重新渲染
          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';
      });

    }
  },
  created () {
    this.list = JSON.parse(localStorage.getItem('list')) || this.list
  },
  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 {
   2400px;
  height: 800px;
  border: solid 1px #666;
}
svg {
  font-size: 14px;
}

.node rect {
  stroke: #606266;
  fill: #fff;
}

.edgePath path {
  stroke: #606266;
  fill: #333;
  stroke- 1.5px;
}
.node circle {
  stroke: #606266;
  fill: #fff;
  stroke- 0.5px;
}
.node ellipse {
  fill: #606266;
  opacity: 0.2;
  stroke- 1px;
}
.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