i'm having a problem with my stacked chart, as you can see in my first image, and second image, some bars is starting in the middle of other bars making my datalabels beeing invading other bar.
If you look the first image you will se two bars stacked, the first colum you can see that all bars starts when the previus bar ends, but in the second column the third stacked bar starts in the middle of second bar, making the unusual layout as you can see on the second image, and it only happens if the value is 0 what could I do to prevent it and allways start a new bar inthe end of previus bar
Here is my source code, the version of my Chartjs is 4.2.1
import { EChartTheme } from '@/enums'
import { numberFormat } from '@/helpers'
import { ChartData, ChartDataset, ChartOptions } from 'chart.js'
import { RefObject } from 'react'
import { Chart as PrimeChart } from 'primereact/chart'
interface DailyMapChartProps {
chartRef: RefObject<PrimeChart>
lineNumbers: number[]
chartBottomLabels: string[][]
chartTopLabels: string[]
maxColumnsPerView: number
freeTimeData: number[]
markedTimeData: number[]
bloquedTimeData: number[]
}
export const useDailyMapChart = ({
chartRef,
lineNumbers,
chartBottomLabels,
chartTopLabels,
maxColumnsPerView,
bloquedTimeData,
freeTimeData,
markedTimeData,
}: DailyMapChartProps) => {
const findNearestTo5 = (numbers: number[]): number => {
// Initialize variables to keep track of the closest number and its difference from 5.0
let closestNumber: number | null = null
let minDifference: number = Infinity
// Iterate through the array of numbers
for (const num of numbers) {
// Calculate the absolute difference between the current number and 5.0
const difference: number = Math.abs(num - 5.0)
// Check if the current number is closer to 5.0 than the previous closest number
if (difference < minDifference) {
closestNumber = num
minDifference = difference
}
}
// Return the closest number to 5.0
return closestNumber!
}
const nearestToFiveNumer = findNearestTo5(lineNumbers)
let newData: ChartDataset[] = [
{
data: freeTimeData,
backgroundColor: EChartTheme.LIGHT_GREEN_BAR,
barThickness: 30,
minBarLength: 25,
},
{
data: markedTimeData,
backgroundColor: EChartTheme.LIGHT_BLUE_BAR,
barThickness: 30,
minBarLength: 25,
},
{
data: bloquedTimeData,
backgroundColor: EChartTheme.ORANGE_BAR,
barThickness: 30,
minBarLength: 25,
borderRadius: 2,
},
{
data: lineNumbers,
borderColor: EChartTheme.LIGHT_RED_BAR,
type: 'line',
tension: 0.3,
datalabels: {
align: 'right',
offset: 8,
borderRadius: 2,
font: {
size: 11,
},
backgroundColor: EChartTheme.LIGHT_GRAY,
padding: 2,
borderWidth: 2,
borderColor: (context: any) => {
const currentNumber = lineNumbers[context.dataIndex]
if (currentNumber == nearestToFiveNumer) return EChartTheme.RED_BAR
return ''
},
formatter(value, context) {
const formated = numberFormat(value, 1)
return `${formated.replace(',', '.')}%`
},
},
},
]
const data: ChartData = {
labels: chartBottomLabels,
datasets: newData,
}
const options: ChartOptions = {
maintainAspectRatio: false,
plugins: {
datalabels: {
anchor: 'end',
align: 'end',
offset: -25,
font: {
size: 12,
weight: 'bold',
},
},
legend: {
display: false,
},
tooltip: {
backgroundColor: EChartTheme.TOOLTIP_BACKGROUND,
callbacks: {
title: tooltipItems => tooltipItems[0].label.replaceAll(',', ' - '),
},
},
},
scales: {
x: {
beginAtZero: true,
stacked: true,
grid: {
display: false,
},
min: 0,
max: maxColumnsPerView,
},
y: {
stacked: true,
beginAtZero: true,
grid: {
display: false,
},
ticks: {
display: false,
},
border: {
display: false,
},
},
secondYAxis: {
axis: 'x',
position: 'top',
labels: chartTopLabels,
min: 0,
max: maxColumnsPerView,
ticks: {
font: {
size: 14,
weight: 'bold',
},
},
grid: {
display: false,
},
border: {
display: false,
},
},
},
}
// ChartScroll with mouse wheel
const scroller = (scrollEvent: WheelEvent) => {
const { deltaY } = scrollEvent
scrollEvent.preventDefault()
if (data.labels!.length < maxColumnsPerView) return
if (deltaY > 0 && chartRef != null && chartRef.current != null) {
const chart = chartRef.current.getChart()
const options = chartRef.current.props.options as any
if (options.scales.x.min == data.labels!.length - maxColumnsPerView - 1) {
return
}
if (options.scales.x.max >= data.labels!.length) return
options.scales.x.min += 1
options.scales.x.max += 1
options.scales.secondYAxis.min += 1
options.scales.secondYAxis.max += 1
chart.update()
} else if (deltaY < 0 && chartRef != null && chartRef.current != null) {
const chart = chartRef.current.getChart()
const options = chartRef.current.props.options as any
if (options.scales.x.min == 0) return
options.scales.x.min -= 1
options.scales.x.max -= 1
options.scales.secondYAxis.min -= 1
options.scales.secondYAxis.max -= 1
chart.update()
}
}
// ChartScroll with touch click
const touchChartChange = (event: TouchEvent) => {
const screenY = event.touches[0].screenY
const clickPosition = event.touches[0].clientX
if (data.labels!.length < maxColumnsPerView) return
if (chartRef != null && chartRef.current != null) {
// Se o click deu menos que a metade da tela entao ele clickou na esquerda
if (clickPosition < screenY / 2) {
const chart = chartRef.current.getChart()
const options = chartRef.current.props.options as any
if (options.scales.x.min == 0) return
options.scales.x.min -= 1
options.scales.x.max -= 1
options.scales.secondYAxis.min -= 1
options.scales.secondYAxis.max -= 1
chart.update()
} else {
const chart = chartRef.current.getChart()
const options = chartRef.current.props.options as any
if (
options.scales.x.min ==
data.labels!.length - maxColumnsPerView - 1
) {
return
}
if (options.scales.x.max >= data.labels!.length) return
options.scales.x.min += 1
options.scales.x.max += 1
options.scales.secondYAxis.min += 1
options.scales.secondYAxis.max += 1
chart.update()
}
}
}
const addListeners = () => {
const chart = chartRef?.current?.getCanvas()
chart?.addEventListener('wheel', scroller, { once: true })
chart?.addEventListener('touchstart', touchChartChange, { once: true })
}
return {
data,
options,
addListeners,
}
}
I tred to change the stacked properties and beginAtZero true and false