如何从对象数组生成甘特图样式的HTML表格?
如何实现甘特图样式的HTML表格(修复行元素位置错误问题)
我有一个项目数据数组,想要生成甘特图样式的HTML表格。我写了一段代码,但发现行元素被添加到错误的位置,请问有没有其他实现方法?
原有JavaScript代码
// 提取唯一的年份和月份 var years = [...new Set(dataArray.map(item => item.StartYear))]; var months = [...new Set(dataArray.map(item => item.StartMonth).concat(dataArray.map(item => item.EndMonth)))]; // 排序年份和月份 years.sort(); months.sort(); // 动态创建表格 var tableHTML = '<table>'; tableHTML += '<tr><th>Year</th>'; years.forEach(year => tableHTML += `<th colspan="${months.length}">${year}</th>`); tableHTML += '</tr>'; tableHTML += '<tr><td>Month</td>'; months.forEach(month => tableHTML += `<td>${month}</td>`); tableHTML += '</tr>'; dataArray.forEach(item => { tableHTML += `<tr><td>${item.Project}</td>`; years.forEach(year => { months.forEach(month => { var isInProjectRange = (year === item.StartYear && month >= item.StartMonth && month <= item.EndMonth) || (year === item.EndYear && month <= item.EndMonth); tableHTML += `<td>${isInProjectRange ? 'YES' : ''}</td>`; }); }); tableHTML += '</tr>'; }); tableHTML += '</table>'; // 将表格添加到id为'table-container'的div中 document.getElementById('table-container').innerHTML = tableHTML;
原有HTML代码
<div id="table-container"></div> <script> var dataArray = [ {'Project': 'Project 1', 'StartYear': '2023', 'EndYear': '2023', 'StartMonth': 'Nov', 'EndMonth': 'Dec', 'Owner':'Tom'}, {'Project': 'Project 1.1', 'StartYear': '2023', 'EndYear': '2023', 'StartMonth': 'Nov', 'EndMonth': 'Dec', 'Owner':'Tom'}, {'Project': 'Project 2', 'StartYear': '2024', 'EndYear': '2024', 'StartMonth': 'Jan', 'EndMonth': 'May', 'Owner':'Tom'}, {'Project': 'Project 2.1', 'StartYear': '2024', 'EndYear': '2024', 'StartMonth': 'Jan', 'EndMonth': 'Feb', 'Owner':'Tom'}, {'Project': 'Project 2.2', 'StartYear': '2024', 'EndYear': '2024', 'StartMonth': 'Feb', 'EndMonth': 'Mar', 'Owner':'Tom'}, {'Project': 'Project 2.3', 'StartYear': '2024', 'EndYear': '2024', 'StartMonth': 'Mar', 'EndMonth': 'May', 'Owner':'Tom'} ]; </script>
问题分析
原有代码存在两个核心问题导致行元素位置错误:
- 月份排序逻辑错误:直接用字符串排序会导致月份按字母顺序排列(比如
Jan会排在Nov前面),不符合实际时间顺序;且每个年份会渲染所有月份的列,导致2023年的列中出现不属于该年份的Jan等月份,项目对应的单元格位置错位。 - 年份与月份的对应关系错误:每个年份应该只显示该年份实际存在的月份,而非所有月份,否则会出现大量空列且布局混乱。
修正后的实现代码
// 定义月份的正确时间顺序,用于排序和范围判断 const monthOrder = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; // 按年份分组,整理每个年份对应的有效月份 const yearMonthMap = {}; dataArray.forEach(item => { // 初始化年份对应的月份集合 if (!yearMonthMap[item.StartYear]) yearMonthMap[item.StartYear] = new Set(); if (item.StartYear !== item.EndYear && !yearMonthMap[item.EndYear]) yearMonthMap[item.EndYear] = new Set(); // 补充项目覆盖的所有月份(包括中间月份) if (item.StartYear === item.EndYear) { const startIdx = monthOrder.indexOf(item.StartMonth); const endIdx = monthOrder.indexOf(item.EndMonth); for (let i = startIdx; i <= endIdx; i++) { yearMonthMap[item.StartYear].add(monthOrder[i]); } } else { // 跨年份的项目,补充开始年份的剩余月份和结束年份的前置月份 const startIdx = monthOrder.indexOf(item.StartMonth); for (let i = startIdx; i < monthOrder.length; i++) { yearMonthMap[item.StartYear].add(monthOrder[i]); } const endIdx = monthOrder.indexOf(item.EndMonth); for (let i = 0; i <= endIdx; i++) { yearMonthMap[item.EndYear].add(monthOrder[i]); } } }); // 对年份排序 const sortedYears = Object.keys(yearMonthMap).sort(); // 为每个年份的月份按时间顺序排序 sortedYears.forEach(year => { yearMonthMap[year] = Array.from(yearMonthMap[year]).sort((a, b) => monthOrder.indexOf(a) - monthOrder.indexOf(b)); }); // 动态生成表格(添加基础样式优化视觉) let tableHTML = '<table border="1" cellpadding="8" cellspacing="0" style="border-collapse:collapse;">'; // 表头:年份行 tableHTML += '<tr><th style="background:#f0f0f0;">项目</th>'; sortedYears.forEach(year => { tableHTML += `<th colspan="${yearMonthMap[year].length}" style="background:#f0f0f0;">${year}</th>`; }); tableHTML += '</tr>'; // 表头:月份行 tableHTML += '<tr><td style="background:#f5f5f5;">月份</td>'; sortedYears.forEach(year => { yearMonthMap[year].forEach(month => { tableHTML += `<td style="background:#f5f5f5;">${month}</td>`; }); }); tableHTML += '</tr>'; // 生成项目数据行 dataArray.forEach(item => { tableHTML += `<tr><td>${item.Project}</td>`; sortedYears.forEach(year => { yearMonthMap[year].forEach(month => { let isInRange = false; // 判断当前年月是否在项目时间范围内 if (year === item.StartYear && year === item.EndYear) { const monthIdx = monthOrder.indexOf(month); const startIdx = monthOrder.indexOf(item.StartMonth); const endIdx = monthOrder.indexOf(item.EndMonth); isInRange = monthIdx >= startIdx && monthIdx <= endIdx; } else if (year === item.StartYear) { const monthIdx = monthOrder.indexOf(month); const startIdx = monthOrder.indexOf(item.StartMonth); isInRange = monthIdx >= startIdx; } else if (year === item.EndYear) { const monthIdx = monthOrder.indexOf(month); const endIdx = monthOrder.indexOf(item.EndMonth); isInRange = monthIdx <= endIdx; } // 用背景色块标记项目覆盖的月份,更贴近甘特图样式 tableHTML += `<td>${isInRange ? '<div style="width:100%;height:24px;background:#4CAF50;border-radius:4px;"></div>' : ''}</td>`; }); }); tableHTML += '</tr>'; }); tableHTML += '</table>'; // 将表格插入页面容器 document.getElementById('table-container').innerHTML = tableHTML;
改进点说明
- 正确的月份排序:通过
monthOrder数组确保月份按实际时间顺序排列。 - 年份-月份精准分组:每个年份只渲染该年份下项目覆盖的月份,避免空列和位置错位。
- 完善的范围判断:处理同一年份、跨年份的项目时间范围,确保单元格标记准确。
- 视觉优化:用背景色块替代文字标记,更符合甘特图的直观视觉效果,同时添加基础表格样式提升可读性。
内容的提问来源于stack exchange,提问作者grabrep gg
相关产品推荐
相关产品推荐

