-import {_isPointInArea} from '../helpers/helpers.canvas';
import {_lookupByKey, _rlookupByKey} from '../helpers/helpers.collection';
-import {getRelativePosition as helpersGetRelativePosition} from '../helpers/helpers.dom';
+import {getRelativePosition} from '../helpers/helpers.dom';
import {_angleBetween, getAngleFromPoint} from '../helpers/helpers.math';
/**
* @typedef { import("../../types/index.esm").ChartEvent } ChartEvent
* @typedef {{axis?: string, intersect?: boolean}} InteractionOptions
* @typedef {{datasetIndex: number, index: number, element: import("./core.element").default}} InteractionItem
+ * @typedef { import("../../types/index.esm").Point } Point
*/
-/**
- * Helper function to get relative position for an event
- * @param {Event|ChartEvent} e - The event to get the position for
- * @param {Chart} chart - The chart
- * @returns {object} the event position
- */
-function getRelativePosition(e, chart) {
- if ('native' in e) {
- return {
- x: e.x,
- y: e.y
- };
- }
-
- return helpersGetRelativePosition(e, chart);
-}
-
-/**
- * Helper function to traverse all of the visible elements in the chart
- * @param {Chart} chart - the chart
- * @param {function} handler - the callback to execute for each visible item
- */
-function evaluateAllVisibleItems(chart, handler) {
- const metasets = chart.getSortedVisibleDatasetMetas();
- let index, data, element;
-
- for (let i = 0, ilen = metasets.length; i < ilen; ++i) {
- ({index, data} = metasets[i]);
- for (let j = 0, jlen = data.length; j < jlen; ++j) {
- element = data[j];
- if (!element.skip) {
- handler(element, index, j);
- }
- }
- }
-}
-
/**
* Helper function to do binary search when possible
* @param {object} metaset - the dataset meta
}
/**
- * Helper function to get items using binary search, when the data is sorted.
+ * Helper function to select candidate elements for interaction
* @param {Chart} chart - the chart
* @param {string} axis - the axis mode. x|y|xy|r
- * @param {object} position - the point to be nearest to
+ * @param {Point} position - the point to be nearest to, in relative coordinates
* @param {function} handler - the callback to execute for each visible item
* @param {boolean} [intersect] - consider intersecting items
*/
-function optimizedEvaluateItems(chart, axis, position, handler, intersect) {
+function evaluateInteractionItems(chart, axis, position, handler, intersect) {
const metasets = chart.getSortedVisibleDatasetMetas();
const value = position[axis];
for (let i = 0, ilen = metasets.length; i < ilen; ++i) {
/**
* Helper function to get the items that intersect the event position
* @param {Chart} chart - the chart
- * @param {object} position - the point to be nearest to
+ * @param {Point} position - the point to be nearest to, in relative coordinates
* @param {string} axis - the axis mode. x|y|xy|r
* @param {boolean} [useFinalPosition] - use the element's animation target instead of current position
* @return {InteractionItem[]} the nearest items
function getIntersectItems(chart, position, axis, useFinalPosition) {
const items = [];
- if (!_isPointInArea(position, chart.chartArea, chart._minPadding)) {
+ if (!chart.isPointInArea(position)) {
return items;
}
}
};
- optimizedEvaluateItems(chart, axis, position, evaluationFunc, true);
+ evaluateInteractionItems(chart, axis, position, evaluationFunc, true);
return items;
}
/**
* Helper function to get the items nearest to the event position for a radial chart
* @param {Chart} chart - the chart to look at elements from
- * @param {object} position - the point to be nearest to
+ * @param {Point} position - the point to be nearest to, in relative coordinates
* @param {string} axis - the axes along which to measure distance
- * @param {boolean} [useFinalPosition] - use the elements animation target instead of current position
+ * @param {boolean} [useFinalPosition] - use the element's animation target instead of current position
* @return {InteractionItem[]} the nearest items
*/
function getNearestRadialItems(chart, position, axis, useFinalPosition) {
}
}
- optimizedEvaluateItems(chart, axis, position, evaluationFunc);
+ evaluateInteractionItems(chart, axis, position, evaluationFunc);
return items;
}
/**
* Helper function to get the items nearest to the event position for a cartesian chart
* @param {Chart} chart - the chart to look at elements from
- * @param {object} position - the point to be nearest to
+ * @param {Point} position - the point to be nearest to, in relative coordinates
* @param {string} axis - the axes along which to measure distance
* @param {boolean} [intersect] - if true, only consider items that intersect the position
- * @param {boolean} [useFinalPosition] - use the elements animation target instead of current position
+ * @param {boolean} [useFinalPosition] - use the element's animation target instead of current position
* @return {InteractionItem[]} the nearest items
*/
function getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition) {
}
const center = element.getCenterPoint(useFinalPosition);
- const pointInArea = _isPointInArea(center, chart.chartArea, chart._minPadding);
+ const pointInArea = chart.isPointInArea(center);
if (!pointInArea && !inRange) {
return;
}
}
}
- optimizedEvaluateItems(chart, axis, position, evaluationFunc);
+ evaluateInteractionItems(chart, axis, position, 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 {Point} position - the point to be nearest to, in relative coordinates
* @param {string} axis - the axes along which to measure distance
* @param {boolean} [intersect] - if true, only consider items that intersect the position
- * @param {boolean} [useFinalPosition] - use the elements animation target instead of current position
+ * @param {boolean} [useFinalPosition] - use the element's animation target instead of current position
* @return {InteractionItem[]} the nearest items
*/
function getNearestItems(chart, position, axis, intersect, useFinalPosition) {
- if (!_isPointInArea(position, chart.chartArea, chart._minPadding)) {
+ if (!chart.isPointInArea(position)) {
return [];
}
: getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition);
}
-function getAxisItems(chart, e, options, useFinalPosition) {
- const position = getRelativePosition(e, chart);
+/**
+ * Helper function to get the items matching along the given X or Y axis
+ * @param {Chart} chart - the chart to look at elements from
+ * @param {Point} position - the point to be nearest to, in relative coordinates
+ * @param {string} axis - the axis to match
+ * @param {boolean} [intersect] - if true, only consider items that intersect the position
+ * @param {boolean} [useFinalPosition] - use the element's animation target instead of current position
+ * @return {InteractionItem[]} the nearest items
+ */
+function getAxisItems(chart, position, axis, intersect, useFinalPosition) {
const items = [];
- const axis = options.axis;
const rangeMethod = axis === 'x' ? 'inXRange' : 'inYRange';
let intersectsItem = false;
- evaluateAllVisibleItems(chart, (element, datasetIndex, index) => {
+ evaluateInteractionItems(chart, axis, position, (element, datasetIndex, index) => {
if (element[rangeMethod](position[axis], useFinalPosition)) {
items.push({element, datasetIndex, index});
- }
-
- if (element.inRange(position.x, position.y, useFinalPosition)) {
- intersectsItem = true;
+ intersectsItem = intersectsItem || element.inRange(position.x, position.y, useFinalPosition);
}
});
// If we want to trigger on an intersect and we don't have any items
// that intersect the position, return nothing
- if (options.intersect && !intersectsItem) {
+ if (intersect && !intersectsItem) {
return [];
}
return items;
* @namespace Chart.Interaction
*/
export default {
+ // Part of the public API to facilitate developers creating their own modes
+ evaluateInteractionItems,
+
// Helper function for different modes
modes: {
/**
* @return {InteractionItem[]} - items that are found
*/
x(chart, e, options, useFinalPosition) {
- return getAxisItems(chart, e, {axis: 'x', intersect: options.intersect}, useFinalPosition);
+ const position = getRelativePosition(e, chart);
+ return getAxisItems(chart, position, 'x', options.intersect, useFinalPosition);
},
/**
* @return {InteractionItem[]} - items that are found
*/
y(chart, e, options, useFinalPosition) {
- return getAxisItems(chart, e, {axis: 'y', intersect: options.intersect}, useFinalPosition);
+ const position = getRelativePosition(e, chart);
+ return getAxisItems(chart, position, 'y', options.intersect, useFinalPosition);
}
}
};