关于Raphaël
官方宣称兼容各种主流浏览器,据笔者测试在IE6下尚有一些问题(不过这些与本文无关)
他是使用js来创建vml或svg来绘图的
缘起
项目中不能使用Silverlight或者flash来解决绘图和拖动的问题
而且为了项目效果较好,要求拖动的时候箭头能动态改变起点和重点,别且箭头要改变方向
所以只能考虑JS了
效果
演示
http://www.mrlh.net/flowchart/demo.htm[已经不能访问了]
源码
引用
这两个东西是不相干的,引用先后顺序也无所谓
页面加载完成后的代码
$(function () { //用来存储节点的顺序 var connections = []; //拖动节点开始时的事件 var dragger = function () { this.ox = this.attr("x"); this.oy = this.attr("y"); this.animate({ "fill-opacity": .2 }, 500); }; //拖动事件 var move = function (dx, dy) { var att = { x: this.ox + dx, y: this.oy + dy }; this.attr(att); $("#test" + this.id).offset({ top: this.oy + dy + 10, left: this.ox + dx + 10 }); for (var i = connections.length; i--; ) { r.drawArr(connections[i]); } }; //拖动结束后的事件 var up = function () { this.animate({ "fill-opacity": 0 }, 500); }; //创建绘图对象 var r = Raphael("holder", $(window).width(), $(window).height()); //绘制节点 var shapes = [ r.rect(190, 100, 60, 40, 4), r.rect(290, 80, 60, 40, 4), r.rect(290, 180, 60, 40, 4), r.rect(450, 100, 60, 40, 4) ]; //定位节点上的文字 $("#test1").offset({ top: 100 + 10, left: 190 + 10 }); $("#test2").offset({ top: 80 + 10, left: 290 + 10 }); $("#test3").offset({ top: 180 + 10, left: 290 + 10 }); $("#test4").offset({ top: 100 + 10, left: 450 + 10 }); //为节点添加样式和事件,并且绘制节点之间的箭头 for (var i = 0, ii = shapes.length; i < ii; i++) { var color = Raphael.getColor(); shapes[i].attr({ fill: color, stroke: color, "fill-opacity": 0, "stroke-width": 2, cursor: "move" }); shapes[i].id = i + 1; shapes[i].drag(move, dragger, up); shapes[i].dblclick(function () { alert(this.id) }) } //存储节点间的顺序 connections.push(r.drawArr({ obj1: shapes[0], obj2: shapes[1] })); connections.push(r.drawArr({ obj1: shapes[1], obj2: shapes[2] })); connections.push(r.drawArr({ obj1: shapes[2], obj2: shapes[3] })); });
这些代码注释比较详细,就不多说了
在这些代码中涉及到操作的界面元素HTML代码如下
测试1测试2测试3测试4
其中关键元素的样式如下
#holder { top: 0px; left: 0px; right: 0px; bottom: 0px; position: absolute; z-index: 999; } test { position: absolute; width: 80px; height: 30px; top: 0px; z-index: 0; }
在拖动事件中,动态改变了节点文本元素的位置
并且重绘了节点和箭头
drawArr是一个自定义方法,负责修改箭头的方向,代码如下
//随着节点位置的改变动态改变箭头 Raphael.fn.drawArr = function (obj) { var point = getStartEnd(obj.obj1, obj.obj2); var path1 = getArr(point.start.x, point.start.y, point.end.x, point.end.y, 8); if (obj.arrPath) { obj.arrPath.attr({ path: path1 }); } else { obj.arrPath = this.path(path1); } return obj; };
首先需要确定箭头的起始位置,
point包含两个点,
point.start为起点,
point.end为终点,
然后需要确定箭头的绘图路径
一个箭头包含三个线段,四个点
1:起点,2:终点,3:箭头终点1,4:箭头终点2
在此函数中,判断如果箭头已经被绘制过,
只要修改属性即可
如果没有被绘制过,则需要重新绘制
下面来看一下动态确定起点和终点的代码
function getStartEnd(obj1, obj2) { var bb1 = obj1.getBBox(), bb2 = obj2.getBBox(); var p = [ { x: bb1.x + bb1.width / 2, y: bb1.y - 1 }, { x: bb1.x + bb1.width / 2, y: bb1.y + bb1.height + 1 }, { x: bb1.x - 1, y: bb1.y + bb1.height / 2 }, { x: bb1.x + bb1.width + 1, y: bb1.y + bb1.height / 2 }, { x: bb2.x + bb2.width / 2, y: bb2.y - 1 }, { x: bb2.x + bb2.width / 2, y: bb2.y + bb2.height + 1 }, { x: bb2.x - 1, y: bb2.y + bb2.height / 2 }, { x: bb2.x + bb2.width + 1, y: bb2.y + bb2.height / 2 } ]; var d = {}, dis = []; for (var i = 0; i < 4; i++) { for (var j = 4; j < 8; j++) { var dx = Math.abs(p[i].x - p[j].x), dy = Math.abs(p[i].y - p[j].y); if ( (i == j - 4) || (((i != 3 && j != 6) || p[i].x < p[j].x) && ((i != 2 && j != 7) || p[i].x > p[j].x) && ((i != 0 && j != 5) || p[i].y > p[j].y) && ((i != 1 && j != 4) || p[i].y < p[j].y)) ) { dis.push(dx + dy); d[dis[dis.length - 1]] = [i, j]; } } } if (dis.length == 0) { var res = [0, 4]; } else { res = d[Math.min.apply(Math, dis)]; } var result = {}; result.start = {}; result.end = {}; result.start.x = p[res[0]].x; result.start.y = p[res[0]].y; result.end.x = p[res[1]].x; result.end.y = p[res[1]].y; return result; }
这段代码来自Raphael官方demo
不是我写的
也一时半会说不清楚,
大家还是自己去研究吧
确定箭头路径的代码如下
//获取组成箭头的三条线段的路径 function getArr(x1, y1, x2, y2, size) { var angle = Raphael.angle(x1, y1, x2, y2);//得到两点之间的角度 var a45 = Raphael.rad(angle - 45);//角度转换成弧度 var a45m = Raphael.rad(angle + 45); var x2a = x2 + Math.cos(a45) * size; var y2a = y2 + Math.sin(a45) * size; var x2b = x2 + Math.cos(a45m) * size; var y2b = y2 + Math.sin(a45m) * size; var result = ["M", x1, y1, "L", x2, y2, "L", x2a, y2a, "M", x2, y2, "L", x2b, y2b]; return result; }
此函数把箭头路径作为数组反馈给调用函数
数组中
M表示画笔起点移动到此点
L表示从某点绘制到某点,绘制直线
以上函数反馈结果的意思是:
画笔从(x1,y1)开始绘制直线到(x2,y2),然后从(x2,y2)绘制直线到(x2a,y2a)然后画笔移动到(x2,y2)然后从(x2,y2)绘制直线到(x2b,y2b)
在确定这几个点的过程中
用到了一些数学知识,具体原理也不多说了