##### Dataset Controllers
-* `updateElement` was replaced with `updateElements` now taking the elements to update, the `start` index, and `mode`
+* `updateElement` was replaced with `updateElements` now taking the elements to update, the `start` index, `count`, and `mode`
* `setHoverStyle` and `removeHoverStyle` now additionally take the `datasetIndex` and `index`
#### Interactions
const me = this;
const meta = me._cachedMeta;
- me.updateElements(meta.data, 0, mode);
+ me.updateElements(meta.data, 0, meta.data.length, mode);
}
- updateElements(rectangles, start, mode) {
+ updateElements(rectangles, start, count, mode) {
const me = this;
const reset = mode === 'reset';
const vscale = me._cachedMeta.vScale;
me.updateSharedOptions(sharedOptions, mode, firstOpts);
- for (let i = 0; i < rectangles.length; i++) {
- const index = start + i;
- const options = sharedOptions || me.resolveDataElementOptions(index, mode);
- const vpixels = me._calculateBarValuePixels(index, options);
- const ipixels = me._calculateBarIndexPixels(index, ruler, options);
+ for (let i = start; i < start + count; i++) {
+ const options = sharedOptions || me.resolveDataElementOptions(i, mode);
+ const vpixels = me._calculateBarValuePixels(i, options);
+ const ipixels = me._calculateBarIndexPixels(i, ruler, options);
const properties = {
horizontal,
if (includeOptions) {
properties.options = options;
}
- me.updateElement(rectangles[i], index, properties, mode);
+ me.updateElement(rectangles[i], i, properties, mode);
}
}
const points = me._cachedMeta.data;
// Update Points
- me.updateElements(points, 0, mode);
+ me.updateElements(points, 0, points.length, mode);
}
- updateElements(points, start, mode) {
+ updateElements(points, start, count, mode) {
const me = this;
const reset = mode === 'reset';
const {xScale, yScale} = me._cachedMeta;
const sharedOptions = me.getSharedOptions(firstOpts);
const includeOptions = me.includeOptions(mode, sharedOptions);
- for (let i = 0; i < points.length; i++) {
+ for (let i = start; i < start + count; i++) {
const point = points[i];
- const index = start + i;
- const parsed = !reset && me.getParsed(index);
+ const parsed = !reset && me.getParsed(i);
const x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(parsed.x);
const y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(parsed.y);
const properties = {
};
if (includeOptions) {
- properties.options = me.resolveDataElementOptions(index, mode);
+ properties.options = me.resolveDataElementOptions(i, mode);
if (reset) {
properties.options.radius = 0;
}
}
- me.updateElement(point, index, properties, mode);
+ me.updateElement(point, i, properties, mode);
}
me.updateSharedOptions(sharedOptions, mode, firstOpts);
me.outerRadius = outerRadius - radiusLength * me._getRingWeightOffset(me.index);
me.innerRadius = Math.max(me.outerRadius - radiusLength * chartWeight, 0);
- me.updateElements(arcs, 0, mode);
+ me.updateElements(arcs, 0, arcs.length, mode);
}
/**
return reset && opts.animation.animateRotate ? 0 : this.chart.getDataVisibility(i) ? me.calculateCircumference(meta._parsed[i] * opts.circumference / DOUBLE_PI) : 0;
}
- updateElements(arcs, start, mode) {
+ updateElements(arcs, start, count, mode) {
const me = this;
const reset = mode === 'reset';
const chart = me.chart;
startAngle += me._circumference(i, reset);
}
- for (i = 0; i < arcs.length; ++i) {
- const index = start + i;
- const circumference = me._circumference(index, reset);
+ for (i = start; i < start + count; ++i) {
+ const circumference = me._circumference(i, reset);
const arc = arcs[i];
const properties = {
x: centerX + me.offsetX,
innerRadius
};
if (includeOptions) {
- properties.options = sharedOptions || me.resolveDataElementOptions(index, mode);
+ properties.options = sharedOptions || me.resolveDataElementOptions(i, mode);
}
startAngle += circumference;
- me.updateElement(arc, index, properties, mode);
+ me.updateElement(arc, i, properties, mode);
}
me.updateSharedOptions(sharedOptions, mode, firstOpts);
}
import {valueOrDefault} from '../helpers/helpers.core';
import {isNumber} from '../helpers/helpers.math';
import {resolve} from '../helpers/helpers.options';
+import {_lookupByKey} from '../helpers/helpers.collection';
export default class LineController extends DatasetController {
update(mode) {
const me = this;
const meta = me._cachedMeta;
- const line = meta.dataset;
- const points = meta.data || [];
+ const {dataset: line, data: points = []} = meta;
+ const {start, count} = getStartAndCountOfVisiblePoints(meta, points);
+
+ me._drawStart = start;
+ me._drawCount = count;
// Update Line
// In resize mode only point locations change, so no need to set the points or options.
}
// Update Points
- me.updateElements(points, 0, mode);
+ me.updateElements(points, start, count, mode);
}
- updateElements(points, start, mode) {
+ updateElements(points, start, count, mode) {
const me = this;
const reset = mode === 'reset';
const {xScale, yScale, _stacked} = me._cachedMeta;
const includeOptions = me.includeOptions(mode, sharedOptions);
const spanGaps = valueOrDefault(me._config.spanGaps, me.chart.options.spanGaps);
const maxGapLength = isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY;
- let prevParsed;
+ let prevParsed = start > 0 && me.getParsed(start - 1);
- for (let i = 0; i < points.length; ++i) {
- const index = start + i;
+ for (let i = start; i < start + count; ++i) {
const point = points[i];
- const parsed = me.getParsed(index);
- const x = xScale.getPixelForValue(parsed.x, index);
- const y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(_stacked ? me.applyStack(yScale, parsed) : parsed.y, index);
+ const parsed = me.getParsed(i);
+ const x = xScale.getPixelForValue(parsed.x, i);
+ const y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(_stacked ? me.applyStack(yScale, parsed) : parsed.y, i);
const properties = {
x,
y,
};
if (includeOptions) {
- properties.options = sharedOptions || me.resolveDataElementOptions(index, mode);
+ properties.options = sharedOptions || me.resolveDataElementOptions(i, mode);
}
- me.updateElement(point, index, properties, mode);
+ me.updateElement(point, i, properties, mode);
prevParsed = parsed;
}
},
}
};
+
+function getStartAndCountOfVisiblePoints(meta, points) {
+ const pointCount = points.length;
+
+ let start = 0;
+ let count = pointCount;
+
+ if (meta._sorted) {
+ const {iScale, _parsed} = meta;
+ const {min, max, minDefined, maxDefined} = iScale.getUserBounds();
+ start = minDefined ? Math.max(0, _lookupByKey(_parsed, iScale.axis, min).lo) : 0;
+ count = (maxDefined ? Math.min(pointCount, _lookupByKey(_parsed, iScale.axis, max).hi + 1) : pointCount) - start;
+ }
+
+ return {start, count};
+}
const arcs = this._cachedMeta.data;
this._updateRadius();
- this.updateElements(arcs, 0, mode);
+ this.updateElements(arcs, 0, arcs.length, mode);
}
/**
me.innerRadius = me.outerRadius - radiusLength;
}
- updateElements(arcs, start, mode) {
+ updateElements(arcs, start, count, mode) {
const me = this;
const reset = mode === 'reset';
const chart = me.chart;
for (i = 0; i < start; ++i) {
angle += me._computeAngle(i);
}
- for (i = 0; i < arcs.length; i++) {
+ for (i = start; i < start + count; i++) {
const arc = arcs[i];
- const index = start + i;
let startAngle = angle;
- let endAngle = angle + me._computeAngle(index);
- let outerRadius = this.chart.getDataVisibility(index) ? scale.getDistanceFromCenterForValue(dataset.data[index]) : 0;
+ let endAngle = angle + me._computeAngle(i);
+ let outerRadius = this.chart.getDataVisibility(i) ? scale.getDistanceFromCenterForValue(dataset.data[i]) : 0;
angle = endAngle;
if (reset) {
outerRadius,
startAngle,
endAngle,
- options: me.resolveDataElementOptions(index, mode)
+ options: me.resolveDataElementOptions(i, mode)
};
- me.updateElement(arc, index, properties, mode);
+ me.updateElement(arc, i, properties, mode);
}
}
}
// Update Points
- me.updateElements(points, 0, mode);
+ me.updateElements(points, 0, points.length, mode);
}
- updateElements(points, start, mode) {
+ updateElements(points, start, count, mode) {
const me = this;
const dataset = me.getDataset();
const scale = me._cachedMeta.rScale;
const reset = mode === 'reset';
- let i;
- for (i = 0; i < points.length; i++) {
+ for (let i = start; i < start + count; i++) {
const point = points[i];
- const index = start + i;
- const options = me.resolveDataElementOptions(index, mode);
- const pointPosition = scale.getPointPositionForValue(index, dataset.data[index]);
+ const options = me.resolveDataElementOptions(i, mode);
+ const pointPosition = scale.getPointPositionForValue(i, dataset.data[i]);
const x = reset ? scale.xCenter : pointPosition.x;
const y = reset ? scale.yCenter : pointPosition.y;
options
};
- me.updateElement(point, index, properties, mode);
+ me.updateElement(point, i, properties, mode);
}
}
this._data = undefined;
this._objectData = undefined;
this._sharedOptions = undefined;
+ this._drawStart = undefined;
+ this._drawCount = undefined;
this.enableOptionSharing = false;
this.initialize();
parsed = me.parsePrimitiveData(meta, data, start, count);
}
-
+ const isNotInOrderComparedToPrev = () => isNaN(cur[iAxis]) || (prev && cur[iAxis] < prev[iAxis]);
for (i = 0; i < count; ++i) {
meta._parsed[i + start] = cur = parsed[i];
if (sorted) {
- if (prev && cur[iAxis] < prev[iAxis]) {
+ if (isNotInOrderComparedToPrev()) {
sorted = false;
}
prev = cur;
parsed = _parsed[i];
value = parsed[scale.axis];
otherValue = parsed[otherScale.axis];
- return (isNaN(value) || otherMin > otherValue || otherMax < otherValue);
+ return (isNaN(value) || isNaN(otherValue) || otherMin > otherValue || otherMax < otherValue);
}
for (i = 0; i < ilen; ++i) {
const elements = meta.data || [];
const area = chart.chartArea;
const active = [];
- let i, ilen;
+ const start = me._drawStart || 0;
+ const count = me._drawCount || (elements.length - start);
+ let i;
if (meta.dataset) {
- meta.dataset.draw(ctx, area);
+ meta.dataset.draw(ctx, area, start, count);
}
- for (i = 0, ilen = elements.length; i < ilen; ++i) {
+ for (i = start; i < start + count; ++i) {
const element = elements[i];
if (element.active) {
active.push(element);
}
}
- for (i = 0, ilen = active.length; i < ilen; ++i) {
+ for (i = 0; i < active.length; ++i) {
active[i].draw(ctx, area);
}
}
}
me.parse(start, count);
- me.updateElements(elements, start, 'reset');
+ me.updateElements(data, start, count, 'reset');
}
- updateElements(element, start, mode) {} // eslint-disable-line no-unused-vars
+ updateElements(element, start, count, mode) {} // eslint-disable-line no-unused-vars
/**
* @private
return lineTo;
}
+function pathVars(points, segment, params) {
+ params = params || {};
+ const count = points.length;
+ const start = Math.max(params.start || 0, segment.start);
+ const end = Math.min(params.end || count - 1, segment.end);
+
+ return {
+ count,
+ start,
+ loop: segment.loop,
+ ilen: end < start ? count + end - start : end - start
+ };
+}
+
/**
* Create path from points, grouping by truncated x-coordinate
* Points need to be in order by x-coordinate for this to work efficiently
* @param {number} segment.end - end index of the segment, referring the points array
* @param {boolean} segment.loop - indicates that the segment is a loop
* @param {object} params
- * @param {object} params.move - move to starting point (vs line to it)
- * @param {object} params.reverse - path the segment from end to start
+ * @param {boolean} params.move - move to starting point (vs line to it)
+ * @param {boolean} params.reverse - path the segment from end to start
+ * @param {number} params.start - limit segment to points starting from `start` index
+ * @param {number} params.end - limit segment to points ending at `start` + `count` index
*/
function pathSegment(ctx, line, segment, params) {
- const {start, end, loop} = segment;
const {points, options} = line;
+ const {count, start, loop, ilen} = pathVars(points, segment, params);
const lineMethod = getLineMethod(options);
- const count = points.length;
// eslint-disable-next-line prefer-const
let {move = true, reverse} = params || {};
- const ilen = end < start ? count + end - start : end - start;
let i, point, prev;
for (i = 0; i <= ilen; ++i) {
* @param {number} segment.end - end index of the segment, referring the points array
* @param {boolean} segment.loop - indicates that the segment is a loop
* @param {object} params
- * @param {object} params.move - move to starting point (vs line to it)
- * @param {object} params.reverse - path the segment from end to start
+ * @param {boolean} params.move - move to starting point (vs line to it)
+ * @param {boolean} params.reverse - path the segment from end to start
+ * @param {number} params.start - limit segment to points starting from `start` index
+ * @param {number} params.end - limit segment to points ending at `start` + `count` index
*/
function fastPathSegment(ctx, line, segment, params) {
const points = line.points;
- const count = points.length;
- const {start, end} = segment;
+ const {count, start, ilen} = pathVars(points, segment, params);
const {move = true, reverse} = params || {};
- const ilen = end < start ? count + end - start : end - start;
let avgX = 0;
let countX = 0;
let i, point, prevX, minY, maxY, lastY;
* @param {number} segment.end - end index of the segment, referring the points array
* @param {boolean} segment.loop - indicates that the segment is a loop
* @param {object} params
- * @param {object} params.move - move to starting point (vs line to it)
- * @param {object} params.reverse - path the segment from end to start
+ * @param {boolean} params.move - move to starting point (vs line to it)
+ * @param {boolean} params.reverse - path the segment from end to start
+ * @param {number} params.start - limit segment to points starting from `start` index
+ * @param {number} params.end - limit segment to points ending at `start` + `count` index
* @returns {undefined|boolean} - true if the segment is a full loop (path should be closed)
*/
pathSegment(ctx, segment, params) {
/**
* Append all segments of this line to current path.
* @param {CanvasRenderingContext2D} ctx
+ * @param {number} [start]
+ * @param {number} [count]
* @returns {undefined|boolean} - true if line is a full loop (path should be closed)
*/
- path(ctx) {
+ path(ctx, start, count) {
const me = this;
const segments = me.segments;
const ilen = segments.length;
const segmentMethod = _getSegmentMethod(me);
let loop = me._loop;
+
+ start = start || 0;
+ count = count || (me.points.length - start);
+
for (let i = 0; i < ilen; ++i) {
- loop &= segmentMethod(ctx, me, segments[i]);
+ loop &= segmentMethod(ctx, me, segments[i], {start, end: start + count - 1});
}
return !!loop;
}
/**
* Draw
* @param {CanvasRenderingContext2D} ctx
+ * @param {object} chartArea
+ * @param {number} [start]
+ * @param {number} [count]
*/
- draw(ctx) {
+ draw(ctx, chartArea, start, count) {
const options = this.options || {};
const points = this.points || [];
ctx.beginPath();
- if (this.path(ctx)) {
+ if (this.path(ctx, start, count)) {
ctx.closePath();
}
linkScales(): void;
getAllParsedValues(scale: Scale): number[];
protected getLabelAndValue(index: number): { label: string; value: string };
- updateElements(elements: E[], start: number, mode: UpdateMode): void;
+ updateElements(elements: E[], start: number, count: number, mode: UpdateMode): void;
update(mode: UpdateMode): void;
updateIndex(datasetIndex: number): void;
protected getMaxOverflow(): boolean | number;