Search code examples
javascripttypescriptchart.jsreact-chartjs

ChartJS: Grouped Stacked Bar Chart rendering incorrectly


I am trying to use ChartJS to create a grouped stacked bar chart. In my dataset, there are 2 groups: Groups A and Groups B. Groups A are rendered out correctly, but for some reason, Groups B (gray bars) are not. This can be seen here: Incorrect bar chart rendering The datasets are prepared as follows:

function createDataSets() {
    const billable: { [projectId: string]: { projectName: string; data: number[] } } = {};
    const nonBillable: { [projectId: string]: { projectName: string; data: number[] } } = {};
    for (const [overviewIndex, overview] of props.userOverviews.entries()) {
        for (const [projectId, projectOverview] of Object.entries(overview.timeWorkedPerProject)) {
            const projectName = props.getProjectData(projectId).name;

            // Billable Dataset
            if (!billable[projectId]) {
                billable[projectId] = { projectName, data: Array<number>(props.userOverviews.length).fill(0) };
            }
            billable[projectId].data[overviewIndex] = projectOverview.billableMins;

            // Non-Billable Dataset
            if (!nonBillable[projectId]) {
                nonBillable[projectId] = { projectName, data: Array<number>(props.userOverviews.length).fill(0) };
            }
            nonBillable[projectId].data[overviewIndex] = projectOverview.nonBillableMins;
        }
    }
    return [
        ...Object.values(billable).map(({ projectName, data }) => ({
            data,
            label: projectName,
            stack: "billable",
        })),
        ...Object.values(nonBillable).map(({ projectName, data }) => ({
            data,
            label: projectName,
            stack: "non_billable",
        })),
    ];
}

The datasets do seem to be correct. For example, they indicate that Groups B (the gray bars) should have multiple items. But, as can be seen in the image, only one item is rendered.

What is strange is that the rendered totals for Groups B are correct. The bar marked in the image is expected to reach a total of 480. The bar does indeed seem to render to 480, though the tooltip only shows 270 which is only one of the expected items in the group.

My suspicion is that Groups B only render a single item, although I am not sure why. If use the exact same function to prepare the data, but just render Groups B (the gray bars), it does indicate that the data is prepared correctly:

return [
    // ...Object.values(billable).map(({ projectName, data }) => ({
    //     data,
    //     label: projectName,
    //     stack: "billable",
    // })),
    ...Object.values(nonBillable).map(({ projectName, data }) => ({
        data,
        label: projectName,
        stack: "non_billable",
    })),
];

Results in: enter image description here

I use react-chartjs-2 to render everything. This is done as follows:

<Bar
    height={400}
    options={{
        maintainAspectRatio: false,
        responsive: true,
        plugins: {
            legend: {
                display: false,
            },
        },
        scales: {
            x: {
                stacked: true,
                beginAtZero: true,
            },
            y: {
                stacked: true,
                type: "category",
                position: "right"
            },
        },
        indexAxis: "y",
    }}
    data={{
        labels: props.userOverviews.map((overview) => overview.fullUserName + overview.fullUserName),
        datasets: createDataSets(),
    }}
/>

I have been debugging this for a while, but can't seem to figure it out. Anything I am overlooking?


Solution

  • The solution ended up being a lot easier than expected! ChartJS simply ran out of colors and displayed all items in Groups B as gray. As the gray bars just appeared as one continuous line, I did not notice.
    I was able to verify this by supplying a set of colors large enough for my data set:

    return [
        ...Object.values(billable).map(({ projectName, data }) => ({
            data,
            label: projectName,
            stack: "billable",
            backgroundColor: [
                "rgba(219, 80, 74, 0.85)",
                "rgba(4, 80, 74, 0.85)",
                "rgba(10, 219, 135, 0.85)",
                "rgba(84, 74, 83, 0.85)",
                "rgba(173, 219, 74, 0.85)",
                "rgba(96, 74, 219, 0.85)",
                "rgba(219, 80, 74, 0.85)",
                "rgba(219, 195, 74, 0.85)",
                "rgba(156, 140, 165, 0.85)",
                "rgba(60, 54, 54, 0.85)",
            ][Math.floor(Math.random() * 10)],
        })),
        ...Object.values(nonBillable).map(({ projectName, data }) => ({
            data,
            label: projectName,
            stack: "non_billable",
            backgroundColor: [
                "rgba(219, 80, 74, 0.85)",
                "rgba(4, 80, 74, 0.85)",
                "rgba(10, 219, 135, 0.85)",
                "rgba(84, 74, 83, 0.85)",
                "rgba(173, 219, 74, 0.85)",
                "rgba(96, 74, 219, 0.85)",
                "rgba(219, 80, 74, 0.85)",
                "rgba(219, 195, 74, 0.85)",
                "rgba(156, 140, 165, 0.85)",
                "rgba(60, 54, 54, 0.85)",
            ][Math.floor(Math.random() * 10)],
        })),
    ];