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

摘要:
前言:要求创建OA系统的组织权限结构的流程图。它支持单击以添加串行并行节点。图表应该看起来很直观,因此dagre-d3库是合适且漂亮的。以下是我在早期研究中编写的演示;1.首先从“dagre-d3”安装依赖项importdagreD3;从“d3”导入*asd3;2.源代码描述(复制到vue项目运行。我使用了ts)

前言:需求是要做一个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.串行效果图

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

b.并行效果图

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

 4.注意

我把操作生成的流程图数据存在了缓存中,若想刷新初始化删掉缓存即可

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

还有一个重要问题,做出demo后还是不够美观,所以我改了dagre-d3库源码让线条链接更美观了(主要是让线条90度折线) ,修改node_modules中如下位置代码

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

 添加红色框中的优化代码即可

 // 节点连接线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

免责声明:文章转载自《【前端开发】流程图设计库dagre-d3前端开发教程》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇遇到double 数目过大,转String变成科学计数法什么是闭包?闭包的优缺点?下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

颠覆式前端UI开发框架:React

转自:http://www.infoq.com/cn/articles/subversion-front-end-ui-development-framework-react/ 基于HTML的前端界面开发正变得越来越复杂,其本质问题基本都可以归结于如何将来自于服务器端或者用户输入的动态数据高效的反映到复杂的用户界面上。而来自Facebook的React框...

OpenStack(7)-cinder服务部署

OpenStack Block Storage服务(Cinder)将持久存储添加到虚拟机。块存储提供用于管理卷的基础结构,并与OpenStack Compute交互以为实例提供卷。该服务还可以管理卷快照和卷类型。 块存储服务包含以下组件: cinderAPI 接受API请求,并将它们路由到cinder-volumefor操作。 cinder卷 直接...

Linux中三种SCSI target的介绍之STGT

  版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。本文链接:https://blog.csdn.net/scaleqiao/article/details/46706953 最近在做一个和scsi target相关的项目,我借着这个机会调研了一下linux中现有的scsi target方...

mockjs,json-server一起搭建前端通用的数据模拟框架教程

无论是在工作,还是在业余时间做前端开发的时候,难免出现后端团队还没完成接口的开发,而前端团队却需要实现对应的功能,不要问为什么,这是肯定存在的。本篇文章就是基于此原因而产出的。希望对有这方面的需求的同志有所帮助。 一、使用的组件包 1. mockjs:用于模拟查询结果 2. json-server:搭建模拟服务器,以及模拟CRUD的相关操作接口 二、具体的...

Android源码分析(一)-----如何快速掌握Android编译文件

一 : Android.mk文件概述 主要向编译系统指定相应的编译规则。会被解析一次或多次。因此尽量减少源码中声明变量,因为这些变量可能会被多次定义从而影响到后面的解析。这个文件的语法会把源代码组织成模块,每个模块属于下列类型之一: - APK程序:一般的Android程序,编译打包生成apk文件。 - JAVA库:java类库,编译打包生成jar包文件。...

理解 Android Build 系统

测试 前言 Android Build 系统是 Android 源码的一部分。关于如何获取 Android 源码,请参照 Android Source 官方网站 。 Android Build 系统用来编译 Android 系统,Android SDK 以及相关文档。该系统主要由 Make 文件,Shell 脚本以及 Python 脚本组成,其中最主要的是...