Twproject Gantt开源甘特图功能扩展

Posted 腾信@迪克猪

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Twproject Gantt开源甘特图功能扩展相关的知识,希望对你有一定的参考价值。

1、Twproject Gantt甘特图介绍

    Twproject Gantt 是一款基于 jQuery 开发的甘特图组件,也可以创建其它图表,例如任务树(Task Trees)。内置编辑、缩放和 CSS 皮肤等功能。更重要的是,它是免费开源的。

    官网地址是:https://gantt.twproject.com/ 源码可以从github下载:

2016-09-17_112730

2、扩展功能一:code自动层级编码,满足wbs编码要求

    工作分解结构 (WBS) 代码是项目的识别您的分级显示结构中的每个任务的唯一位置的字母数字代码。WBS 代码可用于报告日程和跟踪成本。在工程系统中,应用非常广泛,相关的编码还有OBS\\RBS\\PBS等等。

   在Microsoft Office Project中的WBS代码有两种:大纲数字和自定义WBS代码。大纲数字是最简单的 WBS 编码类型。Microsoft Office Project 将自动计算每项任务大纲数字基础上的任务列表的大纲结构编号。例如,您的任务列表中的第一个任务的编号的 1。如果该任务具有三个子任务,子任务是编号 1.1、1.2 和 1.3。大纲数字只包含数字 (无号) 和不能对其进行编辑。他们执行操作,但是,自动移动时更改任务向上或向下在任务列表中,当降级或升级任务。例如,如果子任务当前具有的大纲数字为 3.5.4,并且移列表中的一行,大纲数字将自动更新为 3.5.3。如果然后升级该相同的子任务,大纲数字将自动更新到 3.6。

   理解WBS编码的概念,扩展Twproject Gantt的code编码,采用大纲数字。如下图:

image

    对ganttMaster.js进行扩展,增加属性:isLevelCode(bool类型),是否自动对code进行编码。增加扩展方法levelCode:

GanttMaster.prototype.levelCode = function () {
    if (this.tasks && this.tasks.length > 0) {
        var curCodeExt = 1;
        for (var i = 0; i < this.tasks.length; i++) {
            var tsk = this.tasks[i];
            if (tsk.level == 0) {
                tsk.code = curCodeExt;
                levelChildCode(tsk.getChildren(), tsk.code);
                curCodeExt++;
            }
        }
    }

    function levelChildCode(cTasks, prefix) {
        if (cTasks && cTasks.length > 0) {
            var curCodeExt = 1;
            for (var i = 0; i < cTasks.length; i++) {
                var tsk = cTasks[i];
                tsk.code = prefix + "." + curCodeExt;
                levelChildCode(tsk.getChildren(), tsk.code);
                curCodeExt++;
            }
        }
    }
}

    本函数采用递归实现:

%5V1IPFG8}OCFC`ZQF2UGS9

    调用步骤:

    定义甘特图:var ge; //this is the hugly but very friendly global var for the gantt editor

    加载甘特图模板: $("#ganttemplates").loadTemplates();

    初始化甘特图:

          // here starts gantt initialization
            ge = new GanttMaster();
            //TODO:是否自动显示bar
            ge.isBrowserTaskBar = true;
            //TODO:设置code是否自动编码
            ge.isLevelCode = true;
            var workSpace = $("#workSpace");
            workSpace.css({
                width: $(window).width() - 20,
                height: $(window).height() - 100
            });
            ge.init(workSpace)

    加载数据并调用层级方法:

         function loadFromLocalStorage() {
            var ret;
            if (localStorage) {
                if (localStorage.getObject("teamworkGantDemo")) {
                    ret = localStorage.getObject("teamworkGantDemo");
                }
            } else {
                $("#taZone").show();
            }
            if (!ret || !ret.tasks || ret.tasks.length == 0) {
                ret = JSON.parse($("#ta").val());
                //actualiza data
                var offset = new Date().getTime() - ret.tasks[0].start;
                for (var i = 0; i < ret.tasks.length; i++)
                    ret.tasks[i].start = ret.tasks[i].start + offset;
                //debugger;
            }
            ge.loadProject(ret);
            ge.checkpoint(); //empty the undo stack
            ge.levelCode();
        }

     可查看gantt213.html页面。

3、扩展功能二:让选择的task出现在显示窗口

3.1 效果图

    选择task对应的gantt bar显示出来,且在滑动窗口居中显示,效果如下图:

a

3.2 ganttMaster修订

    经过dom之间关系分析,找到其中的内在联系,主要增加如下方法:

GanttMaster.prototype.browserTaskBar = function (tsk) {
    if (tsk && tsk instanceof Task) {
        var id = tsk.id;
        //找到taskBar
        var taskBar = $("svg[taskid=\\"" + id + "\\"]");
        var taskBarX = parseInt($(taskBar).attr("x"));
        var taskBarWidth = parseInt($(taskBar).attr("width"));
        //获取svg最外层画布canvas
        var canvas = $(taskBar).parent().parent().parent();
        var canvasWidth = $(canvas).width();
        //获取滚动区域
        var scroll = $(taskBar).parent().parent().parent().parent();
        var scrollWidth = $(scroll).width();
        var scrollScrollWidth = canvasWidth - scrollWidth + 17;
        //获取水平滚动条要移动的位置
        var centerLeft = (taskBarX + taskBarWidth / 2);
        var scrollLeft = (centerLeft – scrollWidth/2) / canvasWidth * scrollScrollWidth;
        console.log("-------------------------------------------------------");
        console.log("canvas width:" + canvasWidth); //画布区域大小
        console.log("scroll width:" + scrollWidth); //滚动区域大小
        console.log("scroll scroll max width:" + scrollScrollWidth); //滚动区域最大滑动区域
        console.log("scroll left position(old):" + $(scroll).scrollLeft()); //滚动区域滑块位置(旧)
        $(scroll).scrollLeft(scrollLeft); //设置滑块位置
        console.log("scroll left position(new):" + $(scroll).scrollLeft()); //滚动区域滑块位置(新)
        console.log("-------------------------------------------------------");
    }
};

    方法讲解:根据taskid找到bar,获取对应的div层与滚动窗口,设置滚动窗口的scrollLeft。

    注意:红色底纹17,是如何得到的呢?这个是滚动条宽度,3.4中有详细分析。

3.3 思路与模型

    设置bar在滚动区域的中心位置,就是如何设置滚动区域的scrollLeft值,这是解决问题的关键。原甘特图如下:

image

画布区域(灰色部分)

image

滚动区域(灰色部分)

    对应的dom层级结构如下:

{A79DD(QZ321T4GII1I4)[E

    从Bar到展示区域,总共是4层嵌套。找到对应的bar,分析上层的节点间的依赖关系,从以下4个问题入手:

    • (1)如何找到Bar?
    • (2)上层节点哪些起决定作用?
    • (3)画布区域与滚动区域的关系如何?
    • (4)滚动区域的最大scrollLeft是多少?

问题一:如何找到Bar?

    Bar与任务项是联动的,两者是通过taskid进行关联的,比如找到taskid=-6的甘特图形:

        var id = -6;
        //找到taskBar
        var taskBar = $("svg[taskid=\\"" + id + "\\"]")

问题二:上层节点哪些起到决定作用?

    上层节点只有画布区域与滚动区域起到作用,bar在这两个区域的位置是如何的?通过分析dom结构,建立以下模型:

L`W1OF2_(XE4{56F@TR}C3Y

模型示意图1

    注意事项:(1)垂直滚动条暂不考虑。(2)task bar相对于画布区域的坐标x是不变的。   

问题三:画布区域与滚动区域的关系如何?

    从模型示意图来看,滚动区域必须而且正好可以查看画布区域,要保证这点,canvasWidth与scrollScrollWidth是一定的比例关系,则比例系数为scale=canvasWidth/scrollScrollWidth,表示滑动区域的比例,例如滑动区域向左滑动1,则画布区域则向右滑动scale。

(1)当出现滚动条时,canvasWidth>scrollScrollWidth>scrollWidth,而scale>1

(2)当滚动区域宽度增加时,scrollScrollWidth是在逐渐变小

(3)未出现滚动条时,scrollScrollWidth = 0

    解惑,从模型来看,scrollWidth应该是大于scrollScrollWidth的吧?如何把比自己更大的画布区域包括进来,就是依靠滚动区域,这里可看作scrollScrollWidth是对scrollWidth的放大,其放到比例就是scale。

问题四:滚动区域最大scrollLeft是多少

    如何获取某一时刻,最大scollLeft。解决办法,通过chrome浏览器打开甘特图,将滚动区域的滑块拉到最左边,在console窗口输入$("div[class=\'splitElement splitBox2\']").scrollLeft();可得到。如下图:

image

3.4 数字分析确定依赖关系

    根据chrome调试器,可获取canvasWidth、scrollWidth、scrollScrollWidth,甘特图原始状态下,使用两个浏览器记测试,数据记录如下:

image

Excel分析图表1

   对canvasWidth-scrollWidth与scrollScrollWidth比较,绝对值都为17,得到结果为:

var scrollScrollWidth = canvasWidth - scrollWidth + 17

    如果甘特图处于放到、缩小状态,是否成立呢?再次测试结果如下:

image

Excel分析图表2

    无论甘特图如何缩放,公式依然成立。这就找到了滚动窗口最大滑动区域scrollScrollWidth,bar的显示位置,对应的值就是scrollScrollWidth的一部分。把模型中的bar至于中心位置,如下图:

%L}9516[GEF}ZZVVDM4AWTE

模型示意图2

    上图清晰的给出以下结论:left=centerLeft - scrollWidth/2,计算滑块的位置,也就是找到left在滑块上的映射scrollLeft,结合scale系数,可计算出scrollLeft = left * scale。

    这样就实现了定位scrollLeft,真的是这样吗?经过测试,发现bar在滚动区域,并不在中心位置。从分析方法来看,比较臃肿、复杂,要简化处理,如何做呢?下面将继续进行分销模型并优化。

3.5 模型深入分析与优化

    在模型分析中,没有对遮挡区域进行解析,分析图表如下:

FCLYV@HWGDWQ%V`}SO4H4@3

模型示意图3

    遮挡区域宽度maskWidth(maskWidth1+maskWidth2)=canvasWidth – scrollWidth,与scrollScrollWidth相差17,也就是scrollScrollWidth≈maskWidth。而滚动条的存在,正是滑动遮挡区域,scrollLeft≈left,这样就非常简单化了。考虑17呢?为什么是固定值?而不是变量值呢?

    为了弄懂这个问题,我们使用QQ截图滚动区域的垂直滚动条,如下图:

image

    推测17是垂直滚动条的宽度,对此可忽略不计,则scrollScrollWidth=maskWidth,可以看作scrollLeft=left=maskWidth1,实施也的确如此。

    修订后的js代码如下(也包括获取svg相关属性的修订):

GanttMaster.prototype.browserTaskBar = function (tsk) {
    if (tsk && tsk instanceof Task) {
        var id = tsk.id;
        //找到taskBar
        var taskBar = $("svg[taskid=\\"" + id + "\\"]");
        //var taskBarX = $(taskBar)[0].getBoundingClientRect().left;
        var taskBarX = parseFloat($(taskBar)[0].getAttribute("x"));
        var taskBarWidth = parseFloat($(taskBar)[0].getBoundingClientRect().width);
        //获取svg最外层画布canvas
        var canvas = $(taskBar).parent().parent().parent();
        var canvasWidth = parseFloat($(canvas).width());
        //获取滚动区域
        var scroll = $(taskBar).parent().parent().parent().parent();
        var scrollWidth = parseFloat($(scroll).width());
        //获取水平滚动条要移动的位置
        var centerLeft = (taskBarX + taskBarWidth / 2.0);
        var scrollLeft = (centerLeft - scrollWidth / 2.0);
        var oldScrollLeft = $(scroll).scrollLeft();
        if (Math.abs(oldScrollLeft - scrollLeft) >= 1.0) {
            $(scroll).scrollLeft(scrollLeft); //设置滑块位置
        } else {
            return;
        }
        console.log("-------------------------------------------------------");
        console.log("task left:" + taskBarX);
        console.log("task width:" + taskBarWidth);
        console.log("canvas width:" + canvasWidth); //画布区域大小
        console.log("scroll width:" + scrollWidth); //滚动区域大小
        console.log("scroll left position(old):" + oldScrollLeft); //滚动区域滑块位置(旧)
        console.log("scroll left position(new):" + scrollLeft); //滚动区域滑块位置(新)
        console.log("-------------------------------------------------------");
    }
};

    运行后的效果图如下:

b

    最终优化后的代码:http://files.cnblogs.com/files/zsy/gantt.rar

    温馨提示,如对您有帮助,麻烦赞一下。~~~~~~~~~~

以上是关于Twproject Gantt开源甘特图功能扩展的主要内容,如果未能解决你的问题,请参考以下文章

Dhtmlx Gantt 常用方法及基本配置合集

甘特图[javascript甘特图,甘特图组件,vue甘特图,vue Gantt图表,响应式甘特图,项目经理,vue项目]

Gantt/BTS 生产计划电子看板甘特图

前端甘特图组件开发

甘特图Gantt入门

计划-甘特图 dhtmlx-gantt使用方式与踩坑记