Master_T
Master_T

Reputation: 7913

ChartJS hover/tooltips: selecting the correct points in the datasets based on x-value

I'm using ChartJS (v 2.9.4) and I want the hover interaction / tooltip system (they work the same way from what I can tell) to select the point nearest (relative to the x axis) to the mouse pointer on all datasets.

Now, if the x-axis of the chart is operating in the default category mode (i.e.: data points with the same index in the dataset have the same position on the x-axis) this is easily achieved with this configuration:

options: {
    hover: {
        mode: 'index',
        intersect: false
    }
    //other options...
}

However, if the x-axis is of type linear (i.e.: each data point in each dataset has a specific x-value) this doesn't work properly, since the decision on which point to select is based on its index in the dataset, rather than its x-axis value. I've created an example here:
https://jsfiddle.net/parhwv73/5/
as you can see by hovering the mouse around, you can easily find yourself in situations like this, where the points selected in the two datasets are very far apart: enter image description here

while what I would like is this:

enter image description here

in other words: I would like to select the point in each dataset nearest to the mouse pointer (relative to the x-axis). I've tried playing around with the mode and intersect options, but I found no combination that works, mainly because most other modes only select a point in one single dataset, rather than all of them, index mode is the closest one to being correct, but not quite as I've explained.

Is there a way to achieve this natively?
Or if there isn't, can anyone give some pointers on how to implement a plugin of some sort that can achieve this?

Upvotes: 0

Views: 1281

Answers (2)

simondx
simondx

Reputation: 83

If someone has the same problem in a more recent version (like 3.7.0), you can just modify the interaction mode: instead of 'index', use this:

options: {
        interaction: {
            intersect: false,
            mode: 'nearest',
            axis: 'x'      
        },
}

found on the docs

Upvotes: 1

Master_T
Master_T

Reputation: 7913

Well, in the end I had to modify the ChartJS source code to achieve this. Fortunately, it wasn't too hard. All I had to do was add this function to the core.interaction.js file:

function xPositionMode(chart, e, options) {
    var position = getRelativePosition(e, chart);
    // Default axis for index mode is 'x' to match old behaviour
    options.axis = options.axis || 'x';
    var distanceMetric = getDistanceMetricForAxis(options.axis);
    var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
    var elements = [];
    if (!items.length) {
        return [];
    }

    const findClosestByX = function(array, element) {
        let minDiff = -1;
        let ans;
        for (let i = 0; i < array.length; i++) {
          var m = Math.abs(element._view.x - array[i]._view.x);
          if (minDiff === -1 || m < minDiff) {
            minDiff = m;
            ans = array[i];
          }
        }
        return ans;
    }

    chart._getSortedVisibleDatasetMetas().forEach(function(meta) {
        var element = findClosestByX(meta.data, items[0]);

        // don't count items that are skipped (null data)
        if (element && !element._view.skip) {
            elements.push(element);
        }
    });

    return elements;
}

This is basically a modified version of the indexMode function present in the same file, but instead of searching items by their index in the dataset it searches the closest items by their horizontal position on the canvas (see the findClosestByX inner function for the search algorithm)

To make this usable, you also have to add the new mode in the list of exports (in the same file):

module.exports = {
    // Helper function for different modes
    modes: {
        xPosition: xPositionMode,
        //Rest of the original code...
    }
}

Once this is done, recompile the ChartJS library and use it instead of the original one. Now you can use the new mode like this:

options: {
    hover: {
        mode: 'xPosition',
        intersect: false
    }
    //other options...
}

Note: all code in this answer refers to the 2.9.4 version of the ChartJS library, it might not work the same for 3.x versions.

Upvotes: 0

Related Questions