JointJS:JavaScript流程图绘制框架
JointJS:JavaScript 流程图绘制框架
最近调研了js画流程图的框架,最后选择了Joint。配合上 dagre 可以画出像模像样的流程图。
⽬录
JointJS
是⼀个开源前端框架,⽀持绘制各种各样的流程图、⼯作流图等。Rappid 是 Joint 的商业版,提供了⼀些更强的插件。JointJS 的特点有下⾯⼏条,摘⾃官⽹:
能够实时地渲染上百(或者上千)个元素和连接
⽀持多种形状(矩形、圆、⽂本、图像、路径等)
⾼度事件驱动,⽤户可⾃定义任何发⽣在 paper 下的事件响应
元素间连接简单
可定制的连接和关系图
连接平滑(基于贝塞尔插值 bezier interpolation)& 智能路径选择
基于 SVG 的可定制、可编程的图形渲染
NodeJS ⽀持
通过 JSON 进⾏序列化和反序列化
总之 JoingJS 是⼀款很强的流程图制作框架,开源版本已经⾜够⽇常使⽤了。
jquery框架原理⼀些常⽤地址:
API:
Tutorials:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="cdnjs.cloudflare/ajax/libs/jointjs/2.1.0/joint.css" />
</head>
<body>
<!-- content -->
<div id="myholder"></div>
<!-- dependencies 通过CDN加载依赖-->
<script src="cdnjs.cloudflare/ajax/libs/jquery/3.1.1/jquery.js"></script>
<script src="cdnjs.cloudflare/ajax/libs/lodash.js/3.10.1/lodash.js"></script>
<script src="cdnjs.cloudflare/ajax/libs/backbone.js/1.3.3/backbone.js"></script>
<script src="cdnjs.cloudflare/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: ElementById('myholder'),
model: graph,
width: 600,
height: 100,
gridSize: 1
});
var rect = new joint.shapes.standard.Rectangle();
rect.position(100, 30);
rect.attr({
body: {
fill: 'blue'
},
label: {
text: 'Hello',
fill: 'white'
}
});
rect.addTo(graph);
var rect2 = rect.clone();
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.attr({
body: {
fill: 'blue'
},
label: {
text: 'Hello',
fill: 'white'
}
});
rect.addTo(graph);
var rect2 = rect.clone();
rect2.attr('label/text', 'World!');
rect2.addTo(graph);
var link = new joint.shapes.standard.Link();
link.source(rect);
link.target(rect2);
link.addTo(graph);
JSON();
}
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();
});
<('/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="cdnjs.cloudflare/ajax/libs/jointjs/2.1.0/joint.css" />
</head>
<body>
<!-- content -->
<div id="myholder"></div>
<!-- dependencies 通过CDN加载依赖-->
<script src="cdnjs.cloudflare/ajax/libs/jquery/3.1.1/jquery.js"></script>
<script src="cdnjs.cloudflare/ajax/libs/lodash.js/3.10.1/lodash.js"></script>
<script src="cdnjs.cloudflare/ajax/libs/backbone.js/1.3.3/backbone.js"></script>
<script src="cdnjs.cloudflare/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: ElementById('myholder'),
model: graph,
width: 600,
height: 100,
gridSize: 1
});
$.get('192.168.237.128:8071/graph', function(data, statue){
graph.fromJSON(data);
});
</script>
</body>
</html>
⾃动布局
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 { 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 .
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 .
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.attr({
body: {
fill: 'blue'
},
label: {
text: 'Hello',
fill: 'white'
}
});
rect.addTo(graph);
var rect2 = rect.clone();
rect2.attr('label/text', 'World!');
rect2.addTo(graph);
for(var i=0; i<10; i++){
var cir = new joint.shapes.standard.Circle();
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
});
JSON();
}
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();
});
<('/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="cdnjs.cloudflare/ajax/libs/jointjs/2.1.0/joint.css" /> </head>
<body>
<!-- content -->
<div id="myholder"></div>
<!-- dependencies 通过CDN加载依赖-->
<script src="cdnjs.cloudflare/ajax/libs/jquery/3.1.1/jquery.js"></script>
<script src="cdnjs.cloudflare/ajax/libs/lodash.js/3.10.1/lodash.js"></script>
<script src="cdnjs.cloudflare/ajax/libs/backbone.js/1.3.3/backbone.js"></script>
<script src="cdnjs.cloudflare/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: ElementById('myholder'),
model: graph,
width: 2000,
height: 2000,
gridSize: 1
});
$.get('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.d({
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.d({
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 = $(_.plate)());
// 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.
<('change', this.updateBox, this);
// Remove the box when the model gets removed from the graph.
<('remove', veBox, this);
this.updateBox();
},
render: function() {
joint.dia.der.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 = BBox();
// Example of updating the HTML with a data stored in the cell model.
this.$box.find('label').('label'));
this.$box.find('code').('code'));
var color = ('color');
this.$box.css({
width: bbox.width,
height: bbox.height,
left: bbox.x,
top: bbox.y,
background: color,
"border-color": color
});
},
removeBox: function(evt) {
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论