JointJS JavaScript流程图绘制框架解析


Posted in Javascript onAugust 15, 2019

JointJS:JavaScript 流程图绘制框架

最近调研了js画流程图的框架,最后选择了Joint。配合上 dagre 可以画出像模像样的流程图。

JointJS 简介

JointJS 是一个开源前端框架,支持绘制各种各样的流程图、工作流图等。Rappid 是 Joint 的商业版,提供了一些更强的插件。JointJS 的特点有下面几条,摘自官网:

  • 能够实时地渲染上百(或者上千)个元素和连接
  • 支持多种形状(矩形、圆、文本、图像、路径等)
  • 高度事件驱动,用户可自定义任何发生在 paper 下的事件响应
  • 元素间连接简单
  • 可定制的连接和关系图
  • 连接平滑(基于贝塞尔插值 bezier interpolation)& 智能路径选择
  • 基于 SVG 的可定制、可编程的图形渲染
  • NodeJS 支持
  • 通过 JSON 进行序列化和反序列化

总之 JoingJS 是一款很强的流程图制作框架,开源版本已经足够日常使用了。

一些常用地址:

API: https://resources.jointjs.com/docs/jointjs/v1.1/joint.html

Tutorials: https://resources.jointjs.com/tutorial

JointJS Hello world

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/jointjs/2.1.0/joint.css" rel="external nofollow" rel="external nofollow" rel="external nofollow" />
</head>
<body>
  <!-- content -->
  <div id="myholder"></div>
  <!-- dependencies 通过CDN加载依赖-->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jointjs/2.1.0/joint.js"></script>
  <!-- code -->
  <script type="text/javascript">
    var graph = new joint.dia.Graph;
    var paper = new joint.dia.Paper({
      el: document.getElementById('myholder'),
      model: graph,
      width: 600,
      height: 100,
      gridSize: 1
    });
    var rect = new joint.shapes.standard.Rectangle();
    rect.position(100, 30);
    rect.resize(100, 40);
    rect.attr({
      body: {
        fill: 'blue'
      },
      label: {
        text: 'Hello',
        fill: 'white'
      }
    });
    rect.addTo(graph);

    var rect2 = rect.clone();
    rect2.translate(300, 0);
    rect2.attr('label/text', 'World!');
    rect2.addTo(graph);
    var link = new joint.shapes.standard.Link();
    link.source(rect);
    link.target(rect2);
    link.addTo(graph);
  </script>
</body>
</html>

hello world 代码没什么好说的。要注意这里的图形并没有自动排版,而是通过移动第二个 rect 实现的手动排版。

JointJS JavaScript流程图绘制框架解析

前后端分离架构

既然支持 NodeJs,那就可以把繁重的图形绘制任务交给服务器,再通过 JSON 序列化在 HTTP 上传输对象,这样减轻客户端的压力。

NodeJS 后端

var express = require('express');
var joint = require('jointjs');

var app = express();

function get_graph(){
  var graph = new joint.dia.Graph();

  var rect = new joint.shapes.standard.Rectangle();
  rect.position(100, 30);
  rect.resize(100, 40);
  rect.attr({
    body: {
      fill: 'blue'
    },
    label: {
      text: 'Hello',
      fill: 'white'
    }
  });
  rect.addTo(graph);

  var rect2 = rect.clone();
  rect2.translate(300, 0);
  rect2.attr('label/text', 'World!');
  rect2.addTo(graph);

  var link = new joint.shapes.standard.Link();
  link.source(rect);
  link.target(rect2);
  link.addTo(graph);

  return graph.toJSON();
}

app.all('*', function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "X-Requested-With");
  res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
  next();
});

app.get('/graph', function(req, res){
  console.log('[+] send graph json to client')
  res.send(get_graph());
});
app.listen(8071);

HTML 前端

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/jointjs/2.1.0/joint.css" rel="external nofollow" rel="external nofollow" rel="external nofollow" />
</head>
<body>
  <!-- content -->
  <div id="myholder"></div>

  <!-- dependencies 通过CDN加载依赖-->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jointjs/2.1.0/joint.js"></script>

  <!-- code -->
  <script type="text/javascript">
    var graph = new joint.dia.Graph;
    var paper = new joint.dia.Paper({
      el: document.getElementById('myholder'),
      model: graph,
      width: 600,
      height: 100,
      gridSize: 1
    });

    $.get('http://192.168.237.128:8071/graph', function(data, statue){
      graph.fromJSON(data);
    });
  </script>
</body>
</html>

其他

自动布局 Automatic layout

JointJS 内置了插件进行自动排版,原理是调用 Dagre 库。官方 api 中有样例。

使用方法:

var graphBBox = joint.layout.DirectedGraph.layout(graph, {
nodeSep: 50,
edgeSep: 80,
rankDir: "TB"
});

配置参数 注释
nodeSep 相同rank的邻接节点的距离
edgeSep 相同rank的邻接边的距离
rankSep 不同 rank 元素之间的距离
rankDir 布局方向 ( "TB" (top-to-bottom) / "BT" (bottom-to-top) / "LR" (left-to-right) / "RL"(right-to-left))
marginX number of pixels to use as a margin around the left and right of the graph.
marginY number of pixels to use as a margin around the top and bottom of the graph.
ranker 排序算法。 Possible values: 'network-simplex' (default), 'tight-tree' or 'longest-path'.
resizeClusters set to false if you don't want parent elements to stretch in order to fit all their embedded children. Default is true.
clusterPadding A gap between the parent element and the boundary of its embedded children. It could be a number or an object e.g. { left: 10, right: 10, top: 30, bottom: 10 }. It defaults to 10.
setPosition(element, position) a function that will be used to set the position of elements at the end of the layout. This is useful if you don't want to use the default element.set('position', position) but want to set the position in an animated fashion via transitions.
setVertices(link, vertices) If set to true the layout will adjust the links by setting their vertices. It defaults to false. If the option is defined as a function it will be used to set the vertices of links at the end of the layout. This is useful if you don't want to use the default link.set('vertices', vertices) but want to set the vertices in an animated fashion via transitions.
setLabels(link, labelPosition, points) If set to true the layout will adjust the labels by setting their position. It defaults to false. If the option is defined as a function it will be used to set the labels of links at the end of the layout. Note: Only the first label (link.label(0);) is positioned by the layout.
dagre 默认情况下,dagre 应该在全局命名空间当中,不过你也可以当作参数传进去
graphlib 默认情况下,graphlib 应该在全局命名空间当中,不过你也可以当作参数传进去

我们来试一下。NodeJS 后端

var express = require('express');
var joint = require('jointjs');
var dagre = require('dagre')
var graphlib = require('graphlib');
var app = express();
function get_graph(){
  var graph = new joint.dia.Graph();
  var rect = new joint.shapes.standard.Rectangle();
  rect.position(100, 30);
  rect.resize(100, 40);
  rect.attr({
    body: {
      fill: 'blue'
    },
    label: {
      text: 'Hello',
      fill: 'white'
    }
  });
  rect.addTo(graph);
  var rect2 = rect.clone();
  rect2.translate(300, 0);
  rect2.attr('label/text', 'World!');
  rect2.addTo(graph);
  for(var i=0; i<10; i++){
    var cir = new joint.shapes.standard.Circle();
    cir.resize(100, 100);
    cir.position(10, 10);
    cir.attr('root/title', 'joint.shapes.standard.Circle');
    cir.attr('label/text', 'Circle' + i);
    cir.attr('body/fill', 'lightblue');
    cir.addTo(graph);
    var ln = new joint.shapes.standard.Link();
    ln.source(cir);
    ln.target(rect2);
    ln.addTo(graph);
  }
  var link = new joint.shapes.standard.Link();
  link.source(rect);
  link.target(rect2);
  link.addTo(graph);
  //auto layout
  joint.layout.DirectedGraph.layout(graph, {
    nodeSep: 50,
    edgeSep: 50,
    rankDir: "TB",
    dagre: dagre,
    graphlib: graphlib
  });
  return graph.toJSON();
}
app.all('*', function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "X-Requested-With");
  res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
  next();
});
app.get('/graph', function(req, res){
  console.log('[+] send graph json to client')
  res.send(get_graph());
});
app.listen(8071);

HTML 前端

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/jointjs/2.1.0/joint.css" rel="external nofollow" rel="external nofollow" rel="external nofollow" />
</head>
<body>
  <!-- content -->
  <div id="myholder"></div>

  <!-- dependencies 通过CDN加载依赖-->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jointjs/2.1.0/joint.js"></script>

  <!-- code -->
  <script type="text/javascript">
    var graph = new joint.dia.Graph;
    var paper = new joint.dia.Paper({
      el: document.getElementById('myholder'),
      model: graph,
      width: 2000,
      height: 2000,
      gridSize: 1
    });
    $.get('http://192.168.237.128:8071/graph', function(data, statue){
      graph.fromJSON(data);
    });
  </script>
</body>
</html>

结果:

JointJS JavaScript流程图绘制框架解析

使用 HTML 定制元素

流程图中的每个点,也就是是元素,都可以自定义,直接编写 html 代码能添加按钮、输入框、代码块等。

我的一个代码块 demo,搭配 highlight.js 可以达到类似 IDA 控制流图的效果。这个 feature 可玩度很高。

joint.shapes.BBL = {};
joint.shapes.BBL.Element = joint.shapes.basic.Rect.extend({
  defaults: joint.util.deepSupplement({
    type: 'BBL.Element',
    attrs: {
      rect: { stroke: 'none', 'fill-opacity': 0 }
    }
  }, joint.shapes.basic.Rect.prototype.defaults)
});

// Create a custom view for that element that displays an HTML div above it.
// -------------------------------------------------------------------------
joint.shapes.BBL.ElementView = joint.dia.ElementView.extend({
  template: [
    '<div class="html-element" data-collapse>',
    '<label></label><br/>',
    '<div class="hljs"><pre><code></code></pre></span></div>',
    '</div>'
  ].join(''),

  initialize: function() {
    _.bindAll(this, 'updateBox');
    joint.dia.ElementView.prototype.initialize.apply(this, arguments);

    this.$box = $(_.template(this.template)());
    // Prevent paper from handling pointerdown.
    this.$box.find('h3').on('mousedown click', function(evt) {
      evt.stopPropagation();
    });

    // Update the box position whenever the underlying model changes.
    this.model.on('change', this.updateBox, this);
    // Remove the box when the model gets removed from the graph.
    this.model.on('remove', this.removeBox, this);

    this.updateBox();
  },
  render: function() {
    joint.dia.ElementView.prototype.render.apply(this, arguments);
    this.paper.$el.prepend(this.$box);
    this.updateBox();
    return this;
  },
  updateBox: function() {
  // Set the position and dimension of the box so that it covers the JointJS element.
    var bbox = this.model.getBBox();
    // Example of updating the HTML with a data stored in the cell model.
    this.$box.find('label').text(this.model.get('label'));
    this.$box.find('code').html(this.model.get('code'));
    var color = this.model.get('color');
    this.$box.css({
      width: bbox.width,
      height: bbox.height,
      left: bbox.x,
      top: bbox.y,
      background: color,
      "border-color": color
    });
  },
  removeBox: function(evt) {
    this.$box.remove();
  }
});

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
学习YUI.Ext基础第一天
Mar 10 Javascript
简单的Jquery全选功能
Nov 07 Javascript
关闭浏览器输入框自动补齐 兼容IE,FF,Chrome等主流浏览器
Feb 11 Javascript
JS实现文字链接感应鼠标淡入淡出改变颜色的方法
Feb 26 Javascript
jquery ztree实现树的搜索功能
Feb 25 Javascript
深入理解vue.js中的v-if和v-show
Jun 22 Javascript
jQuery实现全选、反选和不选功能
Aug 16 jQuery
angularjs 缓存的使用详解
Mar 19 Javascript
Vue项目引发的「过滤器」使用教程
Mar 12 Javascript
es6 filter() 数组过滤方法总结
Apr 03 Javascript
vue 强制组件重新渲染(重置)的两种方案
Oct 29 Javascript
vue-i18n实现中英文切换的方法
Jul 06 Javascript
基于vue写一个全局Message组件的实现
Aug 15 #Javascript
vue element-ui table组件动态生成表头和数据并修改单元格格式 父子组件通信
Aug 15 #Javascript
解决vuex刷新状态初始化的方法实现
Aug 15 #Javascript
超详细的5个Shell脚本实例分享(值得收藏)
Aug 15 #Javascript
koa2+vue实现登陆及登录状态判断
Aug 15 #Javascript
原生js实现的移动端可拖动进度条插件功能详解
Aug 15 #Javascript
快速对接payjq的个人微信支付接口过程解析
Aug 15 #Javascript
You might like
全国FM电台频率大全 - 30 宁夏回族自治区
2020/03/11 无线电
Zend Framework教程之请求对象的封装Zend_Controller_Request实例详解
2016/03/07 PHP
thinkphp验证码的实现(form、ajax实现验证)
2016/07/28 PHP
Thinkphp3.2实用篇之计算型验证码示例
2017/02/09 PHP
Yii2实现UploadedFile上传文件示例
2017/02/15 PHP
Laravel如何同时连接多个数据库详解
2019/08/13 PHP
Laravel框架源码解析之入口文件原理分析
2020/05/14 PHP
JavaScript replace(rgExp,fn)正则替换的用法
2010/03/04 Javascript
JQGrid的用法解析(列编辑,添加行,删除行)
2013/11/08 Javascript
javascript通过className来获取元素的简单示例代码
2014/01/10 Javascript
一个JavaScript的求爱小特效
2014/05/09 Javascript
浅谈javascript中的call、apply、bind
2016/03/06 Javascript
sea.js常用的api简易文档
2016/11/15 Javascript
老生常谈的跨域处理
2017/01/11 Javascript
全站最详细的Vuex教程
2018/04/13 Javascript
微信小程序实现弹出菜单动画
2019/06/21 Javascript
Vue + Scss 动态切换主题颜色实现换肤的示例代码
2020/04/27 Javascript
JavaScript 面向对象程序设计详解【类的创建、实例对象、构造函数、原型等】
2020/05/12 Javascript
JavaScript组合设计模式--改进引入案例分析
2020/05/23 Javascript
Python使用django搭建web开发环境
2017/06/09 Python
Python图形绘制操作之正弦曲线实现方法分析
2017/12/25 Python
Python3.5 Pandas模块缺失值处理和层次索引实例详解
2019/04/23 Python
详解python路径拼接os.path.join()函数的用法
2019/10/09 Python
python中的RSA加密与解密实例解析
2019/11/18 Python
tensorflow 获取checkpoint中的变量列表实例
2020/02/11 Python
Keras 使用 Lambda层详解
2020/06/10 Python
Python3爬虫里关于Splash负载均衡配置详解
2020/07/10 Python
利用Python pandas对Excel进行合并的方法示例
2020/11/04 Python
简单介绍HTML5中的文件导入
2015/05/08 HTML / CSS
遗体告别仪式答谢词
2014/01/23 职场文书
工地门卫岗位职责范本
2014/07/01 职场文书
人事经理岗位职责范本
2014/08/04 职场文书
个人四风问题对照检查材料
2014/10/01 职场文书
2015年纪检监察工作总结
2015/04/08 职场文书
《领导干部从政道德启示录》学习心得体会
2016/01/20 职场文书
MySQL学习之基础操作总结
2022/03/19 MySQL