]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
feat: remove line element from scatter controller (#10439)
authorSlava Terekhov <thabarbados@gmail.com>
Mon, 1 Aug 2022 19:39:09 +0000 (23:39 +0400)
committerGitHub <noreply@github.com>
Mon, 1 Aug 2022 19:39:09 +0000 (22:39 +0300)
* feat: remove line element from scatter controller default config

* feat: move common controllers methods to helpers and add types

* feat: mark methods for scatter and line conntrollers as private

* fix: fix error when showline is true at root options and add tests

* feat: remove else inside scatter controller update

* fix: update getStartAndCountOFVisiblePoints helper code

src/controllers/controller.line.js
src/controllers/controller.scatter.js
src/helpers/helpers.extras.js
test/specs/controller.scatter.tests.js

index b28997aad07e27ec14133f304475fd7d200833ce..3ddd09896e0207a8dea6f629320316b22a2168f7 100644 (file)
@@ -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 {
 
@@ -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;
     }
@@ -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;
-}
index 1d1ccb73e5a95664dd2123319d1fd7620de61edf..ee229120bc0d47967273e7b4cb9ac0da09581d78 100644 (file)
@@ -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;
+
+    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';
@@ -10,6 +128,8 @@ ScatterController.id = 'scatter';
  * @type {any}
  */
 ScatterController.defaults = {
+  datasetElementType: false,
+  dataElementType: 'point',
   showLine: false,
   fill: false
 };
index 2b73328f9ac453eb61fd6b5a751e44b792a7fb5f..8bab58ae1820cbd9a41efb9c4f95368a9b5e40bc 100644 (file)
@@ -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;
@@ -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;
+}
index 3f0b17594d582a61f3156d84182cf6b83e801536..1e89849a40f0c1c4f534ed237b91782aff3e8476 100644 (file)
@@ -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);
+  });
 });