diff --git a/src/controllers/controller.line.js b/src/controllers/controller.line.js index fddd5ce9889..ec2ea1ef5b7 100644 --- a/src/controllers/controller.line.js +++ b/src/controllers/controller.line.js @@ -45,14 +45,18 @@ export default class LineController extends DatasetController { const animationsDisabled = this.chart._animationsDisabled; let {start, count} = _getStartAndCountOfVisiblePoints(meta, points, animationsDisabled); - this._drawStart = start; - this._drawCount = count; - if (_scaleRangesChanged(meta)) { start = 0; count = points.length; } + if (count !== points.length) { + ({start, count} = this._includeBoundaryPoints(meta, start, count)); + } + + this._drawStart = start; + this._drawCount = count; + // Update Line line._chart = this.chart; line._datasetIndex = this.index; @@ -73,6 +77,154 @@ export default class LineController extends DatasetController { this.updateElements(points, start, count, mode); } + _includeBoundaryPoints(meta, start, count) { + const {iScale, _parsed} = meta; + const {min, max, axis: iAxis} = iScale; + + if (!_parsed?.length || !isNumber(min) || !isNumber(max)) { + return {start, count}; + } + + const length = _parsed.length; + + // Find the largest value < min (beforeIndex) using binary search. + let beforeIndex = -1; + let beforeValue = -Infinity; + { + let low = 0; + let high = length - 1; + while (low <= high) { + let mid = (low + high) >> 1; + let value = _parsed[mid][iAxis]; + + // If value is not numeric, search outward from mid to find a nearby numeric value. + if (!isNumber(value)) { + let left = mid - 1; + let right = mid + 1; + let found = false; + while (left >= low || right <= high) { + if (left >= low) { + const lv = _parsed[left][iAxis]; + if (isNumber(lv)) { + value = lv; + mid = left; + found = true; + break; + } + left--; + } + if (right <= high) { + const rv = _parsed[right][iAxis]; + if (isNumber(rv)) { + value = rv; + mid = right; + found = true; + break; + } + right++; + } + } + if (!found) { + // No numeric values in current search window. + break; + } + } + + if (!isNumber(value)) { + break; + } + + if (value < min) { + if (value > beforeValue) { + beforeValue = value; + beforeIndex = mid; + } + low = mid + 1; + } else { + high = mid - 1; + } + } + } + + // Find the smallest value > max (afterIndex) using binary search. + let afterIndex = -1; + let afterValue = Infinity; + { + let low = 0; + let high = length - 1; + while (low <= high) { + let mid = (low + high) >> 1; + let value = _parsed[mid][iAxis]; + + // If value is not numeric, search outward from mid to find a nearby numeric value. + if (!isNumber(value)) { + let left = mid - 1; + let right = mid + 1; + let found = false; + while (left >= low || right <= high) { + if (left >= low) { + const lv = _parsed[left][iAxis]; + if (isNumber(lv)) { + value = lv; + mid = left; + found = true; + break; + } + left--; + } + if (right <= high) { + const rv = _parsed[right][iAxis]; + if (isNumber(rv)) { + value = rv; + mid = right; + found = true; + break; + } + right++; + } + } + if (!found) { + // No numeric values in current search window. + break; + } + } + + if (!isNumber(value)) { + break; + } + + if (value > max) { + if (value < afterValue) { + afterValue = value; + afterIndex = mid; + } + high = mid - 1; + } else { + low = mid + 1; + } + } + } + + let drawStart = start; + let drawEnd = count > 0 ? start + count - 1 : -1; + + if (beforeIndex !== -1) { + drawStart = drawEnd === -1 ? beforeIndex : Math.min(drawStart, beforeIndex); + drawEnd = Math.max(drawEnd, beforeIndex); + } + + if (afterIndex !== -1) { + drawStart = drawEnd === -1 ? afterIndex : Math.min(drawStart, afterIndex); + drawEnd = Math.max(drawEnd, afterIndex); + } + + if (drawEnd >= drawStart && drawEnd !== -1) { + return {start: drawStart, count: drawEnd - drawStart + 1}; + } + + return {start, count}; + } + updateElements(points, start, count, mode) { const reset = mode === 'reset'; const {iScale, vScale, _stacked, _dataset} = this._cachedMeta; diff --git a/src/core/core.datasetController.js b/src/core/core.datasetController.js index 9b7126a93fd..dfa09473713 100644 --- a/src/core/core.datasetController.js +++ b/src/core/core.datasetController.js @@ -459,8 +459,26 @@ export default class DatasetController { if (this._parsing === false) { meta._parsed = data; - meta._sorted = true; parsed = data; + + if (sorted && iScale) { + const axis = iAxis; + + for (i = start; i < start + count; ++i) { + cur = parsed[i]; + const curValue = cur && cur[axis]; + const prevValue = prev && prev[axis]; + + if (curValue === null || curValue === undefined || (prevValue !== undefined && curValue < prevValue)) { + sorted = false; + break; + } + + prev = cur; + } + } + + meta._sorted = sorted; } else { if (isArray(data[start])) { parsed = this.parseArrayData(meta, data, start, count);