先看效果

java甘特图用什么软件做啊_甘特图

java甘特图用什么软件做啊_甘特图_02

左侧为任务列表,是树形结构。右侧是渲染的甘特图,可以用鼠标滚轮滚动来缩放时间线宽度。

实现思路


树形列表维护一个AST,类似以下结构:

declare interface TaskNode {
  name: string, //任务名称
  start_time: string, //假定日期都是以xxxx-xx-xxTxx:xx:xx格式
  end_time: string, //假定日期都是以xxxx-xx-xxTxx:xx:xx格式
  list_order: number,
  par_id?: string,
  children?: TaskNode[], //子任务列表
  color?: string, //颜色
  expanded?: boolean, //是否展开
}

然后就可以通过解析该AST渲染出甘特图,每当列表数据发生修改、删除、增加操作时立即再次渲染保证左右同步。

主要难点


1. 需要知道甘特图的显示算法,摸清它的规律。

2. 如何实现鼠标滚轮滚动时,甘特图相应缩放?

3. 如何渲染任务块?


甘特图的显示规律:从上往下,从左往右,越早的任务在越左上的位置,越晚的任务在越偏右下的位置,子任务呈阶梯状在父任务下方显示。来看project的甘特图


java甘特图用什么软件做啊_html_03

project 甘特图

当然本文没有实现类似的箭头效果 


如何实现鼠标滚轮滚动时,甘特图相应缩放?

需要对dom的了解比较好,目前浏览器支持对DOM的鼠标滚轮事件

handleMouseWheel(e : any){
      e.preventDefault();
      // console.log(e);
      if(e.deltaY < 0){
        // 向上滚动 
        
      }else{
        // 向下滚动 
        
      }
      
    },

我实现的甘特图主要是有两个部分,一个是时间线,二是任务块。

如果保证时间线和任务块缩放同步,使得甘特图收放自如,需要一个统一的"指挥"。

 

scaleX: 1,  //当前缩放倍率
dayColDomWidth: 1200, // "天"列宽
hourColDomWidth: 50, //"时"列宽
taskBlockHeight: 40, //"任务块"默认高度

 鼠标滚轮事件改变的只是scaleX,就这么简单。通过获取dom动态修改宽度即可,任务块的修改稍有不同

handleScale(scaleX){
      // 时间线
      var doms = document.getElementsByClassName('day-col');
      // console.log(doms);
      
      for(var i=0;i<doms.length;i++){
        const ele : any = doms.item(i);
        ele.style.width = scaleX * this.dayColDomWidth + 'px';
        var hourDoms = ele.getElementsByClassName('hour-col');
        for(var j=0;j<hourDoms.length;j++){
          const hourEle : any = hourDoms.item(j);
          hourEle.style.width = scaleX * this.hourColDomWidth + 'px';
        }
      }
      // 任务块
      var doms = document.getElementsByClassName('task-block');
      for(var i=0;i<doms.length;i++){
        const ele : any = doms.item(i);
        // 任务块宽度的修改 稍有不同
        ele.style.left = parseFloat(ele.dataset.left) * scaleX  + 'px';
        ele.style.width = parseFloat(ele.dataset.width) * scaleX + 'px';
      }
    },

由于JS存在浮点数计算误差的问题,缩放过程会让甘特图产生一定的偏差,当然,这非常小。

 任务块不像"天"列和"小时"列有在scaleX为1时的固定宽度,它本身的宽度是不固定的,所以它的缩放处理稍微麻烦。那么,我们只需要给它找一个"固定宽度"即可:

任务块的宽度与它的时长有关,距离左边的偏移量与他的起始时间和“最小时间”有关,那么不管它处于什么scaleX时渲染,我们都可以计算出它的"固定宽度",即 实际宽度 / scaleX。左侧偏移量同理。

var width = (this.hourColDomWidth * this.scaleX * totalHour) + (this.hourColDomWidth *           this.scaleX * restSecond / 3600);
    taskBlockDom.setAttribute('data-width',`${width / this.scaleX}`);

对于时间线的渲染,无非就是递归下AST,找到最小日期和最大日期,得出天数,剩下的就是DOM的一些遍历操作,这里不再赘述。


任务块的渲染

主要是递归AST,伪代码如下

/**
     * 渲染任务块
     * @param {TaskNode} taskNode 任务节点
     * @param {*} params 参数
     * @param {Date} minDate 最小日期
     */
    renderTaskBlock(treeNode : TaskNode, params : any, minDate : Date){
      if(该节点有开始时间和结束世间){
       
  
        // 得到当前任务的开始时间和结束时间,注意时区处理

        // 得到当前任务的开始时间和结束时间的之间总的秒数差值
        
        // 得到整除小时后的余秒数
        
        // 得到小时数(取整)

        // 得到左侧偏移小时数
        
        
        // 创建 dom 任务块, 设置类名和大小和位置

        
        // 任务块的Y坐标系数
        params.index = params.index + 1;
      }

      // 如果有子任务,递归渲染子任务
      
     
      
    },