Reputation: 7913
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:
while what I would like is this:
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
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
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