您的位置:首页 > Web前端 > JavaScript

JointJS

2015-08-06 16:07 537 查看


JointJS介绍

时间 2014-03-03 15:21:24 GroovyQ
原文 http://www.groovyq.net/node/733
主题 SVG JavaScript最近因项目需要,要在页面实现可操作的流程图——根据状态量的不同演示对应流程,这就不是单单加载一个图片的问题了,还存在控制与元素变化,用什么实现怎么实现,是个让人头疼的问题。我们最先想到的是flash,flash是可以通过接口实现交互的,但是flash的制作成本过高,需要安装专业的软件,最主要的是flash不支持移动端,这就对我们之后的开发埋下了隐患,经过权衡,最终选择了最近最火的HTML5,利用 JointJS 去做图。为什么是JointJS?JointJS是一个HTML5的JavaScript库,用于创建完全互动式的图表,它极易上手且操作简单,并且支持所有的现代浏览器,对于时间紧迫的我们非常有利。我们可以使用JointJS已提供的图元素绘图,也可根据需求自定义一些图元素。除此之外,JointJS创建的图表就是SVG图形,它具有SVG的所有优点,但是我们却不必在意SVG的那些元素是怎么定义的,不用在意那些标签规则,我们更注重的是逻辑,只要通过封装好的方法把想法表现即可,这样,JointJS自然就成为我们的第一选择。更多有关JointJS的内容请参见 官网 。下来就让我们通过一个简单的小例子了解一下JointJS的使用。需求:展示一个具有开关、电源、安培表、电阻及电灯的串联电路在电源闭合和断开时电灯的状态。下图即为做出后的效果,左边为关闭电灯的状态,右边为开起状态,通过开关控制。加载JointJS库前往 官网 下载joint.js和joint.css或其他已经定义好的图形库,并在页面中添加:
创建画板首先,我们需要一张可以在上面作画的纸,JointJS中一个图像模型就是一个joint.dia.Graph 的模型实例,其他的子模型都必须包含其中,但单单定义图像模型还不够,只有将图像模型附加到joint.dia.Paper模型中,才能显示对应的图像,Graph就像是在我们脑子中的构思一样,我们需要将他画在纸Paper上。
var graph=new joint.dia.Graph;
var paper=new joint.dia.Paper({
el:$("#demo"), //指明了当前图像需要显示在页面的什么地方
width:800,
height:600,
gridSize:10,
perpendicularLinks:true,
model:graph
});
添加链接这里,我利用了JointJS的连线模型模拟电路图中的线,首先让我们先了解一下连线的SVG DOM结构:
了解了元素的SVG DOM结构后,我们就可以直接定义元素并指定样式了:
var link=new joint.dia.Link({
attrs: {
'.connection': { stroke: '#000000' },
'.marker-target': { d: 'M 10 0 L 0 5 L 10 10 z' }
},
source: { x:325,y:150},
target: {x:400,y:125},
vertices:[{x:100,y:150},{x:100,y:450},{x:700,y:450},{x:700,y:150},{x:600,y:150},{x:600,y:50},{x:400,y:50}]
});
graph.addCell([link]);
以上添加了一个joint.dia.Link实例,每个实例对象都是graph模型的cell,必须通过graph.addCell()添加到模型中以便显示出来。除了初始化外,我们也可以通过element.attr()方法去为特定的对象添加样式,element.attr()的方法接受一个对象类型的参数,对象的Key是匹配SVG DOM元素的CSS选择器。要想随心所欲的设置样式,那就需要对SVG有一定的了解,可参见 教程 。添加元素我们可以利用JointJS已经定义的元素实现安培表及电阻,在此之前需要了解一下元素的SVG DOM结构,以矩形joint.shapes.basic.Rect为例:
了解了元素的SVG DOM结构后,我们就可以直接定义元素并指定样式了:
//定义电阻。其实在画电路的时候已经将电阻的另一部分实现,这里只是画了剩下的矩形。
var R=new joint.shapes.basic.Rect({
position: { x: 325, y: 125 },
size: { width: 150, height: 50 },
attrs: { rect: { fill: 'none', stroke: '#000000'}}
});
//定义安培表。
var A=new joint.shapes.basic.Circle({
size: { width: 80, height: 80 },
position: { x: 60, y: 260 },
attrs: {
'text':{text: 'A+', fill: 'black','font-size': 18}
}
});
graph.addCell([R,A]);
添加自定义元素很多时候,现有的元素(joint.shapes.basic.Rect 、 joint.shapes.basic.Circle、......)并不能满足我们的需求,我们也可以自定义一些元素,自定义的元素其实是通过多个SVG节点联合构建而成的,现在就让我们先定义一个电灯元素,电路图中的电灯为一个圆形及两条相交的线段构成的图形。
var k=12.5*Math.pow(2,0.5);
joint.shapes.basic.Bulb = joint.shapes.basic.Generic.extend({
markup: '',
defaults: joint.util.deepSupplement({
type: 'basic.Bulb',
size: { width: 50, height: 50 },
attrs: {
circle: { cx: 25, cy: 25,r:25, fill: 'white', stroke: 'black'},
'.bulb_line1':{
stroke: 'black',x1:25-k,y1:25-k,x2:25+k,y2:25+k
},
'.bulb_line2':{
stroke: 'black' ,x1:25-k,y1:25+k,x2:25+k,y2:25-k
}
}
}, joint.shapes.basic.Generic.prototype.defaults)
});
接下来只需要实例化一个电灯对象即可:
var Bulb=new joint.shapes.basic.Bulb({size: { width: 80, height: 80 },position: { x: 660, y: 260 }});
graph.addCell([Bulb]);
现在,让我们将电源和开关也画出来吧。
//定义开关元素
joint.shapes.basic.Switch = joint.shapes.basic.Generic.extend({
markup: '',
defaults: joint.util.deepSupplement({
type: 'basic.Switch',
size: { width: 100, height: 60 },
attrs: {
rect:{width: 100, height: 60},
'.switch': { stroke: 'black',x1:5,y1:40,x2:90,y2:0},
'.switch_circle1':{
stroke: 'black',cx:10,cy:50,r:10
},
'.switch_circle2':{
stroke: 'black',cx:90,cy:50,r:10
}
}
}, joint.shapes.basic.Generic.prototype.defaults)
});
//定义电源元素
joint.shapes.basic.PowerSupply = joint.shapes.basic.Generic.extend({
markup: '',
defaults: joint.util.deepSupplement({
type: 'basic.PowerSupply',
size: { width: 10, height: 100 },
attrs: {
'.body':{width: 10, height: 100},
'.power_line1': {x1:0,y1:0,x2:0,y2:100 },
'.power_line2': {x1:10,y1:10,x2:10,y2:90},
'line':{'stroke-width': 2, stroke: 'black'}
}
}, joint.shapes.basic.Generic.prototype.defaults)
});
var Switch=new joint.shapes.basic.Switch({position: { x: 200, y: 400 },attrs: {'rect': { id: 'switch' }}});
var PowerSupply=new joint.shapes.basic.PowerSupply({size:{height:70},position: { x: 550, y: 415 }});
graph.addCell([Switch,PowerSupply]);
事件处理JointJS库依赖于Backbone MVC,我们可以利用Backbone的主要功能为graph中的任一模型添加事件,例如,我们可以监听graph的所有元素的所有事件:
graph.on('all', function(eventName, cell) {
console.log(arguments);
});
我们也可以监听特定元素的指定事件,例如我们可以监听Switch的参数改变事件:
Switch.on('change:attrs', function(element) {
console.log(element.id, ':', element.get('attrs'));
});
添加动作电路图已经画好,但想要完成我们的目的首先需要知道:开灯的时候电灯是亮的且开关会闭合,关灯时则与之相反。
$("#switch").click(function(){
if(Switch.attr('.switch/x2')==90){
//打开电灯
//闭合开关
Switch.attr({
'.switch': {x2:100,y2:40}
});
//电灯亮
Bulb.attr({
'circle': {fill:'yellow'}
});
}else{
//关闭
//打开开关
Switch.attr({
'.switch': {x2:90,y2:0}
});
//电灯灭
Bulb.attr({
'circle': {fill:'#ffffff'}
});
}
});
现在,一个灯泡控制功能已经实现了,但当运行起来你就会发现一个问题:页面中的所有元素都可以通过鼠标来控制,然而我们只想让它一直保持我们定义时的状态怎么办?我们可以重写方法,关闭元素和链接的一些动作:
var ElementView = joint.dia.ElementView.extend({
pointermove: function(evt, x, y) {}
});
var LinkView = joint.dia.LinkView.extend({
addVertex: function(evt, x, y) {},
removeVertex: function(endType) {},
pointerdown:function(evt, x, y) {}
});
var paper=new joint.dia.Paper({
el:$("#demo"),
width:800,
height:600,
gridSize:10,
perpendicularLinks:true,
model:graph,
elementView: ElementView
linkView:LinkView
});
除此之外,如果你用的是官方的css那么你还需将一些元素的hover事件去除。在所有元素都不允许操作的情况下,我们也可以通过paper.$el.css('pointer-events', 'none')使所有事件都无用。现在,我们的功能才算真正的完成( 点击查看完整代码 ),如果你也想利用JointJS做电路图,其实我建议你可以写一个JS库,将每个图形都自定义为元素,为了防止现有的功能对我们的做图产生影响,我们也可以重写它,当然你也可以通过一些算法去改变状态,而不是像我这样每个动作都要写个控制。总结通过这个例子,抛砖引玉的介绍了一下JonitJS,可以发现,它还是十分容易上手的,其实除了上面列举的,JonitJS还有很多方法和事件,比如A图像沿路径元素link移动:A.animateAlongPath({ dur: 5 + 's', repeatCount: 1 }, paper.findViewByModel(link).$('.connection')[0]),都在等你慢慢去挖掘,只要你够有创意,你的想法都可以用它来实现,但在此之前你要先去查看JonitJS的 API 。JonitJS虽然容易上手,但真正想熟练运用,还是需要下一番功夫的。我在开发时,就碰上了很多问题,直接造成了流程不能按预计的效果显示,比如:由于我们需要根据不同的状态去实现诸如阀门的开关、元素分裂、轴转动等动作,这就涉及了很多延时加载setTimeout()和定时任务setInterval(),在切换状态前,没有将它们关掉,以至于在当前状态下执行的动作还是上个状态的遗留,所以在切换状态时务必要关闭这些事件(window.clearInterval(intervalID); window.clearTimeout(timeoutID));还有在实现类似地球自转功能的时候,由于对SVG的遮罩不熟悉,起初是通过gif图像来实现转动的,但毕竟gif的加载比较慢,最重要的是缺少了物体移动的流动感,总是有停顿,在了解了遮罩后,一切都变得简单了,只需要一张地球的平面矩形图,并在图上添加一个遮罩,只让一个圆形区域显示,接下来将地球图片沿一定路径循环移动就可以了(我做了一个地球自传的例子,可参见 earthRotation ,只是例子做的不够细致,缺乏球面投影的效果,但是大致的效果是有的。)。要想了解JonitJS的更多可能,我们可以查看 官方 提供的一些例子,可能会给你带来更过的灵感。虽然我现在只是刚刚接触,但已被吸引,它将成为我的一把利器,我相信它也会帮助你更加轻松的实现互动式的图表的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: