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

feat: remove line element from scatter controller #10439

Merged
merged 6 commits into from Aug 1, 2022
Merged
59 changes: 4 additions & 55 deletions src/controllers/controller.line.js
@@ -1,7 +1,7 @@
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 {

Expand All @@ -16,12 +16,12 @@ 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;
}
Expand Down Expand Up @@ -133,54 +133,3 @@ LineController.overrides = {
},
}
};

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;
}
124 changes: 122 additions & 2 deletions src/controllers/controller.scatter.js
@@ -1,7 +1,125 @@
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;
LeeLenaleee marked this conversation as resolved.
Show resolved Hide resolved

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';
Expand All @@ -10,6 +128,8 @@ ScatterController.id = 'scatter';
* @type {any}
*/
ScatterController.defaults = {
datasetElementType: false,
dataElementType: 'point',
showLine: false,
fill: false
};
Expand Down
67 changes: 67 additions & 0 deletions src/helpers/helpers.extras.js
@@ -1,3 +1,5 @@
import {_limitValue} from './helpers.math';
import {_lookupByKey} from './helpers.collection';

export function fontString(pixelSize, fontStyle, fontFamily) {
return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
Expand Down Expand Up @@ -87,3 +89,68 @@ export const _textX = (align, left, right, rtl) => {
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;
}
103 changes: 103 additions & 0 deletions test/specs/controller.scatter.tests.js
Expand Up @@ -61,4 +61,107 @@ describe('Chart.controllers.scatter', function() {
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);
});
});