【4】基于svg,WEB在线流程图Joint.js组件开发

2,137 阅读4分钟
JointJS是一个开源的、基于JavaScript的图表库,可以用来创建静态图表、完全可交互的图表、 WEB在线流程图、应用程序
jointJS是一个基于svg的图形化工具库,在画布上画出支持拖动的svg图形,而且可以导出JSON,也能通过JSON配置导入直接生成图形。 可以基于jointJS开发出流程图、UML图以及图表等。由于jointJS是基于svg的,因此对svg有一定的了解会对jointJS的理解和使用有较大帮助。 由于jointJS是基于backbone的,因此有view(视图)和model(模型)的概念。 使用jointJS需要引入jQuery、backbone、lodash以及jointJS的包,可以通过script标签引入,也可以通过npm安装。

因为业务需求,主要需要满足:

1、初始化流程图

2、两种节点,一种“一进一出”,另外一种“一进多出”(未完美实现)

3、不同状态显示不同颜色连线(暂未实现)

4、可编辑,节点跟Link需要自定义

一开始,把官网上的初级、中级教程过一遍,大致了解他的构建流程(比如画布构建,Model、Graph、Link等概念,还有工具属性以及触发方法等),挑战才刚刚开始...

比较坑的一个点是:Rappid(商业收费,好贵)是高度集成了Joint.js的很多常见的业务场景。但是,Joint.js的demo,列举的例子,隐含了Rappid的混淆引用,就是还是基于Rappid进行展示,但是工具类外部属性(暂时发现)是经过混淆的(可能是为了引导大家去使用收费的Rappid)。文档也不够清晰,可能是我还没熟悉这种模式(套路)

<template>
  <div>
      <div id="toolbar">
          <button class="btn add-question" @click='addAudioNode'>Add Audio-Node</button>
          <button class="btn add-answer" @click='addVideoNode'>Add Video-Node</button>
          <button class="btn preview-dialog">Preview Dialog</button>
          <button class="btn code-snippet">Code Snippet</button>
          <button class="btn clear">Clear Canvas</button>
          <button class="btn load-example">Load Example</button>
      </div>
      <div id="myholder" @click="click_joint"></div>
  </div>
</template>
<script>
require('../assets/css/toolbar.css')
import joint from 'jointjs'
import $ from 'jquery'

export default {
name: 'App',
data: function () {
  return {
    active: true,
    graph:null,
    rectAudio:null,
    rectVideo:null,
  }
},
mounted: function () {
  this.init()
},
methods: {
  init(){
    // 先创建joint graph 对象
    var graph = new joint.dia.Graph;
    this.graph = graph;


    //设定画布基本信息
    var paper = new joint.dia.Paper({
        el: document.getElementById('myholder'),
        width: 900,
        height: 700,
        model: graph,
        gridSize:1,
        //默认link样式
        defaultLink:  function(elementView, magnet) {
          return new joint.shapes.standard.Link;
        },
        //限制画布范围内
        restrictTranslate: true,
    });

    //连接节点
    var connect = function(source, sourcePort, target, targetPort) {
      var link = new joint.shapes.standard.Link({
          source: {
              id: source.id,
              port: sourcePort
          },
          target: {
              id: target.id,
              port: targetPort,
          },     
      });
      //link工具类-编辑
      var verticesTool = new joint.linkTools.Vertices();
      var segmentsTool = new joint.linkTools.Segments();
      var sourceArrowheadTool = new joint.linkTools.SourceArrowhead();
      var targetArrowheadTool = new joint.linkTools.TargetArrowhead();
      var sourceAnchorTool = new joint.linkTools.SourceAnchor();
      var targetAnchorTool = new joint.linkTools.TargetAnchor();
      var boundaryTool = new joint.linkTools.Boundary();
      var toolsView = new joint.dia.ToolsView({
          init:[],
          tools: [
              verticesTool, segmentsTool,
              sourceArrowheadTool, targetArrowheadTool,
              sourceAnchorTool, targetAnchorTool,
              boundaryTool, 
          ]
      });
      link.addTo(graph).parent();
      var linkView = link.findView(paper);
      //link添加可编辑
      linkView.addTools(toolsView);
      linkView.hideTools();
    };

      //音频节点
      var rect = new joint.shapes.devs.Model({
        position: { x: 100, y: 30 },
        size: { width: 100, height: 40 },
        inPorts: ['in'],
        outPorts: ['out'],
        ports: {
            groups: {
                'in': {
                    attrs: {
                        '.port-body': {
                            fill: '#16A085',
                            magnet: 'passive',
                            refDx:'-5',
                            r:'5'
                        },
                    }
                },
                'out': {
                    attrs: {
                        '.port-body': {
                            fill: '#E74C3C',
                            refDx:'5',
                            r:'5'
                        }
                    }
                }
            }
        },
        attrs: {
            '.label': { text: 'voice', 'ref-x': .5, 'ref-y': .2, },
            rect: { fill: '#2ECC71' }
        }
      });

      //视频节点
      var rect_video = new joint.shapes.devs.Model({
        position: { x: 200, y: 100 },
        size: { width: 100, height: 40 },
        inPorts: ['in'],
        outPorts: ['out'],
        ports: {
            groups: {
                'in': {
                    attrs: {
                        '.port-body': {
                            fill: '#16A085',
                            magnet: 'passive',
                            refDx:'-5',
                            r:'5'
                        },
                    }
                },
                'out': {
                    attrs: {
                        '.port-body': {
                            fill: '#E74C3C',
                            refDx:'5',
                            r:'5'
                        }
                    }
                }
            }
        },
        attrs: {
            '.label': { text: 'video', 'ref-x': .5, 'ref-y': .2, },
            rect: { fill: '#FFF' }
        }
      });

      this.rectAudio = rect;
      this.rectVideo = rect_video;

      rect.addTo(graph);
      rect_video.addTo(graph);

      //克隆一个节点
      let rect2 = rect.clone();
      rect2.translate(300,0);
      rect2.attr('label/text','node2');
      rect2.addTo(graph);

      //克隆一个节点
      let rect3 = rect.clone();
      rect3.translate(500,0);
      rect3.attr('label/text','node2');
      rect3.addTo(graph);

      //链接
      graph.addCells([rect, rect2, rect3,]);
      connect(rect, 'in', rect2, 'out');
      connect(rect2, 'in', rect3, 'out');

      //改变element postion 
      //建议加上防抖,以实现实时保存位置
      graph.on('change:position', function(cell) {
          var center = cell.getBBox().center();
          var label = center.toString();
          cell.attr('label/text', label);
      });

      //双击element事件
      paper.on('element:pointerdblclick', function(cellView) {
          //保存数据
          console.log('data',graph.toJSON())
      });

      paper.on('link:mouseenter', function(linkView) {
        console.log('111')
        linkView.showTools();
      });

      paper.on('link:mouseleave', function(linkView) {
        console.log('222')
        linkView.hideTools();
      });

  },
  //克隆一个音频节点
  addAudioNode(){
    let rect3 = this.rectAudio.clone();
    rect3.translate(Math.random()*100+200,0);
    rect3.attr('label/text',Math.random()*100+200);
    rect3.addTo(this.graph);
  },
  //克隆一个视频节点
  addVideoNode(){
    let rect3 = this.rectVideo.clone();
    rect3.translate(Math.random()*100+50,0);
    rect3.attr('label/text',Math.random()*100+50);
    rect3.addTo(this.graph);
  },
  click_joint(){
    console.log('click_joint');
  },
},

}
</script>
<style>
#myholder{
  width: 900px;
  height: 700px;
  margin: 0 auto;
  margin-top: 25px;
  border: 1px solid #d3d3d3;
}
</style>

迁移回本地环境,还没配置好,实现图稍后上传...如有问题可以跟我说吼,谢谢!恳请各位大大指正