mirror of
https://github.com/chartjs/Chart.js.git
synced 2025-12-08 20:36:08 +00:00
Add option to include invisible points (#10362)
* Add option to include invisible points
* Minor fixes
* Add doc for newly added option
* Fix typo
* Add test for newly added option
* Improve description of the new option
* Update docs/configuration/interactions.md
Co-authored-by: Jacco van den Berg <39033624+LeeLenaleee@users.noreply.github.com>
* Apply suggestions from code review
Co-authored-by: Jacco van den Berg <39033624+LeeLenaleee@users.noreply.github.com>
Co-authored-by: Yiwen Wang 🌊 <yiwwan@microsoft.com>
Co-authored-by: Jacco van den Berg <39033624+LeeLenaleee@users.noreply.github.com>
This commit is contained in:
parent
cf780a5db5
commit
ebcaff15c2
@ -7,6 +7,7 @@ Namespace: `options.interaction`, the global interaction configuration is at `Ch
|
||||
| `mode` | `string` | `'nearest'` | Sets which elements appear in the interaction. See [Interaction Modes](#modes) for details.
|
||||
| `intersect` | `boolean` | `true` | if true, the interaction mode only applies when the mouse position intersects an item on the chart.
|
||||
| `axis` | `string` | `'x'` | 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.
|
||||
| `includeInvisible` | `boolean` | `true` | if true, the invisible points that are outside of the chart area will also be included when evaluating interactions.
|
||||
|
||||
By default, these options apply to both the hover and tooltip interactions. The same options can be set in the `options.hover` namespace, in which case they will only affect the hover interaction. Similarly, the options can be set in the `options.plugins.tooltip` namespace to independently configure the tooltip interactions.
|
||||
|
||||
|
||||
@ -62,7 +62,8 @@ export class Defaults {
|
||||
this.indexAxis = 'x';
|
||||
this.interaction = {
|
||||
mode: 'nearest',
|
||||
intersect: true
|
||||
intersect: true,
|
||||
includeInvisible: false
|
||||
};
|
||||
this.maintainAspectRatio = true;
|
||||
this.onHover = null;
|
||||
|
||||
@ -6,7 +6,7 @@ import {_isPointInArea} from '../helpers';
|
||||
/**
|
||||
* @typedef { import("./core.controller").default } Chart
|
||||
* @typedef { import("../../types/index.esm").ChartEvent } ChartEvent
|
||||
* @typedef {{axis?: string, intersect?: boolean}} InteractionOptions
|
||||
* @typedef {{axis?: string, intersect?: boolean, includeInvisible?: boolean}} InteractionOptions
|
||||
* @typedef {{datasetIndex: number, index: number, element: import("./core.element").default}} InteractionItem
|
||||
* @typedef { import("../../types/index.esm").Point } Point
|
||||
*/
|
||||
@ -88,17 +88,18 @@ function getDistanceMetricForAxis(axis) {
|
||||
* @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
|
||||
* @param {boolean} [includeInvisible] - include invisible points that are outside of the chart area
|
||||
* @return {InteractionItem[]} the nearest items
|
||||
*/
|
||||
function getIntersectItems(chart, position, axis, useFinalPosition) {
|
||||
function getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) {
|
||||
const items = [];
|
||||
|
||||
if (!chart.isPointInArea(position)) {
|
||||
if (!includeInvisible && !chart.isPointInArea(position)) {
|
||||
return items;
|
||||
}
|
||||
|
||||
const evaluationFunc = function(element, datasetIndex, index) {
|
||||
if (!_isPointInArea(element, chart.chartArea, 0)) {
|
||||
if (!includeInvisible && !_isPointInArea(element, chart.chartArea, 0)) {
|
||||
return;
|
||||
}
|
||||
if (element.inRange(position.x, position.y, useFinalPosition)) {
|
||||
@ -141,9 +142,10 @@ function getNearestRadialItems(chart, position, axis, useFinalPosition) {
|
||||
* @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
|
||||
* @param {boolean} [includeInvisible] - include invisible points that are outside of the chart area
|
||||
* @return {InteractionItem[]} the nearest items
|
||||
*/
|
||||
function getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition) {
|
||||
function getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition, includeInvisible) {
|
||||
let items = [];
|
||||
const distanceMetric = getDistanceMetricForAxis(axis);
|
||||
let minDistance = Number.POSITIVE_INFINITY;
|
||||
@ -155,7 +157,7 @@ function getNearestCartesianItems(chart, position, axis, intersect, useFinalPosi
|
||||
}
|
||||
|
||||
const center = element.getCenterPoint(useFinalPosition);
|
||||
const pointInArea = chart.isPointInArea(center);
|
||||
const pointInArea = !!includeInvisible || chart.isPointInArea(center);
|
||||
if (!pointInArea && !inRange) {
|
||||
return;
|
||||
}
|
||||
@ -181,16 +183,17 @@ function getNearestCartesianItems(chart, position, axis, intersect, useFinalPosi
|
||||
* @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
|
||||
* @param {boolean} [includeInvisible] - include invisible points that are outside of the chart area
|
||||
* @return {InteractionItem[]} the nearest items
|
||||
*/
|
||||
function getNearestItems(chart, position, axis, intersect, useFinalPosition) {
|
||||
if (!chart.isPointInArea(position)) {
|
||||
function getNearestItems(chart, position, axis, intersect, useFinalPosition, includeInvisible) {
|
||||
if (!includeInvisible && !chart.isPointInArea(position)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return axis === 'r' && !intersect
|
||||
? getNearestRadialItems(chart, position, axis, useFinalPosition)
|
||||
: getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition);
|
||||
: getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition, includeInvisible);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -247,9 +250,10 @@ export default {
|
||||
const position = getRelativePosition(e, chart);
|
||||
// Default axis for index mode is 'x' to match old behaviour
|
||||
const axis = options.axis || 'x';
|
||||
const includeInvisible = options.includeInvisible || false;
|
||||
const items = options.intersect
|
||||
? getIntersectItems(chart, position, axis, useFinalPosition)
|
||||
: getNearestItems(chart, position, axis, false, useFinalPosition);
|
||||
? getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible)
|
||||
: getNearestItems(chart, position, axis, false, useFinalPosition, includeInvisible);
|
||||
const elements = [];
|
||||
|
||||
if (!items.length) {
|
||||
@ -282,9 +286,10 @@ export default {
|
||||
dataset(chart, e, options, useFinalPosition) {
|
||||
const position = getRelativePosition(e, chart);
|
||||
const axis = options.axis || 'xy';
|
||||
const includeInvisible = options.includeInvisible || false;
|
||||
let items = options.intersect
|
||||
? getIntersectItems(chart, position, axis, useFinalPosition) :
|
||||
getNearestItems(chart, position, axis, false, useFinalPosition);
|
||||
? getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) :
|
||||
getNearestItems(chart, position, axis, false, useFinalPosition, includeInvisible);
|
||||
|
||||
if (items.length > 0) {
|
||||
const datasetIndex = items[0].datasetIndex;
|
||||
@ -311,7 +316,8 @@ export default {
|
||||
point(chart, e, options, useFinalPosition) {
|
||||
const position = getRelativePosition(e, chart);
|
||||
const axis = options.axis || 'xy';
|
||||
return getIntersectItems(chart, position, axis, useFinalPosition);
|
||||
const includeInvisible = options.includeInvisible || false;
|
||||
return getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -326,7 +332,8 @@ export default {
|
||||
nearest(chart, e, options, useFinalPosition) {
|
||||
const position = getRelativePosition(e, chart);
|
||||
const axis = options.axis || 'xy';
|
||||
return getNearestItems(chart, position, axis, options.intersect, useFinalPosition);
|
||||
const includeInvisible = options.includeInvisible || false;
|
||||
return getNearestItems(chart, position, axis, options.intersect, useFinalPosition, includeInvisible);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@ -211,7 +211,7 @@ function createResizeObserver(chart, type, listener) {
|
||||
const width = entry.contentRect.width;
|
||||
const height = entry.contentRect.height;
|
||||
// When its container's display is set to 'none' the callback will be called with a
|
||||
// size of (0, 0), which will cause the chart to lost its original height, so skip
|
||||
// size of (0, 0), which will cause the chart to lose its original height, so skip
|
||||
// resizing in such case.
|
||||
if (width === 0 && height === 0) {
|
||||
return;
|
||||
|
||||
@ -870,5 +870,46 @@ describe('Core.Interaction', function() {
|
||||
const elements = Chart.Interaction.modes.point(chart, evt, {intersect: true}).map(item => item.element);
|
||||
expect(elements).not.toContain(firstElement);
|
||||
});
|
||||
|
||||
it ('out-of-range datapoints are shown in tooltip if included', function() {
|
||||
let data = [];
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
data.push({x: i, y: i});
|
||||
}
|
||||
|
||||
const chart = window.acquireChart({
|
||||
type: 'scatter',
|
||||
data: {
|
||||
datasets: [{data}]
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
x: {
|
||||
min: 2
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const meta0 = chart.getDatasetMeta(0);
|
||||
const firstElement = meta0.data[0];
|
||||
|
||||
const evt = {
|
||||
type: 'click',
|
||||
chart: chart,
|
||||
native: true, // needed otherwise it thinks its a DOM event
|
||||
x: firstElement.x,
|
||||
y: firstElement.y
|
||||
};
|
||||
|
||||
const elements = Chart.Interaction.modes.point(
|
||||
chart,
|
||||
evt,
|
||||
{
|
||||
intersect: true,
|
||||
includeInvisible: true
|
||||
}).map(item => item.element);
|
||||
expect(elements).toContain(firstElement);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
7
types/index.esm.d.ts
vendored
7
types/index.esm.d.ts
vendored
@ -701,6 +701,7 @@ export const defaults: Defaults;
|
||||
export interface InteractionOptions {
|
||||
axis?: string;
|
||||
intersect?: boolean;
|
||||
includeInvisible?: boolean;
|
||||
}
|
||||
|
||||
export interface InteractionItem {
|
||||
@ -1434,6 +1435,12 @@ export interface CoreInteractionOptions {
|
||||
* Defines which directions are used in calculating distances. Defaults to 'x' for 'index' mode and 'xy' in dataset and 'nearest' modes.
|
||||
*/
|
||||
axis: InteractionAxis;
|
||||
|
||||
/**
|
||||
* if true, the invisible points that are outside of the chart area will also be included when evaluating interactions.
|
||||
* @default false
|
||||
*/
|
||||
includeInvisible: boolean;
|
||||
}
|
||||
|
||||
export interface CoreChartOptions<TType extends ChartType> extends ParsingOptions, AnimationOptions<TType> {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user