From 9c098a91d0ae6e842ab1a5d15fdf5c30bbfff770 Mon Sep 17 00:00:00 2001 From: Josh Kelley Date: Tue, 4 Jan 2022 11:27:36 -0500 Subject: [PATCH 01/14] Refactor get...Items functions to take events rather than positions To work toward exposing something like the get...Items functions. --- src/core/core.interaction.js | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/core/core.interaction.js b/src/core/core.interaction.js index 662703d7f7b..0969226ea70 100644 --- a/src/core/core.interaction.js +++ b/src/core/core.interaction.js @@ -105,7 +105,7 @@ function optimizedEvaluateItems(chart, axis, position, handler, intersect) { /** * Get a distance metric function for two points based on the * axis mode setting - * @param {string} axis - the axis mode. x|y|xy|r + * @param {string} axis - the axis mode. x|y|xy */ function getDistanceMetricForAxis(axis) { const useX = axis.indexOf('x') !== -1; @@ -121,12 +121,13 @@ function getDistanceMetricForAxis(axis) { /** * 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 {Event|ChartEvent} e - the event * @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) { +function getIntersectItems(chart, e, axis, useFinalPosition) { + const position = getRelativePosition(e, chart); const items = []; if (!_isPointInArea(position, chart.chartArea, chart._minPadding)) { @@ -148,7 +149,7 @@ function getIntersectItems(chart, position, axis, useFinalPosition) { * @param {Chart} chart - the chart to look at elements from * @param {object} position - the point to be nearest to * @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) { @@ -173,7 +174,7 @@ function getNearestRadialItems(chart, position, axis, useFinalPosition) { * @param {object} position - the point to be nearest to * @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) { @@ -210,13 +211,14 @@ function getNearestCartesianItems(chart, position, axis, intersect, useFinalPosi /** * 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 {Event|ChartEvent} e - the event * @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) { +function getNearestItems(chart, e, axis, intersect, useFinalPosition) { + const position = getRelativePosition(e, chart); if (!_isPointInArea(position, chart.chartArea, chart._minPadding)) { return []; } @@ -270,12 +272,11 @@ export default { * @return {InteractionItem[]} - items that are found */ index(chart, e, options, useFinalPosition) { - const position = getRelativePosition(e, chart); // Default axis for index mode is 'x' to match old behaviour const axis = options.axis || 'x'; const items = options.intersect - ? getIntersectItems(chart, position, axis, useFinalPosition) - : getNearestItems(chart, position, axis, false, useFinalPosition); + ? getIntersectItems(chart, e, axis, useFinalPosition) + : getNearestItems(chart, e, axis, false, useFinalPosition); const elements = []; if (!items.length) { @@ -306,11 +307,10 @@ export default { * @return {InteractionItem[]} - items that are found */ dataset(chart, e, options, useFinalPosition) { - const position = getRelativePosition(e, chart); const axis = options.axis || 'xy'; let items = options.intersect - ? getIntersectItems(chart, position, axis, useFinalPosition) : - getNearestItems(chart, position, axis, false, useFinalPosition); + ? getIntersectItems(chart, e, axis, useFinalPosition) : + getNearestItems(chart, e, axis, false, useFinalPosition); if (items.length > 0) { const datasetIndex = items[0].datasetIndex; @@ -335,9 +335,8 @@ export default { * @return {InteractionItem[]} - items that are found */ point(chart, e, options, useFinalPosition) { - const position = getRelativePosition(e, chart); const axis = options.axis || 'xy'; - return getIntersectItems(chart, position, axis, useFinalPosition); + return getIntersectItems(chart, e, axis, useFinalPosition); }, /** @@ -350,9 +349,8 @@ export default { * @return {InteractionItem[]} - items that are found */ nearest(chart, e, options, useFinalPosition) { - const position = getRelativePosition(e, chart); const axis = options.axis || 'xy'; - return getNearestItems(chart, position, axis, options.intersect, useFinalPosition); + return getNearestItems(chart, e, axis, options.intersect, useFinalPosition); }, /** From 43769cbde8c7f2627ba71599307fef113bd58ea5 Mon Sep 17 00:00:00 2001 From: Josh Kelley Date: Tue, 4 Jan 2022 11:31:20 -0500 Subject: [PATCH 02/14] Switch getAxisItems to use optimizedEvaluateItems optimizedEvaluateItems falls back to evaluating all items for unsorted items, and sorting / optimizing ought to be okay, so this ought to be equivalent. --- src/core/core.interaction.js | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/src/core/core.interaction.js b/src/core/core.interaction.js index 0969226ea70..b5dadb6a29b 100644 --- a/src/core/core.interaction.js +++ b/src/core/core.interaction.js @@ -27,26 +27,6 @@ function getRelativePosition(e, chart) { 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 @@ -235,7 +215,7 @@ function getAxisItems(chart, e, options, useFinalPosition) { const rangeMethod = axis === 'x' ? 'inXRange' : 'inYRange'; let intersectsItem = false; - evaluateAllVisibleItems(chart, (element, datasetIndex, index) => { + optimizedEvaluateItems(chart, axis, position, (element, datasetIndex, index) => { if (element[rangeMethod](position[axis], useFinalPosition)) { items.push({element, datasetIndex, index}); } From 7022cda5631f3b3d92520be88ab1a8b5c1cf319c Mon Sep 17 00:00:00 2001 From: Josh Kelley Date: Tue, 4 Jan 2022 12:09:13 -0500 Subject: [PATCH 03/14] Performance --- src/core/core.interaction.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/core/core.interaction.js b/src/core/core.interaction.js index b5dadb6a29b..b0585bf65fe 100644 --- a/src/core/core.interaction.js +++ b/src/core/core.interaction.js @@ -218,10 +218,7 @@ function getAxisItems(chart, e, options, useFinalPosition) { optimizedEvaluateItems(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); } }); From 062e61a89efe4cd577c408e54f7cc54366ed27fd Mon Sep 17 00:00:00 2001 From: Josh Kelley Date: Tue, 4 Jan 2022 12:59:27 -0500 Subject: [PATCH 04/14] Consolidate getRelativePosition helpers.dom.js's getRelativePosition already had logic to handle ChartEvent vs. Event (as demonstrated by the `native` check within `getCanvasPosition`), so it's redundant for core.interaction.js to have its own `native` check. Update `getRelativePosition` to use the same `native` check as core.interaction.js's version. As best as I can tell, the ChartEvent's x and y are populated from `getRelativePosition`, so the previous `getCanvasPosition` was effectively just duplicating `getRelativePosition'`s work. I added a test to verify this; it depends on a local, not-yet-submitted change in chartjs-test-utils' `triggerMouseEvent` to return the mouse event that it triggers. --- src/core/core.interaction.js | 19 +------------------ src/helpers/helpers.dom.js | 10 ++++++++-- test/specs/helpers.dom.tests.js | 24 ++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/core/core.interaction.js b/src/core/core.interaction.js index b0585bf65fe..d4af0479b8b 100644 --- a/src/core/core.interaction.js +++ b/src/core/core.interaction.js @@ -1,6 +1,6 @@ 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'; /** @@ -10,23 +10,6 @@ import {_angleBetween, getAngleFromPoint} from '../helpers/helpers.math'; * @typedef {{datasetIndex: number, index: number, element: import("./core.element").default}} InteractionItem */ -/** - * 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 do binary search when possible * @param {object} metaset - the dataset meta diff --git a/src/helpers/helpers.dom.js b/src/helpers/helpers.dom.js index 8fe1f3f9b2b..26cd9190b68 100644 --- a/src/helpers/helpers.dom.js +++ b/src/helpers/helpers.dom.js @@ -59,8 +59,7 @@ function getPositionedStyle(styles, style, suffix) { const useOffsetPos = (x, y, target) => (x > 0 || y > 0) && (!target || !target.shadowRoot); -function getCanvasPosition(evt, canvas) { - const e = evt.native || evt; +function getCanvasPosition(e, canvas) { const touches = e.touches; const source = touches && touches.length ? touches[0] : e; const {offsetX, offsetY} = source; @@ -79,6 +78,13 @@ function getCanvasPosition(evt, canvas) { } export function getRelativePosition(evt, chart) { + if ('native' in evt) { + return { + x: evt.x, + y: evt.y + }; + } + const {canvas, currentDevicePixelRatio} = chart; const style = getComputedStyle(canvas); const borderBox = style.boxSizing === 'border-box'; diff --git a/test/specs/helpers.dom.tests.js b/test/specs/helpers.dom.tests.js index fd0d0de14c7..8dad7de70ab 100644 --- a/test/specs/helpers.dom.tests.js +++ b/test/specs/helpers.dom.tests.js @@ -427,5 +427,29 @@ describe('DOM helpers tests', function() { expect(dataX).not.toEqual(NaN); expect(dataY).not.toEqual(NaN); }); + + it('Should give consistent results for native and chart events', async function() { + let chartPosition = null; + const chart = window.acquireChart( + { + type: 'bar', + data: { + datasets: [{ + data: [{x: 'first', y: 10}, {x: 'second', y: 5}, {x: 'third', y: 15}] + }] + }, + options: { + onHover: (chartEvent) => { + chartPosition = Chart.helpers.getRelativePosition(chartEvent, chart); + } + } + }); + + const point = chart.getDatasetMeta(0).data[1]; + const nativeEvent = await jasmine.triggerMouseEvent(chart, 'mousemove', point); + const nativePosition = Chart.helpers.getRelativePosition(nativeEvent, chart); + + expect(nativePosition).toEqual(chartPosition); + }); }); }); From c76593830fc9135910d7a87bf577a17ee934e3a4 Mon Sep 17 00:00:00 2001 From: Josh Kelley Date: Tue, 4 Jan 2022 14:10:43 -0500 Subject: [PATCH 05/14] Add an API to refactor duplicate isPointInArea --- src/core/core.controller.js | 13 ++++++++++++- src/core/core.interaction.js | 7 +++---- src/helpers/helpers.canvas.js | 3 ++- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/core/core.controller.js b/src/core/core.controller.js index 6b0bd2f71da..2c16b3c1703 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -15,6 +15,7 @@ import {debounce} from '../helpers/helpers.extras'; /** * @typedef { import('../../types/index.esm').ChartEvent } ChartEvent + * @typedef { import("../../types/index.esm").Point } Point */ const KNOWN_POSITIONS = ['top', 'bottom', 'left', 'right', 'chartArea']; @@ -792,6 +793,16 @@ class Chart { this.notifyPlugins('afterDatasetDraw', args); } + /** + * Checks whether the given point is in the chart area. Point coordinates are + * relative to the chart. (getRelativePosition may be helpful.) + * @param {Point} point + * @returns {boolean} + */ + isPointInArea(point) { + return _isPointInArea(point, this.chartArea, this._minPadding); + } + getElementsAtEventForMode(e, mode, options, useFinalPosition) { const method = Interaction.modes[mode]; if (typeof method === 'function') { @@ -1134,7 +1145,7 @@ class Chart { event: e, replay, cancelable: true, - inChartArea: _isPointInArea(e, this.chartArea, this._minPadding) + inChartArea: this.isPointInArea(e) }; const eventFilter = (plugin) => (plugin.options.events || this.options.events).includes(e.native.type); diff --git a/src/core/core.interaction.js b/src/core/core.interaction.js index d4af0479b8b..3ee549b8269 100644 --- a/src/core/core.interaction.js +++ b/src/core/core.interaction.js @@ -1,4 +1,3 @@ -import {_isPointInArea} from '../helpers/helpers.canvas'; import {_lookupByKey, _rlookupByKey} from '../helpers/helpers.collection'; import {getRelativePosition} from '../helpers/helpers.dom'; import {_angleBetween, getAngleFromPoint} from '../helpers/helpers.math'; @@ -93,7 +92,7 @@ function getIntersectItems(chart, e, axis, useFinalPosition) { const position = getRelativePosition(e, chart); const items = []; - if (!_isPointInArea(position, chart.chartArea, chart._minPadding)) { + if (!chart.isPointInArea(position)) { return items; } @@ -152,7 +151,7 @@ function getNearestCartesianItems(chart, position, axis, intersect, useFinalPosi } const center = element.getCenterPoint(useFinalPosition); - const pointInArea = _isPointInArea(center, chart.chartArea, chart._minPadding); + const pointInArea = chart.isPointInArea(center); if (!pointInArea && !inRange) { return; } @@ -182,7 +181,7 @@ function getNearestCartesianItems(chart, position, axis, intersect, useFinalPosi */ function getNearestItems(chart, e, axis, intersect, useFinalPosition) { const position = getRelativePosition(e, chart); - if (!_isPointInArea(position, chart.chartArea, chart._minPadding)) { + if (!chart.isPointInArea(position)) { return []; } diff --git a/src/helpers/helpers.canvas.js b/src/helpers/helpers.canvas.js index d6290f2ec0c..62869d23a80 100644 --- a/src/helpers/helpers.canvas.js +++ b/src/helpers/helpers.canvas.js @@ -3,6 +3,7 @@ import {PI, TAU, HALF_PI, QUARTER_PI, TWO_THIRDS_PI, RAD_PER_DEG} from './helper /** * @typedef { import("../core/core.controller").default } Chart + * @typedef { import("../../types/index.esm").Point } Point */ /** @@ -242,7 +243,7 @@ export function drawPoint(ctx, options, x, y) { /** * Returns true if the point is inside the rectangle - * @param {object} point - The point to test + * @param {Point} point - The point to test * @param {object} area - The rectangle * @param {number} [margin] - allowed margin * @returns {boolean} From d71d40627ad1ff79bdafb8dd2ef01d6f309bcb7d Mon Sep 17 00:00:00 2001 From: Josh Kelley Date: Tue, 4 Jan 2022 14:12:06 -0500 Subject: [PATCH 06/14] Rename and update JSDoc to prepare for making this public --- src/core/core.interaction.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/core/core.interaction.js b/src/core/core.interaction.js index 3ee549b8269..dd13e096d9f 100644 --- a/src/core/core.interaction.js +++ b/src/core/core.interaction.js @@ -7,6 +7,7 @@ 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 */ /** @@ -45,11 +46,11 @@ function binarySearch(metaset, axis, value, intersect) { * Helper function to get items using binary search, when the data is sorted. * @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) { @@ -102,7 +103,7 @@ function getIntersectItems(chart, e, axis, useFinalPosition) { } }; - optimizedEvaluateItems(chart, axis, position, evaluationFunc, true); + evaluateInteractionItems(chart, axis, position, evaluationFunc, true); return items; } @@ -126,7 +127,7 @@ function getNearestRadialItems(chart, position, axis, useFinalPosition) { } } - optimizedEvaluateItems(chart, axis, position, evaluationFunc); + evaluateInteractionItems(chart, axis, position, evaluationFunc); return items; } @@ -166,7 +167,7 @@ function getNearestCartesianItems(chart, position, axis, intersect, useFinalPosi } } - optimizedEvaluateItems(chart, axis, position, evaluationFunc); + evaluateInteractionItems(chart, axis, position, evaluationFunc); return items; } @@ -197,7 +198,7 @@ function getAxisItems(chart, e, options, useFinalPosition) { const rangeMethod = axis === 'x' ? 'inXRange' : 'inYRange'; let intersectsItem = false; - optimizedEvaluateItems(chart, axis, position, (element, datasetIndex, index) => { + evaluateInteractionItems(chart, axis, position, (element, datasetIndex, index) => { if (element[rangeMethod](position[axis], useFinalPosition)) { items.push({element, datasetIndex, index}); intersectsItem = intersectsItem || element.inRange(position.x, position.y, useFinalPosition); From 9782e7e065968756276007ad87a43ee038b41361 Mon Sep 17 00:00:00 2001 From: Josh Kelley Date: Tue, 4 Jan 2022 14:26:14 -0500 Subject: [PATCH 07/14] Give functions a consistent, generic interface --- src/core/core.interaction.js | 53 ++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/src/core/core.interaction.js b/src/core/core.interaction.js index dd13e096d9f..c5e60d565fa 100644 --- a/src/core/core.interaction.js +++ b/src/core/core.interaction.js @@ -68,7 +68,7 @@ function evaluateInteractionItems(chart, axis, position, handler, intersect) { /** * Get a distance metric function for two points based on the * axis mode setting - * @param {string} axis - the axis mode. x|y|xy + * @param {string} axis - the axis mode. x|y|xy|r */ function getDistanceMetricForAxis(axis) { const useX = axis.indexOf('x') !== -1; @@ -84,13 +84,12 @@ function getDistanceMetricForAxis(axis) { /** * Helper function to get the items that intersect the event position * @param {Chart} chart - the chart - * @param {Event|ChartEvent} e - the event + * @param {Point} position - the point to be nearest to * @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, e, axis, useFinalPosition) { - const position = getRelativePosition(e, chart); +function getIntersectItems(chart, position, axis, useFinalPosition) { const items = []; if (!chart.isPointInArea(position)) { @@ -110,7 +109,7 @@ function getIntersectItems(chart, e, axis, useFinalPosition) { /** * 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 * @param {string} axis - the axes along which to measure distance * @param {boolean} [useFinalPosition] - use the element's animation target instead of current position * @return {InteractionItem[]} the nearest items @@ -134,7 +133,7 @@ function getNearestRadialItems(chart, position, axis, useFinalPosition) { /** * 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 * @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 element's animation target instead of current position @@ -174,14 +173,13 @@ function getNearestCartesianItems(chart, position, axis, intersect, useFinalPosi /** * 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 {Event|ChartEvent} e - the event + * @param {Point} position - the point to be nearest to * @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 element's animation target instead of current position * @return {InteractionItem[]} the nearest items */ -function getNearestItems(chart, e, axis, intersect, useFinalPosition) { - const position = getRelativePosition(e, chart); +function getNearestItems(chart, position, axis, intersect, useFinalPosition) { if (!chart.isPointInArea(position)) { return []; } @@ -191,10 +189,17 @@ function getNearestItems(chart, e, axis, intersect, useFinalPosition) { : 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 + * @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; @@ -207,7 +212,7 @@ function getAxisItems(chart, e, options, 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; @@ -232,11 +237,12 @@ export default { * @return {InteractionItem[]} - items that are found */ index(chart, e, options, useFinalPosition) { + const position = getRelativePosition(e, chart); // Default axis for index mode is 'x' to match old behaviour const axis = options.axis || 'x'; const items = options.intersect - ? getIntersectItems(chart, e, axis, useFinalPosition) - : getNearestItems(chart, e, axis, false, useFinalPosition); + ? getIntersectItems(chart, position, axis, useFinalPosition) + : getNearestItems(chart, position, axis, false, useFinalPosition); const elements = []; if (!items.length) { @@ -267,10 +273,11 @@ export default { * @return {InteractionItem[]} - items that are found */ dataset(chart, e, options, useFinalPosition) { + const position = getRelativePosition(e, chart); const axis = options.axis || 'xy'; let items = options.intersect - ? getIntersectItems(chart, e, axis, useFinalPosition) : - getNearestItems(chart, e, axis, false, useFinalPosition); + ? getIntersectItems(chart, position, axis, useFinalPosition) : + getNearestItems(chart, position, axis, false, useFinalPosition); if (items.length > 0) { const datasetIndex = items[0].datasetIndex; @@ -295,8 +302,9 @@ export default { * @return {InteractionItem[]} - items that are found */ point(chart, e, options, useFinalPosition) { + const position = getRelativePosition(e, chart); const axis = options.axis || 'xy'; - return getIntersectItems(chart, e, axis, useFinalPosition); + return getIntersectItems(chart, position, axis, useFinalPosition); }, /** @@ -309,8 +317,9 @@ export default { * @return {InteractionItem[]} - items that are found */ nearest(chart, e, options, useFinalPosition) { + const position = getRelativePosition(e, chart); const axis = options.axis || 'xy'; - return getNearestItems(chart, e, axis, options.intersect, useFinalPosition); + return getNearestItems(chart, position, axis, options.intersect, useFinalPosition); }, /** @@ -323,7 +332,8 @@ export default { * @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); }, /** @@ -336,7 +346,8 @@ export default { * @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); } } }; From 121167d023968384b896085c2646b6c8fce5b26f Mon Sep 17 00:00:00 2001 From: Josh Kelley Date: Tue, 4 Jan 2022 14:27:30 -0500 Subject: [PATCH 08/14] Export functions for discussion --- src/core/core.interaction.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/core.interaction.js b/src/core/core.interaction.js index c5e60d565fa..96a366e37d0 100644 --- a/src/core/core.interaction.js +++ b/src/core/core.interaction.js @@ -50,7 +50,7 @@ function binarySearch(metaset, axis, value, intersect) { * @param {function} handler - the callback to execute for each visible item * @param {boolean} [intersect] - consider intersecting items */ -function evaluateInteractionItems(chart, axis, position, handler, intersect) { +export 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) { @@ -89,7 +89,7 @@ function getDistanceMetricForAxis(axis) { * @param {boolean} [useFinalPosition] - use the element's animation target instead of current position * @return {InteractionItem[]} the nearest items */ -function getIntersectItems(chart, position, axis, useFinalPosition) { +export function getIntersectItems(chart, position, axis, useFinalPosition) { const items = []; if (!chart.isPointInArea(position)) { @@ -179,7 +179,7 @@ function getNearestCartesianItems(chart, position, axis, intersect, useFinalPosi * @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) { +export function getNearestItems(chart, position, axis, intersect, useFinalPosition) { if (!chart.isPointInArea(position)) { return []; } @@ -198,7 +198,7 @@ function getNearestItems(chart, position, axis, intersect, useFinalPosition) { * @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) { +export function getAxisItems(chart, position, axis, intersect, useFinalPosition) { const items = []; const rangeMethod = axis === 'x' ? 'inXRange' : 'inYRange'; let intersectsItem = false; From 0f99ce409b980dab328919b3ad45f135f90be7d4 Mon Sep 17 00:00:00 2001 From: Josh Kelley Date: Mon, 14 Feb 2022 10:36:54 -0500 Subject: [PATCH 09/14] Code review feedback Add a non-null assertion, as requested in code review. Add JSDoc to clarify that `getCanvasPosition` now expects a native `Event`, not a `ChartEvent`. Add `@ts-ignore`; `getCanvasPosition` relied on some loose conversions between `Event`, `TouchEvent`, and `Touch` that would require several changes to make TypeScript happy. --- src/helpers/helpers.dom.js | 6 ++++++ test/specs/helpers.dom.tests.js | 1 + 2 files changed, 7 insertions(+) diff --git a/src/helpers/helpers.dom.js b/src/helpers/helpers.dom.js index 26cd9190b68..201b06aed4d 100644 --- a/src/helpers/helpers.dom.js +++ b/src/helpers/helpers.dom.js @@ -59,7 +59,13 @@ function getPositionedStyle(styles, style, suffix) { const useOffsetPos = (x, y, target) => (x > 0 || y > 0) && (!target || !target.shadowRoot); +/** + * @param {Event} e + * @param {HTMLCanvasElement} canvas + * @returns {{x: number, y: number, box: boolean}} + */ function getCanvasPosition(e, canvas) { + // @ts-ignore const touches = e.touches; const source = touches && touches.length ? touches[0] : e; const {offsetX, offsetY} = source; diff --git a/test/specs/helpers.dom.tests.js b/test/specs/helpers.dom.tests.js index 8dad7de70ab..f0abe260775 100644 --- a/test/specs/helpers.dom.tests.js +++ b/test/specs/helpers.dom.tests.js @@ -449,6 +449,7 @@ describe('DOM helpers tests', function() { const nativeEvent = await jasmine.triggerMouseEvent(chart, 'mousemove', point); const nativePosition = Chart.helpers.getRelativePosition(nativeEvent, chart); + expect(chartPosition).not.toBeNull(); expect(nativePosition).toEqual(chartPosition); }); }); From 77de6ef057c0deeb712f622f868787174df15ba1 Mon Sep 17 00:00:00 2001 From: Josh Kelley Date: Tue, 15 Feb 2022 10:55:28 -0500 Subject: [PATCH 10/14] Code review feedback Return the event directly, to speed up the code a bit. Add JSDoc to help communicate its intent. Update various comments. --- src/core/core.controller.js | 5 ++--- src/core/core.interaction.js | 10 +++++----- src/helpers/helpers.canvas.js | 7 +++++-- src/helpers/helpers.dom.js | 19 +++++++++++++++---- test/specs/helpers.dom.tests.js | 2 +- 5 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/core/core.controller.js b/src/core/core.controller.js index 2c16b3c1703..17923d90aba 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -794,9 +794,8 @@ class Chart { } /** - * Checks whether the given point is in the chart area. Point coordinates are - * relative to the chart. (getRelativePosition may be helpful.) - * @param {Point} point + * Checks whether the given point is in the chart area. + * @param {Point} point - in relative coordinates (see, e.g., getRelativePosition) * @returns {boolean} */ isPointInArea(point) { diff --git a/src/core/core.interaction.js b/src/core/core.interaction.js index 96a366e37d0..fe864a1ae55 100644 --- a/src/core/core.interaction.js +++ b/src/core/core.interaction.js @@ -84,7 +84,7 @@ function getDistanceMetricForAxis(axis) { /** * Helper function to get the items that intersect the event position * @param {Chart} chart - the chart - * @param {Point} 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 @@ -109,7 +109,7 @@ export function getIntersectItems(chart, position, axis, useFinalPosition) { /** * 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 {Point} 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 element's animation target instead of current position * @return {InteractionItem[]} the nearest items @@ -133,7 +133,7 @@ function getNearestRadialItems(chart, position, axis, useFinalPosition) { /** * 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 {Point} 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 element's animation target instead of current position @@ -173,7 +173,7 @@ function getNearestCartesianItems(chart, position, axis, intersect, useFinalPosi /** * 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 {Point} 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 element's animation target instead of current position @@ -192,7 +192,7 @@ export function getNearestItems(chart, position, axis, intersect, useFinalPositi /** * 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 + * @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 diff --git a/src/helpers/helpers.canvas.js b/src/helpers/helpers.canvas.js index 62869d23a80..54ce2cd54f6 100644 --- a/src/helpers/helpers.canvas.js +++ b/src/helpers/helpers.canvas.js @@ -2,7 +2,10 @@ import {isArray, isNullOrUndef} from './helpers.core'; import {PI, TAU, HALF_PI, QUARTER_PI, TWO_THIRDS_PI, RAD_PER_DEG} from './helpers.math'; /** - * @typedef { import("../core/core.controller").default } Chart + * Note: typedefs are auto-exported, so use a made-up `canvas` namespace where + * necessary to avoid duplicates with `export * from './helpers`; see + * https://github.com/microsoft/TypeScript/issues/46011 + * @typedef { import("../core/core.controller").default } canvas.Chart * @typedef { import("../../types/index.esm").Point } Point */ @@ -95,7 +98,7 @@ export function _longestText(ctx, font, arrayOfThings, cache) { /** * Returns the aligned pixel value to avoid anti-aliasing blur - * @param {Chart} chart - The chart instance. + * @param {canvas.Chart} chart - The chart instance. * @param {number} pixel - A pixel value. * @param {number} width - The width of the element. * @returns {number} The aligned pixel value. diff --git a/src/helpers/helpers.dom.js b/src/helpers/helpers.dom.js index 201b06aed4d..c4f54e00936 100644 --- a/src/helpers/helpers.dom.js +++ b/src/helpers/helpers.dom.js @@ -1,5 +1,13 @@ import {INFINITY} from './helpers.math'; +/** + * Note: typedefs are auto-exported, so use a made-up `dom` namespace where + * necessary to avoid duplicates with `export * from './helpers`; see + * https://github.com/microsoft/TypeScript/issues/46011 + * @typedef { import("../core/core.controller").default } dom.Chart + * @typedef { import('../../types/index.esm').ChartEvent } ChartEvent + */ + /** * @private */ @@ -83,12 +91,15 @@ function getCanvasPosition(e, canvas) { return {x, y, box}; } +/** + * Gets an event's x, y coordinates, relative to the chart area + * @param {Event|ChartEvent} evt + * @param {dom.Chart} chart + * @returns {{x: number, y: number}} + */ export function getRelativePosition(evt, chart) { if ('native' in evt) { - return { - x: evt.x, - y: evt.y - }; + return evt; } const {canvas, currentDevicePixelRatio} = chart; diff --git a/test/specs/helpers.dom.tests.js b/test/specs/helpers.dom.tests.js index f0abe260775..24dbe81b68a 100644 --- a/test/specs/helpers.dom.tests.js +++ b/test/specs/helpers.dom.tests.js @@ -450,7 +450,7 @@ describe('DOM helpers tests', function() { const nativePosition = Chart.helpers.getRelativePosition(nativeEvent, chart); expect(chartPosition).not.toBeNull(); - expect(nativePosition).toEqual(chartPosition); + expect(nativePosition).toEqual({x: chartPosition.x, y: chartPosition.y}); }); }); }); From 03de1239c9875a659ef7f8009be1b7eecb8baec8 Mon Sep 17 00:00:00 2001 From: Josh Kelley Date: Tue, 15 Feb 2022 17:26:11 -0500 Subject: [PATCH 11/14] Finalize exports; add docs and TypeScript --- docs/configuration/interactions.md | 53 ++++++++++++++++++++++++++++++ src/core/core.interaction.js | 13 +++++--- types/index.esm.d.ts | 18 ++++++++-- 3 files changed, 77 insertions(+), 7 deletions(-) diff --git a/docs/configuration/interactions.md b/docs/configuration/interactions.md index abb111b54d4..2fe77f45caf 100644 --- a/docs/configuration/interactions.md +++ b/docs/configuration/interactions.md @@ -220,3 +220,56 @@ const chart = new Chart(ctx, { } }); ``` + +## Custom Interaction Modes + +New modes can be defined by adding functions to the `Chart.Interaction.modes` map. You can use the `Chart.Interaction.evaluateInteractionItems` function to help implement these. + +Example: + +```javascript +import { Interaction } from 'chart.js'; +import { getRelativePosition } from 'chart.js/helpers'; + +/** + * Custom interaction mode + * @function Interaction.modes.myCustomMode + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {InteractionOptions} options - options to use + * @param {boolean} [useFinalPosition] - use final element position (animation target) + * @return {InteractionItem[]} - items that are found + */ +Interaction.modes.myCustomMode = function(chart, e, options, useFinalPosition) { + const position = getRelativePosition(e, chart); + + const items = []; + Interaction.evaluateInteractionItems(chart, 'x', position, (element, datasetIndex, index) => { + if (element.inXRange(position.x, useFinalPosition) && myCustomLogic(element)) { + items.push({element, datasetIndex, index}); + } + }); + return items; +}; + +// Then, to use it... +new Chart.js(ctx, { + type: 'line', + data: data, + options: { + interaction: { + mode: 'myCustomMode' + } + } +}) +``` + +If you're using TypeScript, you'll also need to register the new mode: + +```typescript +declare module 'chart.js' { + interface InteractionModeMap { + myCustomMode: InteractionModeFunction; + } +} +``` \ No newline at end of file diff --git a/src/core/core.interaction.js b/src/core/core.interaction.js index fe864a1ae55..2e18acc9d7e 100644 --- a/src/core/core.interaction.js +++ b/src/core/core.interaction.js @@ -43,14 +43,14 @@ function binarySearch(metaset, axis, value, intersect) { } /** - * 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 {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 */ -export function evaluateInteractionItems(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) { @@ -89,7 +89,7 @@ function getDistanceMetricForAxis(axis) { * @param {boolean} [useFinalPosition] - use the element's animation target instead of current position * @return {InteractionItem[]} the nearest items */ -export function getIntersectItems(chart, position, axis, useFinalPosition) { +function getIntersectItems(chart, position, axis, useFinalPosition) { const items = []; if (!chart.isPointInArea(position)) { @@ -179,7 +179,7 @@ function getNearestCartesianItems(chart, position, axis, intersect, useFinalPosi * @param {boolean} [useFinalPosition] - use the element's animation target instead of current position * @return {InteractionItem[]} the nearest items */ -export function getNearestItems(chart, position, axis, intersect, useFinalPosition) { +function getNearestItems(chart, position, axis, intersect, useFinalPosition) { if (!chart.isPointInArea(position)) { return []; } @@ -198,7 +198,7 @@ export function getNearestItems(chart, position, axis, intersect, useFinalPositi * @param {boolean} [useFinalPosition] - use the element's animation target instead of current position * @return {InteractionItem[]} the nearest items */ -export function getAxisItems(chart, position, axis, intersect, useFinalPosition) { +function getAxisItems(chart, position, axis, intersect, useFinalPosition) { const items = []; const rangeMethod = axis === 'x' ? 'inXRange' : 'inYRange'; let intersectsItem = false; @@ -223,6 +223,9 @@ export function getAxisItems(chart, position, axis, intersect, useFinalPosition) * @namespace Chart.Interaction */ export default { + // Part of the public API to facilitate developers creating their own modes + evaluateInteractionItems, + // Helper function for different modes modes: { /** diff --git a/types/index.esm.d.ts b/types/index.esm.d.ts index 96ad30bf8cd..232ad9d03e9 100644 --- a/types/index.esm.d.ts +++ b/types/index.esm.d.ts @@ -509,6 +509,7 @@ export declare class Chart< render(): void; draw(): void; + isPointInArea(point: Point): boolean; getElementsAtEventForMode(e: Event, mode: string, options: InteractionOptions, useFinalPosition: boolean): InteractionItem[]; getSortedVisibleDatasetMetas(): ChartMeta[]; @@ -741,6 +742,17 @@ export type InteractionMode = keyof InteractionModeMap; export const Interaction: { modes: InteractionModeMap; + + /** + * Helper function to select candidate elements for interaction + */ + evaluateInteractionItems( + chart: Chart, + axis: InteractionAxis, + position: Point, + handler: (element: Element & VisualElement, datasetIndex: number, index: number) => void, + intersect?: boolean + ): InteractionItem[]; }; export const layouts: { @@ -1395,6 +1407,8 @@ export interface ChartComponent { afterUnregister?(): void; } +export type InteractionAxis = 'x' | 'y' | 'xy' | 'r'; + export interface CoreInteractionOptions { /** * Sets which elements appear in the tooltip. See Interaction Modes for details. @@ -1408,9 +1422,9 @@ export interface CoreInteractionOptions { intersect: boolean; /** - * Can be set to 'x', 'y', 'xy' or 'r' to define which directions are used in calculating distances. Defaults to 'x' for 'index' mode and 'xy' in dataset and 'nearest' modes. + * Defines which directions are used in calculating distances. Defaults to 'x' for 'index' mode and 'xy' in dataset and 'nearest' modes. */ - axis: 'x' | 'y' | 'xy' | 'r'; + axis: InteractionAxis; } export interface CoreChartOptions extends ParsingOptions, AnimationOptions { From d50c746d71328ad14727c979551991f76651c272 Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Wed, 23 Mar 2022 16:21:49 +0200 Subject: [PATCH 12/14] Update src/helpers/helpers.dom.js --- src/helpers/helpers.dom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/helpers.dom.js b/src/helpers/helpers.dom.js index c4f54e00936..299dd74f620 100644 --- a/src/helpers/helpers.dom.js +++ b/src/helpers/helpers.dom.js @@ -98,7 +98,7 @@ function getCanvasPosition(e, canvas) { * @returns {{x: number, y: number}} */ export function getRelativePosition(evt, chart) { - if ('native' in evt) { + if (evt && ('native' in evt)) { return evt; } From 597276edf41fed30d330f21169b802a30ee5fb55 Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Wed, 23 Mar 2022 16:46:46 +0200 Subject: [PATCH 13/14] Update src/helpers/helpers.dom.js Only thing needed actually is the update of chartjs-test-utils to 0.4.0 --- src/helpers/helpers.dom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/helpers.dom.js b/src/helpers/helpers.dom.js index 299dd74f620..c4f54e00936 100644 --- a/src/helpers/helpers.dom.js +++ b/src/helpers/helpers.dom.js @@ -98,7 +98,7 @@ function getCanvasPosition(e, canvas) { * @returns {{x: number, y: number}} */ export function getRelativePosition(evt, chart) { - if (evt && ('native' in evt)) { + if ('native' in evt) { return evt; } From b1c8310f36986cc4befb2bdadfdee2ebe35dca18 Mon Sep 17 00:00:00 2001 From: Josh Kelley Date: Wed, 23 Mar 2022 10:59:13 -0400 Subject: [PATCH 14/14] Bump chartjs-test-utils dependency To get supporting work from https://github.com/chartjs/chartjs-test-utils/pull/19 --- package-lock.json | 23 +++++++++++------------ package.json | 2 +- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 96c2ddae4b7..d87bdabc33b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "@vuepress/plugin-html-redirect": "^0.1.2", "chartjs-adapter-luxon": "^1.0.0", "chartjs-adapter-moment": "^1.0.0", - "chartjs-test-utils": "^0.3.1", + "chartjs-test-utils": "^0.4.0", "concurrently": "^6.0.1", "coveralls": "^3.1.0", "cross-env": "^7.0.3", @@ -5431,15 +5431,17 @@ } }, "node_modules/chartjs-test-utils": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/chartjs-test-utils/-/chartjs-test-utils-0.3.1.tgz", - "integrity": "sha512-QsRYLWOedYGsloDvJsByPNUK44TOiqnxQEO5FOrOm9SguEl5WmJDCOIdd/1ePLOX4gGRClXBDVxD7o1SJY+nWA==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chartjs-test-utils/-/chartjs-test-utils-0.4.0.tgz", + "integrity": "sha512-hT7weEZeWDVduSflHMpoNYW4arxVNp3+u7iZW91P6+zTYLHqgtv1gB/K0wiMqForXvw7IsDWuMF2iEvh3WT1mg==", "dev": true, "dependencies": { + "pixelmatch": "^5.2.1" + }, + "peerDependencies": { "jasmine": "^3.6.4", "karma": "^6.1.1", - "karma-jasmine": "^4.0.1", - "pixelmatch": "^5.2.1" + "karma-jasmine": "^4.0.1" } }, "node_modules/chokidar": { @@ -23361,14 +23363,11 @@ "requires": {} }, "chartjs-test-utils": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/chartjs-test-utils/-/chartjs-test-utils-0.3.1.tgz", - "integrity": "sha512-QsRYLWOedYGsloDvJsByPNUK44TOiqnxQEO5FOrOm9SguEl5WmJDCOIdd/1ePLOX4gGRClXBDVxD7o1SJY+nWA==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chartjs-test-utils/-/chartjs-test-utils-0.4.0.tgz", + "integrity": "sha512-hT7weEZeWDVduSflHMpoNYW4arxVNp3+u7iZW91P6+zTYLHqgtv1gB/K0wiMqForXvw7IsDWuMF2iEvh3WT1mg==", "dev": true, "requires": { - "jasmine": "^3.6.4", - "karma": "^6.1.1", - "karma-jasmine": "^4.0.1", "pixelmatch": "^5.2.1" } }, diff --git a/package.json b/package.json index a2175c4e79d..c5cb4507339 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "@vuepress/plugin-html-redirect": "^0.1.2", "chartjs-adapter-luxon": "^1.0.0", "chartjs-adapter-moment": "^1.0.0", - "chartjs-test-utils": "^0.3.1", + "chartjs-test-utils": "^0.4.0", "concurrently": "^6.0.1", "coveralls": "^3.1.0", "cross-env": "^7.0.3",