当前位置:首页 > 虚拟主机 > 正文

甘特图计划与实际如何对比?js实现进度偏差分析

在Web前端开发中,实现甘特图(Gantt Chart)的计划与实际进度对比,核心在于利用JavaScript处理时间数据、计算进度百分比,并通过Canvas或SVG进行可视化渲染,以下将详细阐述实现逻辑、数据结构设计、核心算法及可视化方案。

数据结构设计

为了清晰地区分“计划”与“实际”,数据模型需要包含两个维度的时间信息,通常采用扁平化的任务列表结构,每个任务对象包含基础信息、计划时间区间和实际时间区间。

字段名 类型 说明
id String/Number 任务唯一标识符
name String 任务名称
planStart Date/String 计划开始时间
planEnd Date/String 计划结束时间
actualStart Date/String 实际开始时间(可为空,表示未开始)
actualEnd Date/String 实际结束时间(可为空,表示进行中)
progress Number 当前完成百分比 (0-100)

核心计算逻辑

在渲染之前,必须对原始数据进行清洗和计算,以确定每个任务在时间轴上的位置、长度以及进度条的覆盖范围。

  1. 时间轴基准确定

    找出所有任务中最早的planStart和最晚的actualEnd(或planEnd),以此确定甘特图的时间轴起始点(Min Time)和结束点(Max Time)。

    甘特图计划与实际如何对比?js实现进度偏差分析 第1张

  2. 进度计算算法

    对于每个任务,需要计算两个关键指标:

    • 计划进度条长度:基于planStart和planEnd计算。
    • 实际进度条长度:基于actualStart和当前时间(若未结束)或actualEnd计算。
    • 重叠区域:用于高亮显示延误或提前的部分。
    function calculateTaskMetrics(task, timelineStart, timelineEnd) { const totalDuration = timelineEnd timelineStart; // 计划部分 const planStartOffset = task.planStart timelineStart; const planDuration = task.planEnd task.planStart; // 实际部分 let actualStartOffset = 0; let actualDuration = 0; if (task.actualStart) { actualStartOffset = task.actualStart timelineStart; const currentActualEnd = task.actualEnd || new Date(); // 进行中取当前时间 actualDuration = currentActualEnd task.actualStart; } return { plan: { left: (planStartOffset / totalDuration) 100 + '%', width: (planDuration / totalDuration) 100 + '%' }, actual: { left: (actualStartOffset / totalDuration) 100 + '%', width: (actualDuration / totalDuration) 100 + '%' }, isDelayed: task.actualStart > task.planStart, // 是否延误 isAhead: task.actualStart < task.planStart // 是否提前 }; }

可视化实现方案

目前主流的JS甘特图实现方式主要有两种:基于DOM的HTML/CSS方案(适合轻量级、交互简单)和基于Canvas/SVG的方案(适合大数据量、高性能)。

基于HTML/CSS的轻量级实现

这种方法利用Flexbox或绝对定位来绘制条形图,优点是开发速度快,易于添加Tooltip和点击事件;缺点是当任务数量超过数百个时,DOM节点过多会导致性能下降。

甘特图计划与实际如何对比?js实现进度偏差分析 第2张

实现步骤:

  1. 创建一个容器,设置水平滚动条。
  2. 左侧固定列显示任务名称。
  3. 右侧时间轴区域使用绝对定位绘制背景网格(天/周/月)。
  4. 每个任务行内,使用两个div分别代表计划条(灰色半透明)和实际条(彩色实心)。

<div class="gantt-row"> <div class="task-name">任务A</div> <div class="timeline-area"> <!-计划条 --> <div class="bar plan-bar" style="left: 10%; width: 20%;"></div> <!-实际条 --> <div class="bar actual-bar" style="left: 12%; width: 15%;"></div> </div> </div>

基于Canvas的高性能实现

对于大型项目,使用HTML5 Canvas API绘制,需要手动处理坐标映射、抗锯齿、缩放和平移逻辑。

核心绘制逻辑:

  1. 坐标映射函数:将日期对象转换为Canvas上的X轴像素坐标。 function dateToX(date, timelineStart, pixelsPerDay) { const daysDiff = (date timelineStart) / (1000 60 60 24); return daysDiff pixelsPerDay; }
  2. 绘制循环:遍历任务数组,先绘制背景网格,再绘制计划矩形,最后绘制实际矩形。
  3. 交互处理:监听mousemove事件,通过计算鼠标坐标反推对应的日期,实现悬停提示。

对比视觉优化建议

为了直观展示计划与实际的差异,建议在视觉上进行区分:

甘特图计划与实际如何对比?js实现进度偏差分析 第3张

  • 颜色编码:计划条使用浅灰色或虚线边框;实际条使用蓝色或绿色。
  • 状态标识
    • 若实际开始时间晚于计划开始时间,实际条左侧可添加红色警示标记。
    • 若实际进度条长度超过计划条长度,表示提前完成。

  • 进度百分比标签:在实际条内部或上方显示具体的完成百分比数字。

常见问题与解答

问题1:如何处理跨月或跨年导致的甘特图时间轴刻度显示混乱?

解答:

时间轴刻度的生成不应硬编码,而应基于时间间隔动态计算,建议实现一个generateTimelineTicks函数,根据当前视图的缩放级别(Zoom Level)决定刻度单位(天、周、月、年)。

  • 如果视图范围小于7天,刻度为天。
  • 如果视图范围在1个月到1年之间,刻度为周或月。
  • 如果视图范围超过1年,刻度为年。

    在绘制时,使用Date对象的getMonth()、getFullYear()等方法判断是否跨月/年,如果是,则在刻度线上添加月份或年份的标签,并绘制垂直分割线以区分不同月份。

问题2:当实际进度数据为空(即任务尚未开始或进行中)时,如何正确渲染甘特图?

解答:

需要引入“当前时间点”作为动态参考。

  • 未开始任务:actualStart和actualEnd均为空,此时只渲染计划条,实际条不渲染或渲染为不可见状态。
  • 进行中任务:actualStart有值,actualEnd为空,此时实际条的结束位置应动态绑定到“当前系统时间”,在渲染循环中,如果检测到actualEnd为空,则使用new Date()作为结束时间计算宽度。
  • 实时更新:为了体现“进行中”的动态效果,可以设置一个定时器或使用requestAnimationFrame,每隔一定时间(如1分钟)重新计算进行中任务的实际条宽度并刷新Canvas或DOM,从而直观展示时间推移对进度的影响。

0