You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Chart.js堆叠柱状图:员工同一项目多段工时仅显示第一段的修复问询

问题描述

这是之前Stack Overflow问题的延续,感谢user2057925的帮助。目前实现的堆叠柱状图可正常展示员工项目工时,但当员工在某一年同一项目存在两段不同工时记录时,仅渲染第一段,第二段未显示。

比如数据示例里的Luke Sky,实际有4段工时数据,但柱状图只显示3段堆叠,其中6.5的工时未被统计。推测问题出在prj.data.push(empPrj[0].employeeBookedMonths)仅获取同一项目的第一段工时,需要修改代码以累加同一项目的所有工时数据。


Controller代码

@GetMapping("/chart")
public String viewChartPage(Model model) {
//  List<Employee> employees = employeeService.getAllEmployees();
    ChartYear year = new ChartYear("2023");
    List<Employee> employees = employeeService.getAllEmployeesByEmployeeProjectStartDate(year);
    List<String> yearList = employeeProjectService.findAllStartAndEndDatesByYear();
    List<Project> projects = projectService.getAListOfProjectsAndTheirPersonMonthsByYear(year);
    model.addAttribute("employees", employees);
    model.addAttribute("projects", projects);
    model.addAttribute("yearList", yearList);
    model.addAttribute("currentYear", Year.now());
    return "chart.html";
}

图表JavaScript代码

const labels = listEmployees.reduce(function(result, item) {
  result.push(item.name);
  return result;
}, []);

const randomColorGenerator = function () {
  return '#' + (Math.random().toString(16) + '0000000').slice(2, 8);
};

const projects = listEmployees.reduce(function(result, item) {
  item.employeeProjects.forEach(function(prj){
    const prjId = prj.project.id;
    if (!result[prjId]) {
      result[prjId] = {
        label: prj.project.name,
        data: [],
        backgroundColor: randomColorGenerator()
      };
    }
  });
  return result;
}, {});

listEmployees.forEach(function(item) {
  for (const prjId of Object.keys(projects)) {
    const prj = projects[prjId];
    const empPrj = item.employeeProjects.filter(el => el.project.id === parseFloat(prjId));
    if (empPrj.length) {
      // 原代码只取第一个记录,现在改为累加所有匹配项目的工时
      const totalMonths = empPrj.reduce((sum, record) => sum + record.employeeBookedMonths, 0);
      prj.data.push(totalMonths);
    } else {
      prj.data.push(0);
    }
  }
});

const horizontalArbitraryLine = {
    id: 'horizontalArbitraryLine',
    beforeDraw(chart, args, options) {
        const {ctx, chartArea: {top, right, bottom, left, width, height}, scales:
        {x, y} } = chart;
        ctx.save();
        ctx.strokeStyle = 'red';
        ctx.strokeRect(left, y.getPixelForValue(12), width, 1);

        console.log(y.getPixelForValue(3))
    }
};

const ctx = document.getElementById("myChart");
const myChart = new Chart(ctx, {
  type: 'bar',
  data: {
    labels: labels,
    datasets: Object.values(projects)
  },
  options: {
    scales: {
      x: {
        stacked: true
      },
      y: {
        stacked: true
      }
    }
  },
  plugins: [horizontalArbitraryLine]
});

HTML代码

<div class="container">
        <div class="d-flex">
            <h2>Chart for [[${currentYear}]]</h2>
        </div>
        <div class="d-flex">
            <div id="selectYear" class="p-2">
                <p>Select Sheet Year:</p>
            </div>
            <div class="p-2">
                <form th:action="@{/ines/chart}" th:object="${chartYear}" method="post">
                    <select class="form-control" id="year" name="year" onchange="this.form.submit()">
                        <option></option>
                        <option th:each="year : ${yearList}" th:value="${year}"
                                th:text="${year}" th:selected="${year == currentYear}">

                        </option>
                    </select>
                </form>
            </div>
        </div>
        <div class="myChartDiv">
            <canvas id="myChart" width="900" height="600"></canvas>
        </div>

    <div>
        <canvas id="myBarChart"></canvas>
    </div>

</div>

CSS代码

.myChartDiv {
  max-width: 800px;
  max-height: 600px;
}

#year {
    width: 85px;
    height: 35px;
}

数据示例

[Employee{id=7, name='Luke Sky', contractedFrom='2022-01-15', contractedTo='2025-04-30', employeeProjects=[EmployeeProject{id=24, project=Project{id=1, projectNumber=12345, name='Test Project', startDate='2022-02-01', endDate='2024-08-28', projectLengthInMonths=31.0, currentBookedMonths=32.5, remainingBookedMonths=-30.5, numberOfEmployees=0, comment='31 months'}, employeeBookedMonths=1.0, employeeProjectStartDate=2023-01-01, employeeProjectEndDate=2023-01-31, projectName='Test Project'}, EmployeeProject{id=25, project=Project{id=2, projectNumber=54321, name='Test Project 2', startDate='2021-12-01', endDate='2023-02-28', projectLengthInMonths=15.0, currentBookedMonths=19.5, remainingBookedMonths=-16.5, numberOfEmployees=0, comment='Comment 15 months'}, employeeBookedMonths=0.5, employeeProjectStartDate=2023-04-01, employeeProjectEndDate=2023-04-14, projectName='Test Project 2'}, EmployeeProject{id=27, project=Project{id=1, projectNumber=12345, name='Test Project', startDate='2022-02-01', endDate='2024-08-28', projectLengthInMonths=31.0, currentBookedMonths=32.5, remainingBookedMonths=-30.5, numberOfEmployees=0, comment='31 months'}, employeeBookedMonths=6.5, employeeProjectStartDate=2023-04-15, employeeProjectEndDate=2023-10-31, projectName='Test Project'}, EmployeeProject{id=26, project=Project{id=3, projectNumber=2468, name='Test Project 3', startDate='2022-02-01', endDate='2025-04-30', projectLengthInMonths=39.0, currentBookedMonths=45.0, remainingBookedMonths=-42.0, numberOfEmployees=0, comment='Comment 39 months'}, employeeBookedMonths=1.5, employeeProjectStartDate=2023-11-15, employeeProjectEndDate=2023-12-31, projectName='Test Project 3'}], projectIds=null, startDates=null, endDates=null}]

修改说明

原代码中prj.data.push(empPrj[0].employeeBookedMonths)仅提取了该员工当前项目的第一条工时记录,现在改用reduce方法累加所有匹配项目的employeeBookedMonths值,得到该员工在该项目下的总工时,再推入数据数组中。这样同一项目的多段工时就会被合并统计,堆叠柱状图就能正确显示总工时了。

内容的提问来源于stack exchange,提问作者Kayd Anderson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 17:22:29