jointjs 入门
链接http://www.bjhee.com/jointjs.html
用JointJS做一个简单的功能控制图
2019年1月30日
继上一篇介绍了GoJS之后,继续研究JS的绘图工具,毕竟GoJS有些小贵。这次选择了JointJS,完全开源,它还有一个商业版本叫Raddit,功能更强大。不过就我的需求场景,开源的Joint就足够了。接下来,我们看看它是怎么使用的。
JointJS是基于Backbone开发的,所以使用Joint之前,要先引入Backbone的相关依赖,所以我们的HTML文件是这样的:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>JointJS Sample</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/jointjs/2.2.1/joint.min.css">
<!-- 下面的顺序不能变 -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jointjs/2.2.1/joint.min.js"></script>
<!-- 这是自己的JS文件 -->
<script type="text/javascript" src="js/main.js"></script>
</head>
<body>
<!-- 这里是画布 -->
<div id="myGraph"></div>
</body>
</html>
这里我都用CDN,也可以将相应的库载到本地,但需要注意的是JS的加载顺序不能改。接下来,我们写一个官方的HelloWorld:
window.onload=function() {
var graph = new joint.dia.Graph; // 创建画板,所有图上的元素都在画板里
var paper = new joint.dia.Paper({ // 创建画板上的画布,画布是用来渲染画板
el: document.getElementById('myGraph'), // 指向HTML里ID为"myGraph"的元素
model: graph, // 指定画板
width: 600, // 画布宽600像素
height: 100, // 画布高100像素
gridSize: 1, // 画布上元素拖动时步进的像素,默认1,设的高方便对齐
background: { // 画布背景色
color: 'rgba(0, 0, 0, 0.1)'
},
});
// 创建一个矩形
var rect = new joint.shapes.standard.Rectangle();
rect.position(100, 30); // 矩形左上角的位置,x:100,y:30,单位像素
rect.resize(100, 40); // 矩形大小,宽100,高40,单位像素
rect.attr({
body: {
fill: 'blue' // 填充色
},
label: {
text: 'Hello', // 矩形上显示的文字
fill: 'white' // 文字颜色
}
});
rect.addTo(graph); // 将上面定义的矩形加入到画板中
var rect2 = rect.clone(); // 复制一个相同的矩形
rect2.translate(300, 0); // 将矩形在水平方向上向右移动300像素
rect2.attr('label/text', 'World!'); // 设置矩形2上的文字
rect2.addTo(graph); // 将矩形2加入到画板中
// 创建一条连线
var link = new joint.shapes.standard.Link();
link.source(rect); // 连线头为矩形1
link.target(rect2); // 连线尾为矩形2
link.addTo(graph); // 将上面定义的连线加入到画板中
}
从上例里,我们可以看出,使用JointJS开发主要的步骤就是创建画板、创建画布、创建图形,然后将图形置于画板中。上面的例子执行后会得到下面的图像。
HelloWorld!
JointJS中的图形主要有两类,一是”元素”(Element),由构造函数创建。上例中的矩形就是JointJS库提供的标准元素,其构造函数为joint.shapes.standard.Rectangle
。元素创建后,可以设置各种参数,比如位置,大小,风格等。JointJS提供了丰富的内置元素如矩形,圆形,椭圆等,可以参考API文档。同时,我们可以通过扩展joint.dia.Element
类来自定义元素。
另一类图形是”连接”(Link),用来将两个”元素”连起来,一般显示为一条连线。上例中的连线是通过JointJS库提供的标准构造函数joint.shapes.standard.Link
来创建的,创建后设置其”头”(Source)和”尾”(Target),即可将两个”元素”连起来。我们也可以设置”连接”的各种参数,如颜色,箭头,标签文字等。
HelloWorld之后我们来做一个简单的自动化功能控制图吧,HTML部分不变,我们来写JS部分。
- 第一步定义画板和画布
var graph = new joint.dia.Graph; // 创建画板,所有图上的元素都在画板里
var paper = new joint.dia.Paper({ // 创建画板上的画布,画布是用来渲染画板
el: $('#myGraph'), // 指向HTML里ID为"myGraph"的元素
model: graph, // 指定画板
width: 600, // 画布宽600像素
height: 300, // 画布高300像素
gridSize: 5, // 画布上元素拖动时步进的为5像素,默认1
drawGrid: true, // 显示步进点,方便对齐
background: { // 画布背景色
color: 'rgba(0, 0, 0, 0.1)'
},
// 连接线风格
defaultLink: new joint.shapes.logic.Wire({
connector: { name: 'jumpover' }, // 当两根连线交叉时,其中一根跳过
}),
linkPinning: false, // 连线必须连到某个元素,即不允许连到空白处
snapLinks: {
radius: 25 // 距离元素连接点25像素时自动连接上
},
// 验证连线是否允许,
validateConnection: function(viewSource, magnetSource, viewTarget, magnetTarget, end, viewLink) {
if (end === 'target') {
// 连线目标必须时一个"in"类型连接点
if (!magnetTarget || !magnetTarget.getAttribute('port-group') || magnetTarget.getAttribute('port-group').indexOf('in') < 0) {
return false;
}
// 检查连接点是否已经有连线接入,不允许多重接入
var portUsed = this.model.getLinks().some(function(link) {
return (link.id !== viewLink.model.id &&
link.get('target').id === viewTarget.model.id &&
link.get('target').port === magnetTarget.getAttribute('port'));
});
return !portUsed;
} else { // end === 'source'
// 连线起始点必须时一个"out"类型连接点
return magnetSource && magnetSource.getAttribute('port-group') && magnetSource.getAttribute('port-group').indexOf('out') >= 0;
}
},
});
上面的属性有些多,但大部分都好理解,主要是验证函数validateConnection()
,其返回true
或false
,用来决定连线是否被允许。传入的参数viewSource
和viewTarget
分别是画线过程中鼠标按钮释放时,当前连线头和尾的元素;而参数magnetSource
和magnetTarget
分别是当前连线头和尾的”连接点”(port)。”连接点”的概念下面会讲到。
- 第二步定义基本图例元素
这里,我们创建了与、或、非,三个基本元素,因为是图例元素,所以不允许连入连出。我们使用标准库提供的”joint.shapes.devs.Model”元素,因为它很方便设置”连接点”(port)。
// 创建基础元件模板
var gateTemplate = new joint.shapes.devs.Model({
position: { // 默认位置
x: 0,
y: 0
},
size: { // 默认大小
width: 50,
height: 60
},
// "连接点"(port)的风格
portMarkup: '<rect class="joint-port-body" width="10" height="3" style="fill:black" />',
// "连接点"(port)标签文字的显示风格
portLabelMarkup: '<text class="port-label joint-port-label" font-size="10" y="0" fill="#000" /> ',
ports: { // 定义连接点
groups: {
'in': { // "入"连接点的属性和风格
attrs: {
'.port-body': { // 这是JointJS类库预定义的连接点属性类
magnet: 'passive', // 该连接点只入不出
},
'.joint-port-body': { // 这是JointJS类库预定义的连接点风格类
x:-10 // "入"连接点左移10个像素,这样可以显示在元素外部
}
},
label: {
position: {
args: {x:18}, // 标签右移,这样可以显示在元素内部
},
},
},
'out': {
label: { // "出"连接点的属性和风格
position: {
args: {x:-23}, // 标签左移,这样可以显示在元素内部
},
},
}
}
},
attrs: {
'.label': {
'type': 'primary', // 自定义一个图例属性,后面事件操作时判断用
fontSize: 12, // 标签字体
'ref-x': .5, // 标签相对于元素的水平位置
'ref-y': .05 // 标签相对于元素的垂直位置
},
}
});
// 生成"与"元素,两个"入"连接点,一个"出"连接点,显示"And"字样标签
function genAndPr() {
return gateTemplate.clone().set('inPorts', ['IN1', 'IN2']).set('outPorts', ['OUT']).attr('.label/text', 'And');
}
// 生成"或"元素,两个"入"连接点,一个"出"连接点,显示"Or"字样标签
function genOrPr() {
return gateTemplate.clone().set('inPorts', ['IN1', 'IN2']).set('outPorts', ['OUT']).attr('.label/text', 'Or');
}
// 生成"非"元素,一个"入"连接点,一个"出"连接点,显示"Not"字样标签
function genNotPr() {
return gateTemplate.clone().set('inPorts', ['IN ']).set(