Search code examples
reactjstypescriptchart.jstooltip

Custom tooltip doesn't show for horizontal bars which cover 100% of the horizontal length of the entire chart (react chart js)


I am using v4 of react chart js. The chart type is horizontal bar and I am using a custom tooltip to generate the tooltip but the tooltip is not displaying for bars which have 100% bar length. Initially, I was using

interaction: 'y' as const

but then changed it to

interaction: { mode: 'index' as const, axis: 'y' as const },

The reason for this was, we changed to display only 1 dataset and so sometimes the bar would not be 100% in length. In these cases if I hovered over the unused chart area corresponding to a bar, then the tooltip would just crash.

The following is the complete code for setting the bar options

export const options = (
    xAxisTitle: string,
    yAxisTitle: string,
    spendLabel: string,
    balanceLabel: string,
    langIetf: string
) => ({
    animation: false as const,
    indexAxis: 'y' as const,
    responsive: true,
    aspectRatio: 2,
    scales: {
        x: {
            stacked: true,
            display: true,
            title: {
                display: true,
                text: xAxisTitle,
                font: {
                    weight: 'bold' as const
                }
            },
            ticks: {
                stepSize: 20,
                callback(value: any) {
                    return `${value}%`;
                }
            },
            min: 0,
            max: 100
        },
        y: {
            stacked: true,
            display: true,
            title: {
                display: true,
                text: yAxisTitle,
                font: {
                    weight: 'bold' as const
                }
            },
            ticks: {
                // For a category axis, the val is the index so the lookup via getLabelForValue is needed
                callback(value: any): string {
                    const newThis = this as any;
                    if (newThis.getLabelForValue(value).length > 10) {
                        return `${newThis.getLabelForValue(value).substring(0, 10)}...`;
                    }
                    return newThis.getLabelForValue(value);
                }
            },
            grid: {
                drawOnChartArea: false,
                drawTicks: false
            }
        }
    },
    interaction: {
        mode: 'index' as const,
        axis: 'xy' as const
    },
    plugins: {
        legend: {
            display: false
        },
        tooltip: {
            enabled: false,
            external(context: any) {
                const dir = document.querySelector('html')?.getAttribute('dir');
                const { tooltipEl, tooltipCaret } = generateTooltipElement();
                const { tooltip, chart } = context;

                // Hide if no tooltip
                if (tooltip.opacity === 0) {
                    hideTooltip(tooltipEl, tooltipCaret);
                    return;
                }

                const labelArr: string[] = [spendLabel, balanceLabel, xAxisTitle];
                // Set HTML & Data
                if (tooltip.body) {
                    const innerHtml = setTooltipBody(tooltip, chart, labelArr, langIetf, dir!);
                    tooltipEl!.querySelector('table')!.innerHTML = innerHtml;
                }
                if (tooltipEl !== null && tooltipCaret !== null) {
                    setTooltipStyles(tooltip, tooltipEl, tooltipCaret, chart);
                }
            }
        }
    }
});

Any ideas on the above? Is this the correct way to go about it or is there a better way?

My first obstacle was to not let the tooltip crash when the user hovered over an empty bar area

I tried to work with the configurable options. The interaction solved the above issue but has introduced a new issue where the tooltip just won't display for bars with 100% length so in the attached image the tooltip doesn't show for the 1st two bars.

horizontal bar


Solution

  • Figured out what was wrong - it was how I was passing the data to generate the charts. I needed to display only 1 dataset on the chart but needed to show more data on the tooltip. I was doing that by passing in all the data to the chart object but keeping the other data as transparent. I solved this issue by passing in additional data which was required to be displayed in the custom tooltip as follows

    const chartData = {
            labels: dataLabels,
            datasets: [
                /* passing in additional datasets for spend and balance as below so that they are not plotted on the chart 
                   but can be used to display the required information in the tooltip */
                {
                    label: usageLabel,
                    data: currentUsageDataset,
                    backgroundColor: humanity60,
                    hoverBackgroundColor: humanity60,
                    barThickness: 24,
                    [spendLabel]: spendDataset,
                    [balanceLabel]: actualBalanceDataset
                }
            ]
        };
    

    The

    spendDataset

    and

    actualBalanceDataset

    is the additional data. After that I changed the interaction back to

    interaction: 'y' as const

    and it worked perfectly.