I tried all the options but the end bars are close to the border and other bars are not center. I tried using all the options for the horizontal chart from chart.js documentation but the bars are not aligning to the center. New to using the library and not able to find the cause of the issue.
By default the bars are aligning to the borders
JS used:
console.log('script.js loaded');
// Fetch test execution data from the JSON file
fetch('test-execution-data.json')
.then((response) => {
if (!response.ok) {
throw new Error('Failed to fetch test execution data');
}
return response.json();
})
.then((testExecutionData) => {
// Debug: Log the fetched data
console.log('Fetched test execution data:', testExecutionData);
// Group tests by worker
const workers = {};
testExecutionData.forEach((test) => {
const parallelIndex = test.parallelIndex.toString();
if (!workers[parallelIndex]) {
workers[parallelIndex] = [];
}
workers[parallelIndex].push({
label: test.title,
startTime: new Date(test.startTime),
endTime: new Date(test.endTime),
status: test.status[0], // Get the first status
attempt: test.attempt,
totalAttempts: test.totalAttempts
});
});
// Calculate relative timestamps (in minutes from the earliest test)
const allTimes = testExecutionData.flatMap((test) => [
new Date(test.startTime).getTime(),
new Date(test.endTime).getTime()
]);
const minTimestamp = Math.min(...allTimes);
const maxTimestamp = Math.max(...allTimes);
const timeRangeInMinutes = (maxTimestamp - minTimestamp) / (1000 * 60);
// Create datasets
const datasets = Object.keys(workers).map((worker) => ({
label: `Worker ${worker}`,
data: workers[worker].map((test) => ({
x: [
(test.startTime.getTime() - minTimestamp) / (1000 * 60),
(test.endTime.getTime() - minTimestamp) / (1000 * 60)
],
y: worker,
label: test.label,
start: test.startTime.toLocaleTimeString(),
end: test.endTime.toLocaleTimeString(),
status: test.status,
attempt: test.attempt,
totalAttempts: test.totalAttempts
})),
backgroundColor: (context) => {
const status = context.raw.status;
const attempt = context.raw.attempt;
const totalAttempts = context.raw.totalAttempts;
if (status === 'passed') {
return 'rgba(75, 192, 192, 0.5)';
} else if (attempt < totalAttempts) {
return 'rgba(255, 159, 64, 0.5)'; // Orange for retried failures
} else {
return 'rgba(255, 99, 132, 0.5)'; // Red for final failure
}
},
borderColor: (context) => {
const status = context.raw.status;
const attempt = context.raw.attempt;
const totalAttempts = context.raw.totalAttempts;
if (status === 'passed') {
return 'rgba(75, 192, 192, 1)';
} else if (attempt < totalAttempts) {
return 'rgba(255, 159, 64, 1)'; // Orange for retried failures
} else {
return 'rgba(255, 99, 132, 1)'; // Red for final failure
}
},
borderWidth: 1, // Increased from 1 to 3
borderSkipped: false, // This ensures borders are drawn on all sides
borderRadius: 2, // Optional: slightly rounded corners
barThickness: 5
}));
// Render chart
const ctx = document.getElementById('timelineChart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: { datasets },
options: {
responsive: true,
maintainAspectRatio: false,
indexAxis: 'y',
scales: {
x: {
type: 'linear',
position: 'bottom',
min: 0,
max: Math.ceil(timeRangeInMinutes),
title: {
display: true,
text: 'Time (minutes from start)'
},
ticks: {
stepSize: Math.max(1, Math.ceil(timeRangeInMinutes / 15)), // At most 15 ticks
callback: (value) => {
const date = new Date(value * 60 * 1000 + minTimestamp);
return date.toLocaleTimeString();
}
}
},
y: {
// type: 'category', // Use category scale for discrete worker indices
// labels: Object.keys(workers), // Use worker indices as labels
title: {
display: true,
text: 'Worker'
}
// offset: true // Add some padding to center the bars
}
},
plugins: {
tooltip: {
callbacks: {
title: (context) => context[0].raw.label,
label: (context) => {
const { start, end, status } = context.raw;
return [`Status: ${status}`, `Start: ${start}`, `End: ${end}`];
}
}
},
legend: {
display: false
}
}
}
});
})
.catch((error) => {
// Debug: Log any errors
console.error('Error loading test execution data:', error);
});
Sample input data:
[
{
"title": "@workflow @tagAdded @AUTOMATIONS_WORKFLOW_SMOKE_TRIGGER DND enabled for all<> All Filter - Verify that when DND enabled trigger should fire",
"parallelIndex": 3,
"startTime": "2025-02-04T10:10:21.503Z",
"endTime": "2025-02-04T10:10:33.748Z",
"status": ["passed"],
"retries": 0,
"attempt": 1,
"totalAttempts": 1,
"isRetry": false
},
{
"title": "@workflow @tagAdded @whatsapp DND enabled specific for Whatsapp - Verify that when DND enabled for Whatsapp trigger should fire",
"parallelIndex": 5,
"startTime": "2025-02-04T10:10:21.546Z",
"endTime": "2025-02-04T10:10:27.457Z",
"status": ["passed"],
"retries": 0,
"attempt": 1,
"totalAttempts": 1,
"isRetry": false
}
]
Chart image
Can someone please help
I tried all the options but the end bars are close to the border and other bars are not center. I tried using all the options for the horizontal chart from chart.js documentation but the bars are not aligning to the center. New to using the library and not able to find the cause of the issue.
By default the bars are aligning to the borders
JS used:
console.log('script.js loaded');
// Fetch test execution data from the JSON file
fetch('test-execution-data.json')
.then((response) => {
if (!response.ok) {
throw new Error('Failed to fetch test execution data');
}
return response.json();
})
.then((testExecutionData) => {
// Debug: Log the fetched data
console.log('Fetched test execution data:', testExecutionData);
// Group tests by worker
const workers = {};
testExecutionData.forEach((test) => {
const parallelIndex = test.parallelIndex.toString();
if (!workers[parallelIndex]) {
workers[parallelIndex] = [];
}
workers[parallelIndex].push({
label: test.title,
startTime: new Date(test.startTime),
endTime: new Date(test.endTime),
status: test.status[0], // Get the first status
attempt: test.attempt,
totalAttempts: test.totalAttempts
});
});
// Calculate relative timestamps (in minutes from the earliest test)
const allTimes = testExecutionData.flatMap((test) => [
new Date(test.startTime).getTime(),
new Date(test.endTime).getTime()
]);
const minTimestamp = Math.min(...allTimes);
const maxTimestamp = Math.max(...allTimes);
const timeRangeInMinutes = (maxTimestamp - minTimestamp) / (1000 * 60);
// Create datasets
const datasets = Object.keys(workers).map((worker) => ({
label: `Worker ${worker}`,
data: workers[worker].map((test) => ({
x: [
(test.startTime.getTime() - minTimestamp) / (1000 * 60),
(test.endTime.getTime() - minTimestamp) / (1000 * 60)
],
y: worker,
label: test.label,
start: test.startTime.toLocaleTimeString(),
end: test.endTime.toLocaleTimeString(),
status: test.status,
attempt: test.attempt,
totalAttempts: test.totalAttempts
})),
backgroundColor: (context) => {
const status = context.raw.status;
const attempt = context.raw.attempt;
const totalAttempts = context.raw.totalAttempts;
if (status === 'passed') {
return 'rgba(75, 192, 192, 0.5)';
} else if (attempt < totalAttempts) {
return 'rgba(255, 159, 64, 0.5)'; // Orange for retried failures
} else {
return 'rgba(255, 99, 132, 0.5)'; // Red for final failure
}
},
borderColor: (context) => {
const status = context.raw.status;
const attempt = context.raw.attempt;
const totalAttempts = context.raw.totalAttempts;
if (status === 'passed') {
return 'rgba(75, 192, 192, 1)';
} else if (attempt < totalAttempts) {
return 'rgba(255, 159, 64, 1)'; // Orange for retried failures
} else {
return 'rgba(255, 99, 132, 1)'; // Red for final failure
}
},
borderWidth: 1, // Increased from 1 to 3
borderSkipped: false, // This ensures borders are drawn on all sides
borderRadius: 2, // Optional: slightly rounded corners
barThickness: 5
}));
// Render chart
const ctx = document.getElementById('timelineChart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: { datasets },
options: {
responsive: true,
maintainAspectRatio: false,
indexAxis: 'y',
scales: {
x: {
type: 'linear',
position: 'bottom',
min: 0,
max: Math.ceil(timeRangeInMinutes),
title: {
display: true,
text: 'Time (minutes from start)'
},
ticks: {
stepSize: Math.max(1, Math.ceil(timeRangeInMinutes / 15)), // At most 15 ticks
callback: (value) => {
const date = new Date(value * 60 * 1000 + minTimestamp);
return date.toLocaleTimeString();
}
}
},
y: {
// type: 'category', // Use category scale for discrete worker indices
// labels: Object.keys(workers), // Use worker indices as labels
title: {
display: true,
text: 'Worker'
}
// offset: true // Add some padding to center the bars
}
},
plugins: {
tooltip: {
callbacks: {
title: (context) => context[0].raw.label,
label: (context) => {
const { start, end, status } = context.raw;
return [`Status: ${status}`, `Start: ${start}`, `End: ${end}`];
}
}
},
legend: {
display: false
}
}
}
});
})
.catch((error) => {
// Debug: Log any errors
console.error('Error loading test execution data:', error);
});
Sample input data:
[
{
"title": "@workflow @tagAdded @AUTOMATIONS_WORKFLOW_SMOKE_TRIGGER DND enabled for all<> All Filter - Verify that when DND enabled trigger should fire",
"parallelIndex": 3,
"startTime": "2025-02-04T10:10:21.503Z",
"endTime": "2025-02-04T10:10:33.748Z",
"status": ["passed"],
"retries": 0,
"attempt": 1,
"totalAttempts": 1,
"isRetry": false
},
{
"title": "@workflow @tagAdded @whatsapp DND enabled specific for Whatsapp - Verify that when DND enabled for Whatsapp trigger should fire",
"parallelIndex": 5,
"startTime": "2025-02-04T10:10:21.546Z",
"endTime": "2025-02-04T10:10:27.457Z",
"status": ["passed"],
"retries": 0,
"attempt": 1,
"totalAttempts": 1,
"isRetry": false
}
]
Chart image
Can someone please help
I dont kknow exactly why this happens but my theory is that it is because of the setting grouped
that is by default true to fix this you need to set it to false within the options:
. What i believe this setting does is group the inputs together so they are available on each others y
axis if the data is provided for that paralelIndex by keeping this room prepared the divs wont center. I have used your code in the following example:
let testExecutionData = [
{
"title": "@workflow @tagAdded @AUTOMATIONS_WORKFLOW_SMOKE_TRIGGER DND enabled for all<> All Filter - Verify that when DND enabled trigger should fire",
"parallelIndex": 3,
"startTime": "2025-02-04T10:10:21.503Z",
"endTime": "2025-02-04T10:10:33.748Z",
"status": ["passed"],
"retries": 0,
"attempt": 1,
"totalAttempts": 1,
"isRetry": false
},
{
"title": "@workflow @tagAdded @whatsapp DND enabled specific for Whatsapp - Verify that when DND enabled for Whatsapp trigger should fire",
"parallelIndex": 5,
"startTime": "2025-02-04T10:10:21.546Z",
"endTime": "2025-02-04T10:10:27.457Z",
"status": ["passed"],
"retries": 0,
"attempt": 1,
"totalAttempts": 1,
"isRetry": false
}
];
// Group tests by worker
const workers = {};
testExecutionData.forEach((test) => {
const parallelIndex = test.parallelIndex.toString();
if (!workers[parallelIndex]) {
workers[parallelIndex] = [];
}
workers[parallelIndex].push({
label: test.title,
startTime: new Date(test.startTime),
endTime: new Date(test.endTime),
status: test.status[0], // Get the first status
attempt: test.attempt,
totalAttempts: test.totalAttempts
});
});
// Calculate relative timestamps (in minutes from the earliest test)
const allTimes = testExecutionData.flatMap((test) => [
new Date(test.startTime).getTime(),
new Date(test.endTime).getTime()
]);
const minTimestamp = Math.min(...allTimes);
const maxTimestamp = Math.max(...allTimes);
const timeRangeInMinutes = (maxTimestamp - minTimestamp) / (1000 * 60);
// Create datasets
const datasets = Object.keys(workers).map((worker) => ({
label: `Worker ${worker}`,
data: workers[worker].map((test) => ({
x: [
(test.startTime.getTime() - minTimestamp) / (1000 * 60),
(test.endTime.getTime() - minTimestamp) / (1000 * 60)
],
y: worker,
label: test.label,
start: test.startTime.toLocaleTimeString(),
end: test.endTime.toLocaleTimeString(),
status: test.status,
attempt: test.attempt,
totalAttempts: test.totalAttempts
})),
backgroundColor: (context) => {
const status = context.raw.status;
const attempt = context.raw.attempt;
const totalAttempts = context.raw.totalAttempts;
if (status === 'passed') {
return 'rgba(75, 192, 192, 0.5)';
} else if (attempt < totalAttempts) {
return 'rgba(255, 159, 64, 0.5)'; // Orange for retried failures
} else {
return 'rgba(255, 99, 132, 0.5)'; // Red for final failure
}
},
borderColor: (context) => {
const status = context.raw.status;
const attempt = context.raw.attempt;
const totalAttempts = context.raw.totalAttempts;
if (status === 'passed') {
return 'rgba(75, 192, 192, 1)';
} else if (attempt < totalAttempts) {
return 'rgba(255, 159, 64, 1)'; // Orange for retried failures
} else {
return 'rgba(255, 99, 132, 1)'; // Red for final failure
}
},
borderWidth: 3,
borderSkipped: false,
borderRadius: 2,
barThickness: 50
}));
// Render chart
const ctx = document.getElementById('timelineChart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {datasets}
,
options: {
responsive: true,
maintainAspectRatio: false,
indexAxis: 'y',
scales: {
x: {
type: 'linear',
position: 'bottom',
min: 0,
max: Math.ceil(timeRangeInMinutes),
title: {
display: true,
text: 'Time (minutes from start)'
},
ticks: {
stepSize: Math.max(1, Math.ceil(timeRangeInMinutes / 15)), // At most 15 ticks
callback: (value) => {
const date = new Date(value * 60 * 1000 + minTimestamp);
return date.toLocaleTimeString();
}
}
},
y: {
// type: 'category', // Use category scale for discrete worker indices
// labels: Object.keys(workers), // Use worker indices as labels
title: {
display: true,
text: 'Worker'
},
// offset: true // Add some padding to center the bars
}
},
plugins: {
tooltip: {
callbacks: {
title: (context) => context[0].raw.label,
label: (context) => {
const { start, end, status } = context.raw;
return [`Status: ${status}`, `Start: ${start}`, `End: ${end}`];
}
}
},
legend: {
display: false
}
},
grouped: false
}
});
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<canvas style="display: block; box-sizing: border-box;" height="200" width="500" id="timelineChart"></canvas>
as you can see there is an added option
grouped
that is set to false
and now al the data centers.
If this is not the answer you were searching for and there must be a possibility that they both may exist on eachothers row but if not exist center or something else that is wrong with the answer provided please comment or change the question.