Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix: return nearest non-null point on interaction when spanGaps=true #11986

Merged
merged 7 commits into from
Jan 3, 2025
22 changes: 21 additions & 1 deletion src/core/core.interaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,30 @@ import {_isPointInArea} from '../helpers/index.js';
function binarySearch(metaset, axis, value, intersect) {
const {controller, data, _sorted} = metaset;
const iScale = controller._cachedMeta.iScale;
const spanGaps = metaset.dataset ? metaset.dataset.options ? metaset.dataset.options.spanGaps : null : null;

if (iScale && axis === iScale.axis && axis !== 'r' && _sorted && data.length) {
const lookupMethod = iScale._reversePixels ? _rlookupByKey : _lookupByKey;
if (!intersect) {
return lookupMethod(data, axis, value);
const result = lookupMethod(data, axis, value);
if (spanGaps) {
const {vScale} = controller._cachedMeta;
const {_parsed} = metaset;

const distanceToDefinedLo = (_parsed
.slice(0, result.lo + 1)
.reverse()
.findIndex(
point => point[vScale.axis] || point[vScale.axis] === 0));
kurkle marked this conversation as resolved.
Show resolved Hide resolved
result.lo -= Math.max(0, distanceToDefinedLo);

const distanceToDefinedHi = (_parsed
.slice(result.hi - 1)
.findIndex(
point => point[vScale.axis] || point[vScale.axis] === 0));
result.hi += Math.max(0, distanceToDefinedHi);
}
return result;
} else if (controller._sharedOptions) {
// _sharedOptions indicates that each element has equal options -> equal proportions
// So we can do a ranged binary search based on the range of first element and
Expand Down
58 changes: 58 additions & 0 deletions test/specs/core.interaction.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -912,4 +912,62 @@ describe('Core.Interaction', function() {
expect(elements).toContain(firstElement);
});
});

it('should select closest non-null elements if spanGaps=true and closest non-null element is to the left', function() {
const chart = window.acquireChart({
type: 'line',
data: {
labels: [1, 2, 3, 4, 5, 6, 7, 8, 9],
datasets: [{
data: [12, 19, null, null, null, null, 5, 2, 3],
spanGaps: true,
}]
}
});
chart.update();
const interactionPointIndex = 3;
const meta = chart.getDatasetMeta(0);
const point = meta.data[interactionPointIndex];

const evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: point.x,
y: point.y,
};

const expectedInteractionPointIndex = 1;
const elements = Chart.Interaction.modes.nearest(chart, evt, {axis: 'x', intersect: false}).map(item => item.element);
expect(elements).toEqual([meta.data[expectedInteractionPointIndex]]);
});

it('should select closest non-null elements if spanGaps=true and closest non-null element is to the right', function() {
const chart = window.acquireChart({
type: 'line',
data: {
labels: [1, 2, 3, 4, 5, 6, 7, 8, 9],
datasets: [{
data: [12, 19, null, null, null, null, 5, 2, 3],
kurkle marked this conversation as resolved.
Show resolved Hide resolved
spanGaps: true,
}]
}
});
chart.update();
const interactionPointIndex = 4;
const meta = chart.getDatasetMeta(0);
const point = meta.data[interactionPointIndex];

const evt = {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: point.x,
y: point.y,
};

const expectedInteractionPointIndex = 6;
const elements = Chart.Interaction.modes.nearest(chart, evt, {axis: 'x', intersect: false}).map(item => item.element);
expect(elements).toEqual([meta.data[expectedInteractionPointIndex]]);
});
});
Loading