* @param {Chart} chart - the chart
* @param {function} handler - the callback to execute for each visible item
*/
-function parseVisibleItems(chart, handler) {
+function evaluateAllVisibleItems(chart, handler) {
const metasets = chart._getSortedVisibleDatasetMetas();
let index, data, element;
}
}
+/**
+ * Helper function to check the items at the hovered index on the index scale
+ * @param {Chart} chart - the chart
+ * @param {function} handler - the callback to execute for each visible item
+ * @return whether all scales were of a suitable type
+ */
+function evaluateItemsAtIndex(chart, axis, position, handler) {
+ const metasets = chart._getSortedVisibleDatasetMetas();
+ const indices = [];
+ for (let i = 0, ilen = metasets.length; i < ilen; ++i) {
+ const metaset = metasets[i];
+ const iScale = metaset.controller._cachedMeta.iScale;
+ if (!iScale || axis !== iScale.axis || !iScale.getIndexForPixel) {
+ return false;
+ }
+ const index = iScale.getIndexForPixel(position[axis]);
+ if (!helpers.isNumber(index)) {
+ return false;
+ }
+ indices.push(index);
+ }
+ // do this only after checking whether all scales are of a suitable type
+ for (let i = 0, ilen = metasets.length; i < ilen; ++i) {
+ const metaset = metasets[i];
+ const index = indices[i];
+ const element = metaset.data[index];
+ if (!element._view.skip) {
+ handler(element, metaset.index, index);
+ }
+ }
+ return true;
+}
+
+/**
+ * Get a distance metric function for two points based on the
+ * axis mode setting
+ * @param {string} axis - the axis mode. x|y|xy
+ */
+function getDistanceMetricForAxis(axis) {
+ const useX = axis.indexOf('x') !== -1;
+ const useY = axis.indexOf('y') !== -1;
+
+ return function(pt1, pt2) {
+ const deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
+ const deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
+ return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
+ };
+}
+
/**
* Helper function to get the items that intersect the event position
* @param {ChartElement[]} items - elements to filter
* @param {object} position - the point to be nearest to
* @return {ChartElement[]} the nearest items
*/
-function getIntersectItems(chart, position) {
- var elements = [];
+function getIntersectItems(chart, position, axis) {
+ const items = [];
- parseVisibleItems(chart, function(element, datasetIndex, index) {
+ const evaluationFunc = function(element, datasetIndex, index) {
if (element.inRange(position.x, position.y)) {
- elements.push({element, datasetIndex, index});
+ items.push({element, datasetIndex, index});
}
- });
+ };
- return elements;
+ const optimized = evaluateItemsAtIndex(chart, axis, position, evaluationFunc);
+ if (optimized) {
+ return items;
+ }
+
+ evaluateAllVisibleItems(chart, evaluationFunc);
+ return items;
}
/**
* Helper function to get the items nearest to the event position considering all visible items in the chart
* @param {Chart} chart - the chart to look at elements from
* @param {object} position - the point to be nearest to
+ * @param {function} axis - the axes along which to measure distance
* @param {boolean} intersect - if true, only consider items that intersect the position
- * @param {function} distanceMetric - function to provide the distance between points
* @return {ChartElement[]} the nearest items
*/
-function getNearestItems(chart, position, intersect, distanceMetric) {
- var minDistance = Number.POSITIVE_INFINITY;
- var nearestItems = [];
+function getNearestItems(chart, position, axis, intersect) {
+ const distanceMetric = getDistanceMetricForAxis(axis);
+ let minDistance = Number.POSITIVE_INFINITY;
+ let items = [];
- parseVisibleItems(chart, function(element, datasetIndex, index) {
+ const evaluationFunc = function(element, datasetIndex, index) {
if (intersect && !element.inRange(position.x, position.y)) {
return;
}
- var center = element.getCenterPoint();
- var distance = distanceMetric(position, center);
+ const center = element.getCenterPoint();
+ const distance = distanceMetric(position, center);
if (distance < minDistance) {
- nearestItems = [{element, datasetIndex, index}];
+ items = [{element, datasetIndex, index}];
minDistance = distance;
} else if (distance === minDistance) {
// Can have multiple items at the same distance in which case we sort by size
- nearestItems.push({element, datasetIndex, index});
+ items.push({element, datasetIndex, index});
}
- });
-
- return nearestItems;
-}
+ };
-/**
- * Get a distance metric function for two points based on the
- * axis mode setting
- * @param {string} axis - the axis mode. x|y|xy
- */
-function getDistanceMetricForAxis(axis) {
- var useX = axis.indexOf('x') !== -1;
- var useY = axis.indexOf('y') !== -1;
+ const optimized = evaluateItemsAtIndex(chart, axis, position, evaluationFunc);
+ if (optimized) {
+ return items;
+ }
- return function(pt1, pt2) {
- var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
- var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
- return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
- };
+ evaluateAllVisibleItems(chart, evaluationFunc);
+ return items;
}
/**
index: function(chart, e, options) {
const position = getRelativePosition(e, chart);
// Default axis for index mode is 'x' to match old behaviour
- const distanceMetric = getDistanceMetricForAxis(options.axis || 'x');
- const items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
+ const axis = options.axis || 'x';
+ const items = options.intersect ? getIntersectItems(chart, position, axis) : getNearestItems(chart, position, axis);
const elements = [];
if (!items.length) {
*/
dataset: function(chart, e, options) {
const position = getRelativePosition(e, chart);
- const distanceMetric = getDistanceMetricForAxis(options.axis || 'xy');
- let items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
+ const axis = options.axis || 'xy';
+ let items = options.intersect ? getIntersectItems(chart, position, axis) : getNearestItems(chart, position, axis);
if (items.length > 0) {
items = [{datasetIndex: items[0].datasetIndex}]; // when mode: 'dataset' we only need to return datasetIndex
* @function Chart.Interaction.modes.intersect
* @param {Chart} chart - the chart we are returning items from
* @param {Event} e - the event we are find things at
+ * @param {IInteractionOptions} options - options to use
* @return {Object[]} Array of elements that are under the point. If none are found, an empty array is returned
*/
- point: function(chart, e) {
+ point: function(chart, e, options) {
const position = getRelativePosition(e, chart);
- return getIntersectItems(chart, position);
+ const axis = options.axis || 'xy';
+ return getIntersectItems(chart, position, axis);
},
/**
*/
nearest: function(chart, e, options) {
const position = getRelativePosition(e, chart);
- const distanceMetric = getDistanceMetricForAxis(options.axis || 'xy');
- return getNearestItems(chart, position, options.intersect, distanceMetric);
+ const axis = options.axis || 'xy';
+ return getNearestItems(chart, position, axis, options.intersect);
},
/**
const items = [];
let intersectsItem = false;
- parseVisibleItems(chart, function(element, datasetIndex, index) {
+ evaluateAllVisibleItems(chart, function(element, datasetIndex, index) {
if (element.inXRange(position.x)) {
items.push({element, datasetIndex, index});
}
const items = [];
let intersectsItem = false;
- parseVisibleItems(chart, function(element, datasetIndex, index) {
+ evaluateAllVisibleItems(chart, function(element, datasetIndex, index) {
if (element.inYRange(position.y)) {
items.push({element, datasetIndex, index});
}