Customizing Highcharts – Tooltip Positioning

The Highcharts API Reference for tooltip.positioner reads

A callback function to place the tooltip in a default position. The callback receives three parameters: labelWidth, labelHeight and point, where point contains values for plotX and plotY telling where the reference point is in the plot area. Add chart.plotLeft and chart.plotTop to get the full coordinates. The return should be an object containing x and y values, for example { x: 100, y: 100 }.

However, positioner is much more than just the default position of the tooltip. It takes the following syntax

tooltip:{
    positioner:function(labelWidth, labelHeight, point){
        var tooltipX,tooltipY;
        // ... Calculations go here ... //
        return { x : tooltipX, y : tooltipY };
    }
}

Note the three arguments labelWidth, labelHeight & point at your disposal, these seem to be sufficient for most of the use cases to calculate a desired tooltip position. labelWidth and labelHeight are the width and height that your tooltip requires, hence you can use them for edge cases to adjust your tooltip and prevent it from spilling out of the chart or even worse getting clipped. Let us see an example of positioning the tooltip to the right of the point.

positioner: function (labelWidth, labelHeight, point) {
    var tooltipX, tooltipY;
    tooltipX = point.plotX + chart.plotLeft + 20;
    tooltipY = point.plotY + chart.plotTop - 20;
    return {
        x: tooltipX,
        y: tooltipY
    };
}

Keen eyes would have noticed a problem while hovering over the December points, the tooltip goes outside the chart area. Let’s fix this using the labelWidth, this tells us how much space would the tooltip need. Using the labelWidth along side pointX we can find the far right of the tooltip and compare that with the plot’s width, if it goes outta bounds, we position the tooltip to the left instead. Try fiddling with the rightmost points on the series.

positioner: function (labelWidth, labelHeight, point) {
    var tooltipX, tooltipY;
    if (point.plotX + labelWidth > chart.plotWidth) {
        tooltipX = point.plotX + chart.plotLeft - labelWidth - 20;
    } else {
        tooltipX = point.plotX + chart.plotLeft + 20;
    }
    tooltipY = point.plotY + chart.plotTop - 20;
    return {
        x: tooltipX,
        y: tooltipY
    };
}

Sometimes, all we may want is a static location for the tooltip, like one of the corners of the chart. At the same time, we might not want the tooltip to eclipse any datapoints. We could simply assign the bottom left corner (chart.plotLeft,chart.plotTop + chart.plotHeight – labelHeight) as the tooltip’s position, and make use of the all the three parameters to determine if the current point falls behind the tooltip’s default location, if so, move the tooltip to the top right of the point. Hover over any point and observe the tooltip stays at the corner, now hover over the point at the bottom left and see the tooltip give way to the point.

positioner: function (labelWidth, labelHeight, point) {
    var tooltipX, tooltipY;
    if (point.plotX + chart.plotLeft < labelWidth && point.plotY + labelHeight > chart.plotHeight) {
        tooltipX = chart.plotLeft;
        tooltipY = chart.plotTop + chart.plotHeight - 2 * labelHeight - 10;
    } else {
        tooltipX = chart.plotLeft;
        tooltipY = chart.plotTop + chart.plotHeight - labelHeight;
    }
    return {
        x: tooltipX,
        y: tooltipY
    };
}

For further reading, the default tooltip positioner that comes with highcharts is interesting. One should be able to adapt this to the appropriate requirement (Source).

/**
 * Place the tooltip in a chart without spilling over
 * and not covering the point it self.
 */
getPosition: function (boxWidth, boxHeight, point) {

    var chart = this.chart,
        distance = this.distance,
        ret = {},
        swapped,
        first = ['y', chart.chartHeight, boxHeight, point.plotY + chart.plotTop],
        second = ['x', chart.chartWidth, boxWidth, point.plotX + chart.plotLeft],
        // The far side is right or bottom
        preferFarSide = point.ttBelow || (chart.inverted && !point.negative) || (!chart.inverted && point.negative),
        /**
         * Handle the preferred dimension. When the preferred dimension is tooltip
         * on top or bottom of the point, it will look for space there.
         */
        firstDimension = function (dim, outerSize, innerSize, point) {
            var roomLeft = innerSize < point - distance,
                roomRight = point + distance + innerSize < outerSize,
                alignedLeft = point - distance - innerSize,
                alignedRight = point + distance;

            if (preferFarSide && roomRight) {
                ret[dim] = alignedRight;
            } else if (!preferFarSide && roomLeft) {
                ret[dim] = alignedLeft;
            } else if (roomLeft) {
                ret[dim] = alignedLeft;
            } else if (roomRight) {
                ret[dim] = alignedRight;
            } else {
                return false;
            }
        },
        /**
         * Handle the secondary dimension. If the preferred dimension is tooltip
         * on top or bottom of the point, the second dimension is to align the tooltip
         * above the point, trying to align center but allowing left or right
         * align within the chart box.
         */
        secondDimension = function (dim, outerSize, innerSize, point) {
            // Too close to the edge, return false and swap dimensions
            if (point < distance || point > outerSize - distance) {
                return false;

                // Align left/top
            } else if (point < innerSize / 2) {                 ret[dim] = 1;                 // Align right/bottom             } else if (point > outerSize - innerSize / 2) {
                ret[dim] = outerSize - innerSize - 2;
                // Align center
            } else {
                ret[dim] = point - innerSize / 2;
            }
        },
        /**
         * Swap the dimensions 
         */
        swap = function (count) {
            var temp = first;
            first = second;
            second = temp;
            swapped = count;
        },
        run = function () {
            if (firstDimension.apply(0, first) !== false) {
                if (secondDimension.apply(0, second) === false && !swapped) {
                    swap(true);
                    run();
                }
            } else if (!swapped) {
                swap(true);
                run();
            } else {
                ret.x = ret.y = 0;
            }
        };

    // Under these conditions, prefer the tooltip on the side of the point
    if (chart.inverted || this.len > 1) {
        swap();
    }
    run();

    return ret;

}

Read further about customizing the visibility of the tooltip @ Customizing Highcharts – Tooltip Visibility

Reference Links

 

Latest posts by Jugal Thakkar (see all)