[Chartjs]-Charts.js tooltip overlapping text on chart

1πŸ‘

βœ…

For anyone interested I managed to figure this out, I ended up looking at the tooltip drawing functions within charts.js and using a modified version of this as a custom tooltip, thus drawing the tooltip after the annotations are added.

First add this to your opptions

config = {
    options: {
        tooltips: {
            enabled: false,
            custom: customTooltips
        }

This then calls the custom tooltip function below.

var currentX = null;
var currentY = null;

var customTooltips = function (tooltip) {

    var helpers = Chart.helpers;
    var ctx = this._chart.ctx;
    var vm = this._view;

    if (vm == null || ctx == null || helpers == null || vm.opacity === 0) {
        return;
    }

    var tooltipSize = this.getTooltipSize(vm);

    var pt = {
        x: vm.x,
        y: vm.y
    };

    if (currentX == vm.x && currentY == vm.y) {
        return;
    }

    currentX = vm.x;
    currentY = vm.y;

   //  IE11/Edge does not like very small opacities, so snap to 0
    var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;

    // Draw Background
    var bgColor = helpers.color(vm.backgroundColor);
    ctx.fillStyle = bgColor.alpha(opacity * bgColor.alpha()).rgbString();
    helpers.drawRoundedRectangle(ctx, pt.x, pt.y, tooltipSize.width, tooltipSize.height, vm.cornerRadius);
    ctx.fill();

    // Draw Caret
    this.drawCaret(pt, tooltipSize, opacity);

    // Draw Title, Body, and Footer
    pt.x += vm.xPadding;
    pt.y += vm.yPadding;

    // Titles
    this.drawTitle(pt, vm, ctx, opacity);

    // Body
    this.drawBody(pt, vm, ctx, opacity);

    // Footer
    this.drawFooter(pt, vm, ctx, opacity);
};

6πŸ‘

@user3284707 Actually what you have to do is draw the numbers on top of your bars before the tooltips, you are drawing them onComplete, putting them on top of everything.

I draw those numbers using:

Chart.plugins.register({
  beforeDraw: function(chartInstance) {
    if (chartInstance.config.options.showDatapoints) {
      var helpers = Chart.helpers;
      var ctx = chartInstance.chart.ctx;
      var fontColor = helpers.getValueOrDefault(chartInstance.config.options.showDatapoints.fontColor, chartInstance.config.options.defaultFontColor);

      // render the value of the chart above the bar
      ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, 'normal', Chart.defaults.global.defaultFontFamily);
      ctx.textAlign = 'center';
      ctx.textBaseline = 'bottom';
      ctx.fillStyle = fontColor;

      chartInstance.data.datasets.forEach(function (dataset) {
        for (var i = 0; i < dataset.data.length; i++) {
          var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model;
          var scaleMax = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._yScale.maxHeight;
          var yPos = (scaleMax - model.y) / scaleMax >= 0.93 ? model.y + 20 : model.y - 5;
          var label = dataset.data[i] || '';
          ctx.fillText(label.toLocaleString(), model.x, yPos);
        }
      });
    }
  }
});

Notice the beforeDraw there.

Hope this helps 3 years later, I spent the last 30 minutes trying to fix this 🀣

2πŸ‘

If you are looking for the close solution of the code written in the question then here is the code:

let ctx2 = document.getElementById("barChart").getContext("2d");

        let chart = new Chart(ctx2, {
            type: 'bar',
            data: {
                labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
                datasets: [{
                    label: 'Months',
                    data: ['20','30','10','15','50','35','25'],
                    backgroundColor: 'rgba(26,179,148,0.5)',
                    borderColor: 'rgba(75, 192, 192, 1)',
                    borderWidth: 1
                }]
            },
            options: {
                legend: {
                    display: false
                },
                responsive: true,
                maintainAspectRatio: true,
                legendCallback: function(chart) {
                    var text = [];
                    for (var i=0; i<chart.data.datasets.length; i++) {
                        text.push(chart.data.labels[i]);
                    }
                    return text.join("");
                },
                tooltips: {
                    mode: 'index',
                    callbacks: {
                        // Use the footer callback to display the sum of the items showing in the tooltip
                        title: function(tooltipItem, data) {
                            let title_str = data['labels'][tooltipItem[0]['index']];
                            let lastIndex = title_str.lastIndexOf(" ");
                            return title_str.substring(0, lastIndex);
                        },
                        label: function(tooltipItem, data) {
                            return 'val: '+data['datasets'][0]['data'][tooltipItem['index']];
                        },
                    },
                },

                scales: {
                    xAxes: [{
                        stacked: false,
                        beginAtZero: true,
                        // scaleLabel: {
                        //     labelString: 'Month'
                        // },
                        ticks: {
                            min: 0,
                            autoSkip: false,
                            maxRotation: 60,
                            callback: function(label, index, labels) {
                                return label;
                            }
                        }
                    }]
                }
            },
            plugins:[{
                afterDatasetsDraw: function(chart,options) {
                    // var chartInstance = chart,
                    let ctx = chart.ctx;
                    ctx.font = Chart.defaults.global.defaultFontStyle;
                    ctx.fillStyle = Chart.defaults.global.textColor;
                    ctx.textAlign = "center";
                    ctx.textBaseline = "bottom";

                    chart.data.datasets.forEach(function (dataset, i) {
                        var meta = chart.controller.getDatasetMeta(i);
                        meta.data.forEach(function (bar, index) {
                            ctx.fillText(Math.round(dataset.data[index]), bar._model.x, bar._model.y - 5);
                        });
                    })
                }
            }]
        });
        document.getElementById('barChart').innerHTML = chart.generateLegend();
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
    <canvas id="barChart" height="140"></canvas>
</div>

Here, I have made use of plugin afterDatasetDraw. https://www.chartjs.org/docs/latest/developers/plugins.html?h=afterdatasetsdraw

1πŸ‘

If anyone would look for the solution for this issue, there is an easier way of achieving what OP needed.

Instead of drawing in onComplete callback, draw it in afterDatasetsDraw callback. It’s being called just before the tooltip gets drawn.

Leave a comment