From 03de1239c9875a659ef7f8009be1b7eecb8baec8 Mon Sep 17 00:00:00 2001 From: Josh Kelley Date: Tue, 15 Feb 2022 17:26:11 -0500 Subject: [PATCH] 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 {