I'm working on a React application that utilizes Chart.js to display bar charts. I'm encountering an issue where the tooltips in the bar chart display time data as points instead of the desired formatted HH:MM:SS. I've tried several approaches to format the tooltips correctly, but none of them have worked so far.
Details:
When hovering over the bars in the chart, the tooltips display the time data in points (e.g., 1.5, 2.3
) instead of the desired format HH:MM:SS (e.g., 01:00:00
, 02:00:00
).
I've implemented a custom tooltip callback function within the Chart.js options to convert the total seconds to HH:MM:SS format. However, this approach hasn't resolved the issue.
Code:
import {
BarElement,
CategoryScale,
Chart as ChartJS,
Legend,
LinearScale,
Title,
Tooltip,
} from 'chart.js';
import React from 'react';
import { Bar } from 'react-chartjs-2';
ChartJS.register(
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend
);
export function convertTimeFormat(fractionalHour) {
// Convert fractional part to minutes
var minutes = Math.floor((fractionalHour % 1) * 60);
// Extract whole number part as hours
var hours = Math.floor(fractionalHour);
// Convert remaining decimal part to seconds
var seconds = Math.floor(((fractionalHour % 1) * 60 - minutes) * 60);
// Format the time as HH:MM:SS
var formattedTime = padZero(hours) + ":" + padZero(minutes) + ":" + padZero(seconds);
return formattedTime;
}
const options = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
position: 'top',
},
},
tooltip: {
callbacks: {
label: function (tooltipItem, data) {
return convertTimeFormat(tooltipItem.parsed);
},
}
},
scales: {
x: {
ticks: {
autoSkip: true,
// maxTicksLimit: 12, // Set the maximum number of x-axis ticks
},
grid: {
display: false, // Hide vertical grid lines
},
},
y: {
ticks: {
min: 0, // Set the minimum value for the y-axis
max: 20, // Set the maximum value for the y-axis
maxTicksLimit: 6, // Set the maximum number of x-axis ticks
callback: function (value, index, values) {
return value
},
},
},
},
};
// Initialize the usedColors array outside the component or wherever you are managing state
const usedColors = [];
// Function to generate a random color
function getRandomColor() {
const colors = ["#FFD147", "#BD9AF8", "#B6F09C", "#FF9C9C", "#9ADBF8", "#9CF8E8", "#F8E89A", "#F89A9A", "#F89AD3", "#9AF8B5"];
// Filter out used colors
const availableColors = colors.filter(color => !usedColors.includes(color));
// If there are available colors, use one of them; otherwise, generate a new light color
if (availableColors.length > 0) {
const randomIndex = Math.floor(Math.random() * availableColors.length);
const color = availableColors[randomIndex];
usedColors.push(color);
return color;
} else {
const lightColor = generateLightColor();
usedColors.push(lightColor);
return lightColor;
}
}
function generateLightColor() {
const letters = '89ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 8)];
}
return color;
}
export function DashGroupBarChart({ styles, barData }) {
const dates = barData?.map(entry => entry?.date);
const usernames = Array.from(new Set(barData.flatMap(entry => entry.data.map(item => item.username))));
const datasets = usernames.map(username => ({
label: username,
data: dates.map(date => {
const dateEntry = barData.find(item => item.date === date);
const userData = dateEntry ? dateEntry.data.find(item => item.username === username) : null;
return userData ? userData.totalWorkTimeInSeconds / 3600 : 0;
}),
backgroundColor: getRandomColor(),
borderWidth: 0,
barPercentage: 1,
categoryPercentage: 0.5,
borderRadius: 3,
}));
const data = { labels: dates, datasets };
return <div style={{ width: "100%", height: "100%", ...styles }}>
<Bar options={options} data={data} />
</div>;
}
DashGroupBarChart.defaultProps = {
styles: {},
barData: []
}
And the data format i am passing to CharComponent
[
{
"date": "2024-03-18",
"data": [
{
"username": "smith",
"totalWorkTimeInSeconds": 12260
},
{
"username": "alena",
"totalWorkTimeInSeconds": 12727
},
{
"username": "nadir",
"totalWorkTimeInSeconds": 2759
},
{
"username": "noman",
"totalWorkTimeInSeconds": 7878
},
{
"username": "adam",
"totalWorkTimeInSeconds": 11758
}
]
},
{
"date": "2024-03-19",
"data": [
{
"username": "alena",
"totalWorkTimeInSeconds": 31609
},
{
"username": "jhon",
"totalWorkTimeInSeconds": 1449
},
{
"username": "noman",
"totalWorkTimeInSeconds": 4745
},
{
"username": "adam",
"totalWorkTimeInSeconds": 14266
},
{
"username": "atif",
"totalWorkTimeInSeconds": 7
},
{
"username": "bob",
"totalWorkTimeInSeconds": 5086
}
]
},
{
"date": "2024-03-20",
"data": [
{
"username": "smith",
"totalWorkTimeInSeconds": 13979
},
{
"username": "alena",
"totalWorkTimeInSeconds": 1492
},
{
"username": "noman",
"totalWorkTimeInSeconds": 8335
},
{
"username": "adam",
"totalWorkTimeInSeconds": 7532
},
{
"username": "bob",
"totalWorkTimeInSeconds": 3839
}
]
},
{
"date": "2024-03-21",
"data": [
{
"username": "smith",
"totalWorkTimeInSeconds": 11300
},
{
"username": "alena",
"totalWorkTimeInSeconds": 26813
},
{
"username": "adam",
"totalWorkTimeInSeconds": 13168
},
{
"username": "bob",
"totalWorkTimeInSeconds": 1386
}
]
},
{
"date": "2024-03-22",
"data": [
{
"username": "smith",
"totalWorkTimeInSeconds": 5281
},
{
"username": "alena",
"totalWorkTimeInSeconds": 21844
},
{
"username": "jhon",
"totalWorkTimeInSeconds": 20251
},
{
"username": "adam",
"totalWorkTimeInSeconds": 1987
},
{
"username": "bob",
"totalWorkTimeInSeconds": 15102
}
]
},
{
"date": "2024-03-23",
"data": [
{
"username": "smith",
"totalWorkTimeInSeconds": 7635
},
{
"username": "bob",
"totalWorkTimeInSeconds": 14660
}
]
},
{
"date": "2024-03-24",
"data": [
{
"username": "smith",
"totalWorkTimeInSeconds": 17819
},
{
"username": "alena",
"totalWorkTimeInSeconds": 17999
},
{
"username": "jhon",
"totalWorkTimeInSeconds": 3235
},
{
"username": "bob",
"totalWorkTimeInSeconds": 10241
}
]
},
{
"date": "2024-03-25",
"data": [
{
"username": "smith",
"totalWorkTimeInSeconds": 11338
},
{
"username": "adam",
"totalWorkTimeInSeconds": 3325
}
]
}
]
Your ChartConfiguration
is incorrect, the tooltip
should be in the plugins
object but not at the same level as plugins
.
You need to get the y-axis value via tooltipItem.parsed.y
not tooltipItem.parsed
.
const options = {
responsive: true,
maintainAspectRatio: false,
plugins: {
...,
tooltip: {
callbacks: {
label: function (tooltipItem, data) {
return convertTimeFormat(tooltipItem.parsed.y);
},
},
},
}
...
};