JointJS JavaScript流程图绘制框架解析

 更新时间:2019年08月15日 14:09:39   作者:Helica  
这篇文章主要介绍了JointJS JavaScript流程图绘制框架解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

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 实现的手动排版。

前后端分离架构

既然支持 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>

结果:

使用 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();
  }
});

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

相关文章

  • 用js判断是否为360浏览器的实现代码

    用js判断是否为360浏览器的实现代码

    这篇文章主要介绍了用js判断是否为360浏览器的实现代码,有时候我们需要判断是否为360浏览器,包括百度联盟后台就有这样的提示需要的朋友可以参考下
    2015-01-01
  • JavaScript中document获取元素方法示例详解

    JavaScript中document获取元素方法示例详解

    这篇文章主要介绍了JavaScript中获取页面元素的几种常用方法,分别是getElementById()、getElementsByClassName()、getElementsByTagName()、querySelector()和querySelectorAll(),每种方法都有其特点和适用场景,需要的朋友可以参考下
    2025-03-03
  • JS制作图形验证码实现代码

    JS制作图形验证码实现代码

    这篇文章主要为大家详细介绍了JS制作图形验证码实现代码,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-09-09
  • JavaScript 中使用 Generator的方法

    JavaScript 中使用 Generator的方法

    Generator 是一种非常强力的语法,但它的使用并不广泛。这篇文章主要介绍了如何在 JavaScript 中使用 Generator,需要的朋友可以参考下
    2017-12-12
  • JS实现FLASH幻灯片图片切换效果的方法

    JS实现FLASH幻灯片图片切换效果的方法

    这篇文章主要介绍了JS实现FLASH幻灯片图片切换效果的方法,实例分析了javascript操作图片实现Flash幻灯效果的技巧,需要的朋友可以参考下
    2015-03-03
  • 深入浅析JS中的严格模式

    深入浅析JS中的严格模式

    严格模式就是使JS编码更加规范化的模式,消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为。下面通过代码相结合的形式给大家介绍js中的严格模式,感兴趣的朋友一起看看吧
    2018-06-06
  • JavaScript中获取鼠标位置相关属性总结

    JavaScript中获取鼠标位置相关属性总结

    这篇文章主要介绍了JavaScript中获取鼠标位置相关属性总结,本文重点在搞清楚这些属性的区别,需要的朋友可以参考下
    2014-10-10
  • Js获取当前日期时间及格式化代码

    Js获取当前日期时间及格式化代码

    这篇文章主要为大家详细介绍了Js获取当前日期时间及格式化代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-09-09
  • 基于JavaScript实现的插入排序算法分析

    基于JavaScript实现的插入排序算法分析

    这篇文章主要介绍了基于JavaScript实现的插入排序算法,结合实例形式详细分析了插入排序的原理、操作步骤及javascript相关实现技巧与注意事项,需要的朋友可以参考下
    2017-04-04
  • JavaScript性能优化总结之加载与执行

    JavaScript性能优化总结之加载与执行

    本文详细介绍了如何正确的加载和执行JavaScript代码,从而提高其在浏览器中的性能。对JavaScript学习者很有帮助,有需要的可以参考学习。
    2016-08-08

最新评论