import {_lookupByKey, _rlookupByKey} from '../helpers/helpers.collection.js';
import {getRelativePosition} from '../helpers/helpers.dom.js';
import {_angleBetween, getAngleFromPoint} from '../helpers/helpers.math.js';
-import {_isPointInArea} from '../helpers/index.js';
+import {_isPointInArea, isNullOrUndef} from '../helpers/index.js';
/**
* @typedef { import('./core.controller.js').default } Chart
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 => !isNullOrUndef(point[vScale.axis])));
+ result.lo -= Math.max(0, distanceToDefinedLo);
+
+ const distanceToDefinedHi = (_parsed
+ .slice(result.hi - 1)
+ .findIndex(
+ point => !isNullOrUndef(point[vScale.axis])));
+ 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
import {_limitValue} from './helpers.math.js';
import {_lookupByKey} from './helpers.collection.js';
+import {isNullOrUndef} from './helpers.core.js';
export function fontString(pixelSize: number, fontStyle: string, fontFamily: string) {
return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
.slice(0, start + 1)
.reverse()
.findIndex(
- point => point[vScale.axis] || point[vScale.axis] === 0));
+ point => !isNullOrUndef(point[vScale.axis])));
start -= Math.max(0, distanceToDefinedLo);
}
start = _limitValue(start, 0, pointCount - 1);
const distanceToDefinedHi = (_parsed
.slice(end - 1)
.findIndex(
- point => point[vScale.axis] || point[vScale.axis] === 0));
+ point => !isNullOrUndef(point[vScale.axis])));
end += Math.max(0, distanceToDefinedHi);
}
count = _limitValue(end, start, pointCount) - start;
expect(elements).toContain(firstElement);
});
});
+
+ const testCases = [
+ {
+ data: [12, 19, null, null, null, null, 5, 2],
+ clickPointIndex: 0,
+ expectedNearestPointIndex: 0
+ },
+ {
+ data: [12, 19, null, null, null, null, 5, 2],
+ clickPointIndex: 1,
+ expectedNearestPointIndex: 1},
+ {
+ data: [12, 19, null, null, null, null, 5, 2],
+ clickPointIndex: 2,
+ expectedNearestPointIndex: 1
+ },
+ {
+ data: [12, 19, null, null, null, null, 5, 2],
+ clickPointIndex: 3,
+ expectedNearestPointIndex: 1
+ },
+ {
+ data: [12, 19, null, null, null, null, 5, 2],
+ clickPointIndex: 4,
+ expectedNearestPointIndex: 6
+ },
+ {
+ data: [12, 19, null, null, null, null, 5, 2],
+ clickPointIndex: 5,
+ expectedNearestPointIndex: 6
+ },
+ {
+ data: [12, 19, null, null, null, null, 5, 2],
+ clickPointIndex: 6,
+ expectedNearestPointIndex: 6
+ },
+ {
+ data: [12, 19, null, null, null, null, 5, 2],
+ clickPointIndex: 7,
+ expectedNearestPointIndex: 7
+ },
+ {
+ data: [12, 0, null, null, null, null, 0, 2],
+ clickPointIndex: 3,
+ expectedNearestPointIndex: 1
+ },
+ {
+ data: [12, 0, null, null, null, null, 0, 2],
+ clickPointIndex: 4,
+ expectedNearestPointIndex: 6
+ },
+ {
+ data: [12, -1, null, null, null, null, -1, 2],
+ clickPointIndex: 3,
+ expectedNearestPointIndex: 1
+ },
+ {
+ data: [12, -1, null, null, null, null, -1, 2],
+ clickPointIndex: 4,
+ expectedNearestPointIndex: 6
+ }
+ ];
+ testCases.forEach(({data, clickPointIndex, expectedNearestPointIndex}, i) => {
+ it(`should select nearest non-null element with index ${expectedNearestPointIndex} when clicking on element with index ${clickPointIndex} in test case ${i + 1} if spanGaps=true`, function() {
+ const chart = window.acquireChart({
+ type: 'line',
+ data: {
+ labels: [1, 2, 3, 4, 5, 6, 7, 8, 9],
+ datasets: [{
+ data: data,
+ spanGaps: true,
+ }]
+ }
+ });
+ chart.update();
+ const meta = chart.getDatasetMeta(0);
+ const point = meta.data[clickPointIndex];
+
+ const evt = {
+ type: 'click',
+ chart: chart,
+ native: true, // needed otherwise things its a DOM event
+ x: point.x,
+ y: point.y,
+ };
+
+ const elements = Chart.Interaction.modes.nearest(chart, evt, {axis: 'x', intersect: false}).map(item => item.element);
+ expect(elements).toEqual([meta.data[expectedNearestPointIndex]]);
+ });
+ });
});