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

本文介绍了一个基于JointJS的图表绘制项目实例,详细展示了如何利用JointJS创建可交互的流程图,包括节点和连线的定制、事件监听等功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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>

 

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

 

 跨浏览器,可兼容IE7--IE10, FireFox, Chrome, Opera等几大内核的浏览器,且不需要浏览器再加装任何控件。  多系统兼容性、可移植性:由于只包括前台UI,因此二次开发者可很方便将本插件用在任何一种需要流程图的B/S系统应用上,流程图的详细实现逻辑完全交于后台程序开发者自己实现;对于后台,只要能返回/接收能被本插件解析的JSON格式数据即可.所以本插件可用于不同的服务器语言建立的后台上.  跨领域:流程图设计器不止用在电信领域,在其它需要IT进行技术支持的领域中都有重大作用.  以下从纯技术实现层面具体描述:  页面顶部栏、左边侧边栏均可自定义;  当左边的侧边栏设为不显示时,为只读状态,此时的视图区可当作是一个查看器而非编辑器。  侧边工具栏除了基本和一些流程节点按钮外,还自定义新的节点按钮,自定义节点都可以有自有的图标、类型名称,定义后在使用可可在工作区内增加这些自定义节点。  顶部栏可显示流程图数据组的标题,也可提供一些常用操作按钮。  顶部栏的按钮,除了撤销、重做按钮外,其余按钮均可自定义点击事件。  可画直线、折线;折线还可以左右/上下移动其中段。  具有区域划分功能,能让用户更直观地了解哪些节点及其相互间的转换,是属于何种自定义区域内的。  具有标注功能,用橙红色标注某个结点或者转换线,一般用在展示流程进度时。  能直接双击结点、连线、分组区域中的文字进行编辑  在对结点、连线、分组区域的各种编辑操作,如新增/删除/修改名称/重设样式或大小/移动/标注时,均可捕捉到事件,并触发自定义事件,如果自定义事件执行的方法返回FALSE,则会阻止操作。  具有操作事务序列控制功能,在工作区内的各种有效操作都能记录到一个栈中,然后可以进行撤销(undo())或重做(redo()),像典型的C/S软件一样。  0.4版中,加入了只导出在初始载入后被编辑的流程图中,只作了增删改等变更的元素,这样可用于用户快速存储,只保存本次变更过的内容,不用重新保存整个流程。  0.5版中,结点的样式不再受到原有程序的限制,所有样式均默认为淡蓝色长方形;如果要指定为圆形,可在初始化时定义结点类型为”原有类型”+” round”;如果要指定为复合结点,则可在初始化时定义结点类型为”原有类型”+” mix”。”原有类型”+” myType”:myType可为自己写的一种特殊样式类.  0.6版中,修正了一些BUG,改善了用户操作体验,并增加在可编辑状态下时,能用键盘上DELETE按键对元素进行删除功能。  0.7版中,修正了一些BUG,增加了连线变更要连的起始结点或结束结点的功能。  0.8版,取消原来的拟物化页面,变成如今的扁平化页面,并且支持主要位置的颜色自定义功能(如果想沿用原来老版本中的拟物化页面,只需保留原来的GooFlow.css文件即可);修正0.7版中的画线BUG。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值