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: 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",
})),
];
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?
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)],
})),
];