zoukankan      html  css  js  c++  java
  • 使用Raphael绘制流程图,自绘动态箭头,可拖动,有双击事件,纯前端,兼容各种浏览器

    关于Raphaël

    Raphaël是一个在网页上绘图的js类库,非常小压缩版只有89k左右

    官方宣称兼容各种主流浏览器,据笔者测试在IE6下尚有一些问题(不过这些与本文无关)

    他是使用js来创建vml或svg来绘图的

    缘起

    项目中不能使用Silverlight或者flash来解决绘图和拖动的问题

    而且为了项目效果较好,要求拖动的时候箭头能动态改变起点和重点,别且箭头要改变方向

    所以只能考虑JS了

    效果

    image

    演示

    http://www.mrlh.net/flowchart/demo.htm[已经不能访问了]

    源码

    引用

        <script language="javascript" type="text/javascript" src="raphael.js"></script>
        <script language="javascript" type="text/javascript" src="jquery.js"></script>
     

    这两个东西是不相干的,引用先后顺序也无所谓

    页面加载完成后的代码

            $(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代码如下

    <body>
        <div id="holder">
        </div>
        <div id="test1" class="test">
            测试1</div>
        <div id="test2" class="test">
            测试2</div>
        <div id="test3" class="test">
            测试3</div>
        <div id="test4" class="test">
            测试4</div>
    </body>
     

    其中关键元素的样式如下

            #holder
            {
                top: 0px;
                left: 0px;
                right: 0px;
                bottom: 0px;
                position: absolute;
                z-index: 999;
            }
            test
            {
                position: absolute;
                 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

    image

    在此函数中,判断如果箭头已经被绘制过,

    只要修改属性即可

    如果没有被绘制过,则需要重新绘制

    下面来看一下动态确定起点和终点的代码

    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)

    在确定这几个点的过程中

    用到了一些数学知识,具体原理也不多说了

    喜欢的朋友请点支持!谢谢大家!

  • 相关阅读:
    原创的java数据访问框架
    在ASP.NET中使用Session常见问题集锦
    Infragistics中WebGrid的MultiColumn Headers设计
    常用asp.net代码
    如何实现函数IF的嵌套超过七层?
    Microsoft® Visual Studio® 2005 Team Suite Service Pack 1
    ASP.NET中常用的文件上传下载方法
    ASP.NET 2.0:使用用户控件和定制的Web部件个人化你的门户网站
    office2007TW
    http://www.ydowns.com/download/53279.rar
  • 原文地址:https://www.cnblogs.com/liulun/p/2507472.html
Copyright © 2011-2022 走看看