]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Remove lookup table from TimeScale (#7532)
authorBen McCann <322311+benmccann@users.noreply.github.com>
Sat, 20 Jun 2020 20:39:39 +0000 (13:39 -0700)
committerGitHub <noreply@github.com>
Sat, 20 Jun 2020 20:39:39 +0000 (16:39 -0400)
* Add getDecimalForValue
* Move interpolate to timeseries scale
* Remove getTimestampsForTable from time scale
* Remove parameters from buildLookupTable
* Restore getValueForPixel
* Remove table from time scale

src/scales/scale.time.js
src/scales/scale.timeseries.js

index 2f82656de5d66a5cad105b9eea1566f31e94b361..e5c63913f7c492a5525d630fbbb5c8b0bb0779a1 100644 (file)
@@ -2,7 +2,7 @@ import adapters from '../core/core.adapters';
 import {isFinite, isNullOrUndef, mergeIf, valueOrDefault} from '../helpers/helpers.core';
 import {toRadians} from '../helpers/helpers.math';
 import Scale from '../core/core.scale';
-import {_arrayUnique, _filterBetween, _lookup, _lookupByKey} from '../helpers/helpers.collection';
+import {_arrayUnique, _filterBetween, _lookup} from '../helpers/helpers.collection';
 
 /**
  * @typedef { import("../core/core.adapters").Unit } Unit
@@ -79,31 +79,6 @@ function parse(scale, input) {
        return +value;
 }
 
-/**
- * Linearly interpolates the given source `value` using the table items `skey` values and
- * returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos')
- * returns the position for a timestamp equal to 42. If value is out of bounds, values at
- * index [0, 1] or [n - 1, n] are used for the interpolation.
- * @param {object} table
- * @param {string} skey
- * @param {number} sval
- * @param {string} tkey
- * @return {object}
- */
-function interpolate(table, skey, sval, tkey) {
-       const {lo, hi} = _lookupByKey(table, skey, sval);
-
-       // Note: the lookup table ALWAYS contains at least 2 items (min and max)
-       const prev = table[lo];
-       const next = table[hi];
-
-       const span = next[skey] - prev[skey];
-       const ratio = span ? (sval - prev[skey]) / span : 0;
-       const offset = (next[tkey] - prev[tkey]) * ratio;
-
-       return prev[tkey] + offset;
-}
-
 /**
  * Figures out what unit results in an appropriate number of auto-generated ticks
  * @param {Unit} minUnit
@@ -173,41 +148,6 @@ function addTick(timestamps, ticks, time) {
        ticks[timestamp] = true;
 }
 
-/**
- * Returns the start and end offsets from edges in the form of {start, end}
- * where each value is a relative width to the scale and ranges between 0 and 1.
- * They add extra margins on the both sides by scaling down the original scale.
- * Offsets are added when the `offset` option is true.
- * @param {object} table
- * @param {number[]} timestamps
- * @param {number} min
- * @param {number} max
- * @param {object} options
- * @return {object}
- */
-function computeOffsets(table, timestamps, min, max, options) {
-       let start = 0;
-       let end = 0;
-       let first, last;
-
-       if (options.offset && timestamps.length) {
-               first = interpolate(table, 'time', timestamps[0], 'pos');
-               if (timestamps.length === 1) {
-                       start = 1 - first;
-               } else {
-                       start = (interpolate(table, 'time', timestamps[1], 'pos') - first) / 2;
-               }
-               last = interpolate(table, 'time', timestamps[timestamps.length - 1], 'pos');
-               if (timestamps.length === 1) {
-                       end = last;
-               } else {
-                       end = (last - interpolate(table, 'time', timestamps[timestamps.length - 2], 'pos')) / 2;
-               }
-       }
-
-       return {start, end, factor: 1 / (start + 1 + end)};
-}
-
 /**
  * @param {TimeScale} scale
  * @param {object[]} ticks
@@ -318,8 +258,6 @@ class TimeScale extends Scale {
                this._majorUnit = undefined;
                /** @type {object} */
                this._offsets = {};
-               /** @type {object[]} */
-               this._table = [];
        }
 
        init(options) {
@@ -355,13 +293,6 @@ class TimeScale extends Scale {
                };
        }
 
-       /**
-        * @protected
-        */
-       getTimestampsForTable() {
-               return [this.min, this.max];
-       }
-
        determineDataLimits() {
                const me = this;
                const options = me.options;
@@ -397,7 +328,7 @@ class TimeScale extends Scale {
                min = isFinite(min) && !isNaN(min) ? min : +adapter.startOf(Date.now(), unit);
                max = isFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit) + 1;
 
-               // Make sure that max is strictly higher than min (required by the lookup table)
+               // Make sure that max is strictly higher than min (required by the timeseries lookup table)
                me.min = Math.min(min, max);
                me.max = Math.max(min + 1, max);
        }
@@ -445,8 +376,7 @@ class TimeScale extends Scale {
                        : determineUnitForFormatting(me, ticks.length, timeOpts.minUnit, me.min, me.max));
                me._majorUnit = !tickOpts.major.enabled || me._unit === 'year' ? undefined
                        : determineMajorUnit(me._unit);
-               me._table = me.buildLookupTable(me.getTimestampsForTable(), min, max);
-               me._offsets = computeOffsets(me._table, timestamps, min, max, options);
+               me.initOffsets(timestamps);
 
                if (options.reverse) {
                        ticks.reverse();
@@ -455,6 +385,39 @@ class TimeScale extends Scale {
                return ticksFromTimestamps(me, ticks, me._majorUnit);
        }
 
+       /**
+        * Returns the start and end offsets from edges in the form of {start, end}
+        * where each value is a relative width to the scale and ranges between 0 and 1.
+        * They add extra margins on the both sides by scaling down the original scale.
+        * Offsets are added when the `offset` option is true.
+        * @param {number[]} timestamps
+        * @return {object}
+        * @protected
+        */
+       initOffsets(timestamps) {
+               const me = this;
+               let start = 0;
+               let end = 0;
+               let first, last;
+
+               if (me.options.offset && timestamps.length) {
+                       first = me.getDecimalForValue(timestamps[0]);
+                       if (timestamps.length === 1) {
+                               start = 1 - first;
+                       } else {
+                               start = (me.getDecimalForValue(timestamps[1]) - first) / 2;
+                       }
+                       last = me.getDecimalForValue(timestamps[timestamps.length - 1]);
+                       if (timestamps.length === 1) {
+                               end = last;
+                       } else {
+                               end = (last - me.getDecimalForValue(timestamps[timestamps.length - 2])) / 2;
+                       }
+               }
+
+               me._offsets = {start, end, factor: 1 / (start + 1 + end)};
+       }
+
        /**
         * Generates a maximum of `capacity` timestamps between min and max, rounded to the
         * `minor` unit using the given scale time `options`.
@@ -514,27 +477,6 @@ class TimeScale extends Scale {
                return Object.keys(ticks).map(x => +x);
        }
 
-       /**
-        * Returns an array of {time, pos} objects used to interpolate a specific `time` or position
-        * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is
-        * a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other
-        * extremity (left + width or top + height). Note that it would be more optimized to directly
-        * store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need
-        * to create the lookup table. The table ALWAYS contains at least two items: min and max.
-        *
-        * @param {number[]} timestamps - timestamps sorted from lowest to highest.
-        * @param {number} min
-        * @param {number} max
-        * @return {object[]}
-        * @protected
-        */
-       buildLookupTable(timestamps, min, max) {
-               return [
-                       {time: min, pos: 0},
-                       {time: max, pos: 1}
-               ];
-       }
-
        /**
         * @param {number} value
         * @return {string}
@@ -586,6 +528,15 @@ class TimeScale extends Scale {
                }
        }
 
+       /**
+        * @param {number} value - Milliseconds since epoch (1 January 1970 00:00:00 UTC)
+        * @return {number}
+        */
+       getDecimalForValue(value) {
+               const me = this;
+               return (value - me.min) / (me.max - me.min);
+       }
+
        /**
         * @param {number} value - Milliseconds since epoch (1 January 1970 00:00:00 UTC)
         * @return {number}
@@ -593,7 +544,7 @@ class TimeScale extends Scale {
        getPixelForValue(value) {
                const me = this;
                const offsets = me._offsets;
-               const pos = interpolate(me._table, 'time', value, 'pos');
+               const pos = me.getDecimalForValue(value);
                return me.getPixelForDecimal((offsets.start + pos) * offsets.factor);
        }
 
@@ -605,7 +556,7 @@ class TimeScale extends Scale {
                const me = this;
                const offsets = me._offsets;
                const pos = me.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
-               return interpolate(me._table, 'pos', pos, 'time');
+               return me.min + pos * (me.max - me.min);
        }
 
        /**
index 009d8dfb167a13bcefa3a8a7e0ef6d55d36ea344..053e8d099945cf424ce6be3216fb9630c1cb2a4b 100644 (file)
@@ -1,5 +1,30 @@
 import TimeScale from './scale.time';
-import {_arrayUnique} from '../helpers/helpers.collection';
+import {_arrayUnique, _lookupByKey} from '../helpers/helpers.collection';
+
+/**
+ * Linearly interpolates the given source `value` using the table items `skey` values and
+ * returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos')
+ * returns the position for a timestamp equal to 42. If value is out of bounds, values at
+ * index [0, 1] or [n - 1, n] are used for the interpolation.
+ * @param {object} table
+ * @param {string} skey
+ * @param {number} sval
+ * @param {string} tkey
+ * @return {object}
+ */
+function interpolate(table, skey, sval, tkey) {
+       const {lo, hi} = _lookupByKey(table, skey, sval);
+
+       // Note: the lookup table ALWAYS contains at least 2 items (min and max)
+       const prev = table[lo];
+       const next = table[hi];
+
+       const span = next[skey] - prev[skey];
+       const ratio = span ? (sval - prev[skey]) / span : 0;
+       const offset = (next[tkey] - prev[tkey]) * ratio;
+
+       return prev[tkey] + offset;
+}
 
 /**
  * @param {number} a
@@ -12,29 +37,19 @@ function sorter(a, b) {
 class TimeSeriesScale extends TimeScale {
 
        /**
-        * Returns all timestamps
-        * @protected
+        * @param {object} props
         */
-       getTimestampsForTable() {
-               const me = this;
-               let timestamps = me._cache.all || [];
-
-               if (timestamps.length) {
-                       return timestamps;
-               }
+       constructor(props) {
+               super(props);
 
-               const data = me.getDataTimestamps();
-               const label = me.getLabelTimestamps();
-               if (data.length && label.length) {
-                       // If combining labels and data (data might not contain all labels),
-                       // we need to recheck uniqueness and sort
-                       timestamps = _arrayUnique(data.concat(label).sort(sorter));
-               } else {
-                       timestamps = data.length ? data : label;
-               }
-               timestamps = me._cache.all = timestamps;
+               /** @type {object[]} */
+               this._table = [];
+       }
 
-               return timestamps;
+       initOffsets(timestamps) {
+               const me = this;
+               me._table = me.buildLookupTable();
+               super.initOffsets(timestamps);
        }
 
        /**
@@ -45,13 +60,13 @@ class TimeSeriesScale extends TimeScale {
         * store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need
         * to create the lookup table. The table ALWAYS contains at least two items: min and max.
         *
-        * @param {number[]} timestamps - timestamps sorted from lowest to highest.
-        * @param {number} min
-        * @param {number} max
         * @return {object[]}
         * @protected
         */
-       buildLookupTable(timestamps, min, max) {
+       buildLookupTable() {
+               const me = this;
+               const {min, max} = me;
+               const timestamps = me._getTimestampsForTable();
                if (!timestamps.length) {
                        return [
                                {time: min, pos: 0},
@@ -86,6 +101,40 @@ class TimeSeriesScale extends TimeScale {
                return table;
        }
 
+       /**
+        * Returns all timestamps
+        * @private
+        */
+       _getTimestampsForTable() {
+               const me = this;
+               let timestamps = me._cache.all || [];
+
+               if (timestamps.length) {
+                       return timestamps;
+               }
+
+               const data = me.getDataTimestamps();
+               const label = me.getLabelTimestamps();
+               if (data.length && label.length) {
+                       // If combining labels and data (data might not contain all labels),
+                       // we need to recheck uniqueness and sort
+                       timestamps = _arrayUnique(data.concat(label).sort(sorter));
+               } else {
+                       timestamps = data.length ? data : label;
+               }
+               timestamps = me._cache.all = timestamps;
+
+               return timestamps;
+       }
+
+       /**
+        * @param {number} value - Milliseconds since epoch (1 January 1970 00:00:00 UTC)
+        * @return {number}
+        */
+       getDecimalForValue(value) {
+               return interpolate(this._table, 'time', value, 'pos');
+       }
+
        /**
         * @protected
         */
@@ -121,6 +170,17 @@ class TimeSeriesScale extends TimeScale {
                // We could assume labels are in order and unique - but let's not
                return (me._cache.labels = timestamps);
        }
+
+       /**
+        * @param {number} pixel
+        * @return {number}
+        */
+       getValueForPixel(pixel) {
+               const me = this;
+               const offsets = me._offsets;
+               const pos = me.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
+               return interpolate(me._table, 'pos', pos, 'time');
+       }
 }
 
 TimeSeriesScale.id = 'timeseries';