From: Evert Timberg Date: Mon, 5 Oct 2020 21:14:38 +0000 (-0400) Subject: Provide APIs to set active (hovered) and tooltip elements. (#7845) X-Git-Tag: v3.0.0-beta.4~20 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a8a83d12cda2901b84aaf987bdc589cca215feb7;p=thirdparty%2FChart.js.git Provide APIs to set active (hovered) and tooltip elements. (#7845) Provide APIs to set active (hovered) and tooltip elements. Chart.setActiveElements will set the hovered items. Chart.tooltip.setActiveElements will set the tooltip items. --- diff --git a/docs/docs/developers/api.md b/docs/docs/developers/api.md index 2ce6c25d5..175c52371 100644 --- a/docs/docs/developers/api.md +++ b/docs/docs/developers/api.md @@ -205,6 +205,16 @@ Sets the visibility for the given dataset to true. Updates the chart and animate chart.show(1); // shows dataset at index 1 and does 'show' animation. ``` +## setActiveElements(activeElements) + +Sets the active (hovered) elements for the chart. See the "Programmatic Events" sample file to see this in action. + +```javascript +chart.setActiveElements([ + {datasetIndex: 0, index: 1}, +]); +``` + ## Static: getChart(key) Finds the chart instance from the given key. If the key is a `string`, it is interpreted as the ID of the Canvas node for the Chart. The key can also be a `CanvasRenderingContext2D` or an `HTMLDOMElement`. This will return `undefined` if no Chart is found. To be found, the chart must have previously been created. diff --git a/samples/advanced/programmatic-events.html b/samples/advanced/programmatic-events.html new file mode 100644 index 000000000..d6b1dc22a --- /dev/null +++ b/samples/advanced/programmatic-events.html @@ -0,0 +1,120 @@ + + + + + Programmatic Event Triggers + + + + + + +
+ +
+ + + + + diff --git a/samples/samples.js b/samples/samples.js index 1f4fc3770..3a6bee7a1 100644 --- a/samples/samples.js +++ b/samples/samples.js @@ -250,6 +250,9 @@ }, { title: 'Line Gradient', path: 'advanced/line-gradient.html' + }, { + title: 'Programmatic Event Triggers', + path: 'advanced/programmatic-events.html' }] }]; diff --git a/src/core/core.controller.js b/src/core/core.controller.js index a43090a09..2c2ba3285 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -1061,6 +1061,41 @@ class Chart { } } + /** + * Get active (hovered) elements + * @returns array + */ + getActiveElements() { + return this._active || []; + } + + /** + * Set active (hovered) elements + * @param {array} activeElements New active data points + */ + setActiveElements(activeElements) { + const me = this; + const lastActive = me._active || []; + const active = activeElements.map(({datasetIndex, index}) => { + const meta = me.getDatasetMeta(datasetIndex); + if (!meta) { + throw new Error('No dataset found at index ' + datasetIndex); + } + + return { + datasetIndex, + element: meta.data[index], + index, + }; + }); + const changed = !_elementsEqual(active, lastActive); + + if (changed) { + me._active = active; + me._updateHoverStyles(active, lastActive); + } + } + /** * @private */ diff --git a/src/plugins/plugin.tooltip.js b/src/plugins/plugin.tooltip.js index 67299892b..3e2084f85 100644 --- a/src/plugins/plugin.tooltip.js +++ b/src/plugins/plugin.tooltip.js @@ -908,6 +908,45 @@ export class Tooltip extends Element { } } + /** + * Get active elements in the tooltip + * @returns {Array} Array of elements that are active in the tooltip + */ + getActiveElements() { + return this._active || []; + } + + /** + * Set active elements in the tooltip + * @param {array} activeElements Array of active datasetIndex/index pairs. + * @param {object} eventPosition Synthetic event position used in positioning + */ + setActiveElements(activeElements, eventPosition) { + const me = this; + const lastActive = me._active; + const active = activeElements.map(({datasetIndex, index}) => { + const meta = me._chart.getDatasetMeta(datasetIndex); + + if (!meta) { + throw new Error('Cannot find a dataset at index ' + datasetIndex); + } + + return { + datasetIndex, + element: meta.data[index], + index, + }; + }); + const changed = !_elementsEqual(lastActive, active); + const positionChanged = me._positionChanged(active, eventPosition); + + if (changed || positionChanged) { + me._active = active; + me._eventPosition = eventPosition; + me.update(true); + } + } + /** * Handle an event * @param {IEvent} e - The event to handle @@ -932,8 +971,7 @@ export class Tooltip extends Element { // When there are multiple items shown, but the tooltip position is nearest mode // an update may need to be made because our position may have changed even though // the items are the same as before. - const position = positioners[options.position].call(me, active, e); - const positionChanged = this.caretX !== position.x || this.caretY !== position.y; + const positionChanged = me._positionChanged(active, e); // Remember Last Actives changed = replay || !_elementsEqual(active, lastActive) || positionChanged; @@ -954,6 +992,19 @@ export class Tooltip extends Element { return changed; } + + /** + * Determine if the active elements + event combination changes the + * tooltip position + * @param {array} active - Active elements + * @param {IEvent} e - Event that triggered the position change + * @returns {boolean} True if the position has changed + */ + _positionChanged(active, e) { + const me = this; + const position = positioners[me.options.position].call(me, active, e); + return me.caretX !== position.x || me.caretY !== position.y; + } } /** diff --git a/test/specs/core.controller.tests.js b/test/specs/core.controller.tests.js index ac79cab77..7467c0830 100644 --- a/test/specs/core.controller.tests.js +++ b/test/specs/core.controller.tests.js @@ -1511,4 +1511,35 @@ describe('Chart', function() { expect(Chart.getChart(1)).toBeUndefined(); }); }); + + describe('active elements', function() { + it('should set the active elements', function() { + var chart = acquireChart({ + type: 'pie', + data: { + datasets: [{ + data: [1, 2, 3], + borderColor: 'red', + hoverBorderColor: 'blue', + }] + } + }); + + const meta = chart.getDatasetMeta(0); + let props = meta.data[0].getProps(['borderColor']); + expect(props.options.borderColor).toEqual('red'); + + chart.setActiveElements([{ + datasetIndex: 0, + index: 0, + }]); + + props = meta.data[0].getProps(['borderColor']); + expect(props.options.borderColor).toEqual('blue'); + + const active = chart.getActiveElements(); + expect(active.length).toEqual(1); + expect(active[0].element).toBe(meta.data[0]); + }); + }); }); diff --git a/test/specs/plugin.tooltip.tests.js b/test/specs/plugin.tooltip.tests.js index a117b2d41..9aa6d8344 100644 --- a/test/specs/plugin.tooltip.tests.js +++ b/test/specs/plugin.tooltip.tests.js @@ -1490,4 +1490,25 @@ describe('Plugin.Tooltip', function() { ])); }); }); + + describe('active events', function() { + it('should set the active events', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }, + }); + + const meta = chart.getDatasetMeta(0); + chart.tooltip.setActiveElements([{datasetIndex: 0, index: 0}], {x: 0, y: 0}); + expect(chart.tooltip.getActiveElements()[0].element).toBe(meta.data[0]); + }); + }); }); diff --git a/types/core/index.d.ts b/types/core/index.d.ts index 1d54f1fc1..5936a5f11 100644 --- a/types/core/index.d.ts +++ b/types/core/index.d.ts @@ -240,6 +240,15 @@ export interface IParsingOptions { | false; } +export interface ActiveDataPoint { + datasetIndex: number; + index: number; +} + +export interface ActiveElement extends ActiveDataPoint { + element: Element; +} + export declare class Chart< TYPE extends IChartType = IChartType, DATA extends unknown[] = DefaultDataPoint, @@ -293,6 +302,9 @@ export declare class Chart< hide(datasetIndex: number): void; show(datasetIndex: number): void; + getActiveElements(): ActiveElement[]; + setActiveElements(active: ActiveDataPoint[]); + destroy(): void; toBase64Image(type?: string, quality?: any): string; bindEvents(): void; diff --git a/types/plugins/index.d.ts b/types/plugins/index.d.ts index fa7878071..efa0eda00 100644 --- a/types/plugins/index.d.ts +++ b/types/plugins/index.d.ts @@ -1,4 +1,4 @@ -import { Chart, Element, IAnimationSpecContainer, InteractionMode, LayoutPosition, IPlugin } from '../core'; +import { ActiveDataPoint, ActiveElement, Chart, Element, IAnimationSpecContainer, InteractionMode, LayoutPosition, IPlugin } from '../core'; import { Color, IChartArea, IFontSpec, Scriptable, TextAlign, IEvent, IHoverInteractionOptions } from '../core/interfaces'; import { PointStyle } from '../elements'; import { IChartData, IChartDataset } from '../interfaces'; @@ -281,6 +281,9 @@ export const Tooltip: IPlugin & { readonly positioners: { [key: string]: (items: readonly Element[], eventPosition: { x: number; y: number }) => { x: number; y: number }; }; + + getActiveElements(): ActiveElement[]; + setActiveElements(active: ActiveDataPoint[], eventPosition: { x: number, y: number }); }; export interface ITooltipCallbacks {