JointJS JavaScript流程圖繪制框架解析
JointJS:JavaScript 流程圖繪制框架
最近調(diào)研了js畫流程圖的框架,最后選擇了Joint。配合上 dagre 可以畫出像模像樣的流程圖。
JointJS 簡(jiǎn)介
JointJS 是一個(gè)開源前端框架,支持繪制各種各樣的流程圖、工作流圖等。Rappid 是 Joint 的商業(yè)版,提供了一些更強(qiáng)的插件。JointJS 的特點(diǎn)有下面幾條,摘自官網(wǎng):
- 能夠?qū)崟r(shí)地渲染上百(或者上千)個(gè)元素和連接
- 支持多種形狀(矩形、圓、文本、圖像、路徑等)
- 高度事件驅(qū)動(dòng),用戶可自定義任何發(fā)生在 paper 下的事件響應(yīng)
- 元素間連接簡(jiǎn)單
- 可定制的連接和關(guān)系圖
- 連接平滑(基于貝塞爾插值 bezier interpolation)& 智能路徑選擇
- 基于 SVG 的可定制、可編程的圖形渲染
- NodeJS 支持
- 通過(guò) JSON 進(jìn)行序列化和反序列化
總之 JoingJS 是一款很強(qiáng)的流程圖制作框架,開源版本已經(jīng)足夠日常使用了。
一些常用地址:
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" rel="external nofollow" rel="external nofollow" rel="external nofollow" />
</head>
<body>
<!-- content -->
<div id="myholder"></div>
<!-- dependencies 通過(guò)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 代碼沒什么好說(shuō)的。要注意這里的圖形并沒有自動(dòng)排版,而是通過(guò)移動(dòng)第二個(gè) rect 實(shí)現(xiàn)的手動(dòng)排版。

前后端分離架構(gòu)
既然支持 NodeJs,那就可以把繁重的圖形繪制任務(wù)交給服務(wù)器,再通過(guò) JSON 序列化在 HTTP 上傳輸對(duì)象,這樣減輕客戶端的壓力。
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" rel="external nofollow" rel="external nofollow" rel="external nofollow" />
</head>
<body>
<!-- content -->
<div id="myholder"></div>
<!-- dependencies 通過(guò)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>
其他
自動(dòng)布局 Automatic layout
JointJS 內(nèi)置了插件進(jìn)行自動(dòng)排版,原理是調(diào)用 Dagre 庫(kù)。官方 api 中有樣例。
使用方法:
var graphBBox = joint.layout.DirectedGraph.layout(graph, {
nodeSep: 50,
edgeSep: 80,
rankDir: "TB"
});
| 配置參數(shù) | 注釋 |
|---|---|
| nodeSep | 相同rank的鄰接節(jié)點(diǎn)的距離 |
| 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 | 默認(rèn)情況下,dagre 應(yīng)該在全局命名空間當(dāng)中,不過(guò)你也可以當(dāng)作參數(shù)傳進(jìn)去 |
| graphlib | 默認(rèn)情況下,graphlib 應(yīng)該在全局命名空間當(dāng)中,不過(guò)你也可以當(dāng)作參數(shù)傳進(jìn)去 |
我們來(lái)試一下。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" rel="external nofollow" rel="external nofollow" rel="external nofollow" />
</head>
<body>
<!-- content -->
<div id="myholder"></div>
<!-- dependencies 通過(guò)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>
結(jié)果:

使用 HTML 定制元素
流程圖中的每個(gè)點(diǎn),也就是是元素,都可以自定義,直接編寫 html 代碼能添加按鈕、輸入框、代碼塊等。
我的一個(gè)代碼塊 demo,搭配 highlight.js 可以達(dá)到類似 IDA 控制流圖的效果。這個(gè) 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();
}
});
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
用js判斷是否為360瀏覽器的實(shí)現(xiàn)代碼
這篇文章主要介紹了用js判斷是否為360瀏覽器的實(shí)現(xiàn)代碼,有時(shí)候我們需要判斷是否為360瀏覽器,包括百度聯(lián)盟后臺(tái)就有這樣的提示需要的朋友可以參考下2015-01-01
JS實(shí)現(xiàn)FLASH幻燈片圖片切換效果的方法
這篇文章主要介紹了JS實(shí)現(xiàn)FLASH幻燈片圖片切換效果的方法,實(shí)例分析了javascript操作圖片實(shí)現(xiàn)Flash幻燈效果的技巧,需要的朋友可以參考下2015-03-03
JavaScript中獲取鼠標(biāo)位置相關(guān)屬性總結(jié)
這篇文章主要介紹了JavaScript中獲取鼠標(biāo)位置相關(guān)屬性總結(jié),本文重點(diǎn)在搞清楚這些屬性的區(qū)別,需要的朋友可以參考下2014-10-10
基于JavaScript實(shí)現(xiàn)的插入排序算法分析
這篇文章主要介紹了基于JavaScript實(shí)現(xiàn)的插入排序算法,結(jié)合實(shí)例形式詳細(xì)分析了插入排序的原理、操作步驟及javascript相關(guān)實(shí)現(xiàn)技巧與注意事項(xiàng),需要的朋友可以參考下2017-04-04
JavaScript性能優(yōu)化總結(jié)之加載與執(zhí)行
本文詳細(xì)介紹了如何正確的加載和執(zhí)行JavaScript代碼,從而提高其在瀏覽器中的性能。對(duì)JavaScript學(xué)習(xí)者很有幫助,有需要的可以參考學(xué)習(xí)。2016-08-08

