import DatasetController from '../core/core.datasetController';
import {isNullOrUndef} from '../helpers';
-import {_limitValue, isNumber} from '../helpers/helpers.math';
-import {_lookupByKey} from '../helpers/helpers.collection';
+import {isNumber} from '../helpers/helpers.math';
+import {_getStartAndCountOfVisiblePoints, _scaleRangesChanged} from '../helpers/helpers.extras';
export default class LineController extends DatasetController {
const {dataset: line, data: points = [], _dataset} = meta;
// @ts-ignore
const animationsDisabled = this.chart._animationsDisabled;
- let {start, count} = getStartAndCountOfVisiblePoints(meta, points, animationsDisabled);
+ let {start, count} = _getStartAndCountOfVisiblePoints(meta, points, animationsDisabled);
this._drawStart = start;
this._drawCount = count;
- if (scaleRangesChanged(meta)) {
+ if (_scaleRangesChanged(meta)) {
start = 0;
count = points.length;
}
},
}
};
-
-function getStartAndCountOfVisiblePoints(meta, points, animationsDisabled) {
- const pointCount = points.length;
-
- let start = 0;
- let count = pointCount;
-
- if (meta._sorted) {
- const {iScale, _parsed} = meta;
- const axis = iScale.axis;
- const {min, max, minDefined, maxDefined} = iScale.getUserBounds();
-
- if (minDefined) {
- start = _limitValue(Math.min(
- _lookupByKey(_parsed, iScale.axis, min).lo,
- animationsDisabled ? pointCount : _lookupByKey(points, axis, iScale.getPixelForValue(min)).lo),
- 0, pointCount - 1);
- }
- if (maxDefined) {
- count = _limitValue(Math.max(
- _lookupByKey(_parsed, iScale.axis, max, true).hi + 1,
- animationsDisabled ? 0 : _lookupByKey(points, axis, iScale.getPixelForValue(max), true).hi + 1),
- start, pointCount) - start;
- } else {
- count = pointCount - start;
- }
- }
-
- return {start, count};
-}
-
-function scaleRangesChanged(meta) {
- const {xScale, yScale, _scaleRanges} = meta;
- const newRanges = {
- xmin: xScale.min,
- xmax: xScale.max,
- ymin: yScale.min,
- ymax: yScale.max
- };
- if (!_scaleRanges) {
- meta._scaleRanges = newRanges;
- return true;
- }
- const changed = _scaleRanges.xmin !== xScale.min
- || _scaleRanges.xmax !== xScale.max
- || _scaleRanges.ymin !== yScale.min
- || _scaleRanges.ymax !== yScale.max;
-
- Object.assign(_scaleRanges, newRanges);
- return changed;
-}
-import LineController from './controller.line';
+import DatasetController from '../core/core.datasetController';
+import {isNullOrUndef} from '../helpers';
+import {isNumber} from '../helpers/helpers.math';
+import {_getStartAndCountOfVisiblePoints, _scaleRangesChanged} from '../helpers/helpers.extras';
+import registry from '../core/core.registry';
-export default class ScatterController extends LineController {
+export default class ScatterController extends DatasetController {
+ update(mode) {
+ const meta = this._cachedMeta;
+ const {data: points = []} = meta;
+ // @ts-ignore
+ 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 (this.options.showLine) {
+
+ const {dataset: line, _dataset} = meta;
+
+ // Update Line
+ line._chart = this.chart;
+ line._datasetIndex = this.index;
+ line._decimated = !!_dataset._decimated;
+ line.points = points;
+
+ const options = this.resolveDatasetElementOptions(mode);
+ options.segment = this.options.segment;
+ this.updateElement(line, undefined, {
+ animated: !animationsDisabled,
+ options
+ }, mode);
+ }
+
+ // Update Points
+ this.updateElements(points, start, count, mode);
+ }
+
+ addElements() {
+ const {showLine} = this.options;
+
+ if (!this.datasetElementType && showLine) {
+ this.datasetElementType = registry.getElement('line');
+ }
+
+ super.addElements();
+ }
+
+ updateElements(points, start, count, mode) {
+ const reset = mode === 'reset';
+ const {iScale, vScale, _stacked, _dataset} = this._cachedMeta;
+ const firstOpts = this.resolveDataElementOptions(start, mode);
+ const sharedOptions = this.getSharedOptions(firstOpts);
+ const includeOptions = this.includeOptions(mode, sharedOptions);
+ const iAxis = iScale.axis;
+ const vAxis = vScale.axis;
+ const {spanGaps, segment} = this.options;
+ const maxGapLength = isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY;
+ const directUpdate = this.chart._animationsDisabled || reset || mode === 'none';
+ let prevParsed = start > 0 && this.getParsed(start - 1);
+
+ for (let i = start; i < start + count; ++i) {
+ const point = points[i];
+ const parsed = this.getParsed(i);
+ const properties = directUpdate ? point : {};
+ const nullData = isNullOrUndef(parsed[vAxis]);
+ const iPixel = properties[iAxis] = iScale.getPixelForValue(parsed[iAxis], i);
+ const vPixel = properties[vAxis] = reset || nullData ? vScale.getBasePixel() : vScale.getPixelForValue(_stacked ? this.applyStack(vScale, parsed, _stacked) : parsed[vAxis], i);
+
+ properties.skip = isNaN(iPixel) || isNaN(vPixel) || nullData;
+ properties.stop = i > 0 && (Math.abs(parsed[iAxis] - prevParsed[iAxis])) > maxGapLength;
+ if (segment) {
+ properties.parsed = parsed;
+ properties.raw = _dataset.data[i];
+ }
+
+ if (includeOptions) {
+ properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);
+ }
+
+ if (!directUpdate) {
+ this.updateElement(point, i, properties, mode);
+ }
+
+ prevParsed = parsed;
+ }
+
+ this.updateSharedOptions(sharedOptions, mode, firstOpts);
+ }
+
+ /**
+ * @protected
+ */
+ getMaxOverflow() {
+ const meta = this._cachedMeta;
+ const data = meta.data || [];
+
+ if (!this.options.showLine) {
+ let max = 0;
+ for (let i = data.length - 1; i >= 0; --i) {
+ max = Math.max(max, data[i].size(this.resolveDataElementOptions(i)) / 2);
+ }
+ return max > 0 && max;
+ }
+
+ const dataset = meta.dataset;
+ const border = dataset.options && dataset.options.borderWidth || 0;
+
+ if (!data.length) {
+ return border;
+ }
+
+ const firstPoint = data[0].size(this.resolveDataElementOptions(0));
+ const lastPoint = data[data.length - 1].size(this.resolveDataElementOptions(data.length - 1));
+ return Math.max(border, firstPoint, lastPoint) / 2;
+ }
}
ScatterController.id = 'scatter';
* @type {any}
*/
ScatterController.defaults = {
+ datasetElementType: false,
+ dataElementType: 'point',
showLine: false,
fill: false
};
+import {_limitValue} from './helpers.math';
+import {_lookupByKey} from './helpers.collection';
export function fontString(pixelSize, fontStyle, fontFamily) {
return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
const check = rtl ? 'left' : 'right';
return align === check ? right : align === 'center' ? (left + right) / 2 : left;
};
+
+/**
+ * Return start and count of visible points.
+ * @param {object} meta - dataset meta.
+ * @param {array} points - array of point elements.
+ * @param {boolean} animationsDisabled - if true animation is disabled.
+ * @returns {{start: number; count: number}}
+ * @private
+ */
+export function _getStartAndCountOfVisiblePoints(meta, points, animationsDisabled) {
+ const pointCount = points.length;
+
+ let start = 0;
+ let count = pointCount;
+
+ if (meta._sorted) {
+ const {iScale, _parsed} = meta;
+ const axis = iScale.axis;
+ const {min, max, minDefined, maxDefined} = iScale.getUserBounds();
+
+ if (minDefined) {
+ start = _limitValue(Math.min(
+ _lookupByKey(_parsed, iScale.axis, min).lo,
+ animationsDisabled ? pointCount : _lookupByKey(points, axis, iScale.getPixelForValue(min)).lo),
+ 0, pointCount - 1);
+ }
+ if (maxDefined) {
+ count = _limitValue(Math.max(
+ _lookupByKey(_parsed, iScale.axis, max, true).hi + 1,
+ animationsDisabled ? 0 : _lookupByKey(points, axis, iScale.getPixelForValue(max), true).hi + 1),
+ start, pointCount) - start;
+ } else {
+ count = pointCount - start;
+ }
+ }
+
+ return {start, count};
+}
+
+/**
+ * Checks if the scale ranges have changed.
+ * @param {object} meta - dataset meta.
+ * @returns {boolean}
+ * @private
+ */
+export function _scaleRangesChanged(meta) {
+ const {xScale, yScale, _scaleRanges} = meta;
+ const newRanges = {
+ xmin: xScale.min,
+ xmax: xScale.max,
+ ymin: yScale.min,
+ ymax: yScale.max
+ };
+ if (!_scaleRanges) {
+ meta._scaleRanges = newRanges;
+ return true;
+ }
+ const changed = _scaleRanges.xmin !== xScale.min
+ || _scaleRanges.xmax !== xScale.max
+ || _scaleRanges.ymin !== yScale.min
+ || _scaleRanges.ymax !== yScale.max;
+
+ Object.assign(_scaleRanges, newRanges);
+ return changed;
+}
await jasmine.triggerMouseEvent(chart, 'mousemove', point);
expect(chart.tooltip.body.length).toEqual(1);
});
+
+ it('should not create line element by default', function() {
+ var chart = window.acquireChart({
+ type: 'scatter',
+ data: {
+ datasets: [{
+ data: [{
+ x: 10,
+ y: 15
+ },
+ {
+ x: 12,
+ y: 10
+ }],
+ label: 'dataset1'
+ },
+ {
+ data: [{
+ x: 20,
+ y: 10
+ },
+ {
+ x: 4,
+ y: 8
+ }],
+ label: 'dataset2'
+ }]
+ },
+ });
+
+ var meta = chart.getDatasetMeta(0);
+ expect(meta.dataset instanceof Chart.elements.LineElement).toBe(false);
+ });
+
+ it('should create line element if showline is true at datasets options', function() {
+ var chart = window.acquireChart({
+ type: 'scatter',
+ data: {
+ datasets: [{
+ showLine: true,
+ data: [{
+ x: 10,
+ y: 15
+ },
+ {
+ x: 12,
+ y: 10
+ }],
+ label: 'dataset1'
+ },
+ {
+ data: [{
+ x: 20,
+ y: 10
+ },
+ {
+ x: 4,
+ y: 8
+ }],
+ label: 'dataset2'
+ }]
+ },
+ });
+
+ var meta = chart.getDatasetMeta(0);
+ expect(meta.dataset instanceof Chart.elements.LineElement).toBe(true);
+ });
+
+ it('should create line element if showline is true at root options', function() {
+ var chart = window.acquireChart({
+ type: 'scatter',
+ data: {
+ datasets: [{
+ data: [{
+ x: 10,
+ y: 15
+ },
+ {
+ x: 12,
+ y: 10
+ }],
+ label: 'dataset1'
+ },
+ {
+ data: [{
+ x: 20,
+ y: 10
+ },
+ {
+ x: 4,
+ y: 8
+ }],
+ label: 'dataset2'
+ }]
+ },
+ options: {
+ showLine: true
+ }
+ });
+
+ var meta = chart.getDatasetMeta(0);
+ expect(meta.dataset instanceof Chart.elements.LineElement).toBe(true);
+ });
});