]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Timeseries: support panning (#9345)
authorJukka Kurkela <jukka.kurkela@gmail.com>
Sat, 3 Jul 2021 15:44:44 +0000 (18:44 +0300)
committerGitHub <noreply@github.com>
Sat, 3 Jul 2021 15:44:44 +0000 (18:44 +0300)
* Timeseries: support panning

* Update

* Missing semi, reduntant variables.

* cc

src/scales/scale.timeseries.js
test/fixtures/scale.timeseries/source-data-offset-min-max.png
test/fixtures/scale.timeseries/source-labels-offset-min-max.png
test/fixtures/scale.timeseries/ticks-reverse-max.png
test/fixtures/scale.timeseries/ticks-reverse-min-max.png
test/fixtures/scale.timeseries/ticks-reverse-min.png
test/specs/scale.time.tests.js

index f8228fe3431010d660b1b621f14dbaac1814c689..e42d87817a553a19e13488ff4e155b9ef59b5519 100644 (file)
@@ -1,30 +1,30 @@
 import TimeScale from './scale.time';
-import {_lookup} from '../helpers/helpers.collection';
-import {isNullOrUndef} from '../helpers/helpers.core';
+import {_lookupByKey} from '../helpers/helpers.collection';
 
 /**
  * Linearly interpolates the given source `val` using the table. If value is out of bounds, values
- * at index [0, 1] or [n - 1, n] are used for the interpolation.
+ * at edges are used for the interpolation.
  * @param {object} table
  * @param {number} val
  * @param {boolean} [reverse] lookup time based on position instead of vice versa
  * @return {object}
  */
 function interpolate(table, val, reverse) {
+  let lo = 0;
+  let hi = table.length - 1;
   let prevSource, nextSource, prevTarget, nextTarget;
-
-  // Note: the lookup table ALWAYS contains at least 2 items (min and max)
   if (reverse) {
-    prevSource = Math.floor(val);
-    nextSource = Math.ceil(val);
-    prevTarget = table[prevSource];
-    nextTarget = table[nextSource];
+    if (val >= table[lo].pos && val <= table[hi].pos) {
+      ({lo, hi} = _lookupByKey(table, 'pos', val));
+    }
+    ({pos: prevSource, time: prevTarget} = table[lo]);
+    ({pos: nextSource, time: nextTarget} = table[hi]);
   } else {
-    const result = _lookup(table, val);
-    prevTarget = result.lo;
-    nextTarget = result.hi;
-    prevSource = table[prevTarget];
-    nextSource = table[nextTarget];
+    if (val >= table[lo].time && val <= table[hi].time) {
+      ({lo, hi} = _lookupByKey(table, 'time', val));
+    }
+    ({time: prevSource, pos: prevTarget} = table[lo]);
+    ({time: nextSource, pos: nextTarget} = table[hi]);
   }
 
   const span = nextSource - prevSource;
@@ -42,7 +42,9 @@ class TimeSeriesScale extends TimeScale {
     /** @type {object[]} */
     this._table = [];
     /** @type {number} */
-    this._maxIndex = undefined;
+    this._minPos = undefined;
+    /** @type {number} */
+    this._tableRange = undefined;
   }
 
   /**
@@ -51,8 +53,9 @@ class TimeSeriesScale extends TimeScale {
   initOffsets() {
     const me = this;
     const timestamps = me._getTimestampsForTable();
-    me._table = me.buildLookupTable(timestamps);
-    me._maxIndex = me._table.length - 1;
+    const table = me._table = me.buildLookupTable(timestamps);
+    me._minPos = interpolate(table, me.min);
+    me._tableRange = interpolate(table, me.max) - me._minPos;
     super.initOffsets(timestamps);
   }
 
@@ -68,28 +71,37 @@ class TimeSeriesScale extends TimeScale {
         * @protected
         */
   buildLookupTable(timestamps) {
-    const me = this;
-    const {min, max} = me;
-    if (!timestamps.length) {
+    const {min, max} = this;
+    const items = [];
+    const table = [];
+    let i, ilen, prev, curr, next;
+
+    for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
+      curr = timestamps[i];
+      if (curr >= min && curr <= max) {
+        items.push(curr);
+      }
+    }
+
+    if (items.length < 2) {
+      // In case there is less that 2 timestamps between min and max, the scale is defined by min and max
       return [
         {time: min, pos: 0},
         {time: max, pos: 1}
       ];
     }
 
-    const items = [min];
-    let i, ilen, curr;
+    for (i = 0, ilen = items.length; i < ilen; ++i) {
+      next = items[i + 1];
+      prev = items[i - 1];
+      curr = items[i];
 
-    for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
-      curr = timestamps[i];
-      if (curr > min && curr < max) {
-        items.push(curr);
+      // only add points that breaks the scale linearity
+      if (Math.round((next + prev) / 2) !== curr) {
+        table.push({time: curr, pos: i / (ilen - 1)});
       }
     }
-
-    items.push(max);
-
-    return items;
+    return table;
   }
 
   /**
@@ -119,25 +131,12 @@ class TimeSeriesScale extends TimeScale {
     return timestamps;
   }
 
-  /**
-        * @param {number} value - Milliseconds since epoch (1 January 1970 00:00:00 UTC)
-        * @param {number} [index]
-        * @return {number}
-        */
-  getPixelForValue(value, index) {
-    const me = this;
-    const offsets = me._offsets;
-    const pos = me._normalized && me._maxIndex > 0 && !isNullOrUndef(index)
-      ? index / me._maxIndex : me.getDecimalForValue(value);
-    return me.getPixelForDecimal((offsets.start + pos) * offsets.factor);
-  }
-
   /**
         * @param {number} value - Milliseconds since epoch (1 January 1970 00:00:00 UTC)
         * @return {number}
         */
   getDecimalForValue(value) {
-    return interpolate(this._table, value) / this._maxIndex;
+    return (interpolate(this._table, value) - this._minPos) / this._tableRange;
   }
 
   /**
@@ -148,7 +147,7 @@ class TimeSeriesScale extends TimeScale {
     const me = this;
     const offsets = me._offsets;
     const decimal = me.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
-    return interpolate(me._table, decimal * this._maxIndex, true);
+    return interpolate(me._table, decimal * me._tableRange + me._minPos, true);
   }
 }
 
index a23747193291c127016d3cb2b59999323303dc40..824bf36bd513d356edcfd090be9f304213d95d18 100644 (file)
Binary files a/test/fixtures/scale.timeseries/source-data-offset-min-max.png and b/test/fixtures/scale.timeseries/source-data-offset-min-max.png differ
index a23747193291c127016d3cb2b59999323303dc40..824bf36bd513d356edcfd090be9f304213d95d18 100644 (file)
Binary files a/test/fixtures/scale.timeseries/source-labels-offset-min-max.png and b/test/fixtures/scale.timeseries/source-labels-offset-min-max.png differ
index 1189549dbf5e3868c42f491ebc07a017951aa6fe..9887ed88db3f9b004b1fb166938dd29dd9d90899 100644 (file)
Binary files a/test/fixtures/scale.timeseries/ticks-reverse-max.png and b/test/fixtures/scale.timeseries/ticks-reverse-max.png differ
index f6c66ec676f45746c39570454a0ad64edd077fee..dc7e79882f8e7f5bc90484dfc570c84bcdeab33d 100644 (file)
Binary files a/test/fixtures/scale.timeseries/ticks-reverse-min-max.png and b/test/fixtures/scale.timeseries/ticks-reverse-min-max.png differ
index 97315b374c5ed98b57c9ffd40d8b9d67bbd809ed..5a30d3f026058d1c57030627db845debe45ac4b7 100644 (file)
Binary files a/test/fixtures/scale.timeseries/ticks-reverse-min.png and b/test/fixtures/scale.timeseries/ticks-reverse-min.png differ
index e9fccdf8faf584c4f673f4f00ff29cb37f6b2501..4113d8a497e59a895556eae99ba2966269f7f875 100644 (file)
@@ -764,7 +764,7 @@ describe('Time scale tests', function() {
           var start = scale.left;
           var slice = scale.width / 5;
 
-          expect(scale.getPixelForValue(moment('2017').valueOf(), 1)).toBeCloseToPixel(start + slice);
+          expect(scale.getPixelForValue(moment('2017').valueOf(), 1)).toBeCloseToPixel(86);
           expect(scale.getPixelForValue(moment('2042').valueOf(), 5)).toBeCloseToPixel(start + slice * 5);
         });
         it ('should add a step after if scale.max is after the last data', function() {
@@ -776,10 +776,9 @@ describe('Time scale tests', function() {
           chart.update();
 
           var start = scale.left;
-          var slice = scale.width / 5;
 
           expect(scale.getPixelForValue(moment('2017').valueOf(), 0)).toBeCloseToPixel(start);
-          expect(scale.getPixelForValue(moment('2042').valueOf(), 4)).toBeCloseToPixel(start + slice * 4);
+          expect(scale.getPixelForValue(moment('2042').valueOf(), 4)).toBeCloseToPixel(388);
         });
         it ('should add steps before and after if scale.min/max are outside the data range', function() {
           var chart = this.chart;
@@ -790,11 +789,8 @@ describe('Time scale tests', function() {
           options.max = '2050';
           chart.update();
 
-          var start = scale.left;
-          var slice = scale.width / 6;
-
-          expect(scale.getPixelForValue(moment('2017').valueOf(), 1)).toBeCloseToPixel(start + slice);
-          expect(scale.getPixelForValue(moment('2042').valueOf(), 5)).toBeCloseToPixel(start + slice * 5);
+          expect(scale.getPixelForValue(moment('2017').valueOf(), 1)).toBeCloseToPixel(71);
+          expect(scale.getPixelForValue(moment('2042').valueOf(), 5)).toBeCloseToPixel(401);
         });
       });
       describe('is "time"', function() {