]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Split time scale into time and timeseries (#7525)
authorBen McCann <322311+benmccann@users.noreply.github.com>
Fri, 19 Jun 2020 14:21:39 +0000 (07:21 -0700)
committerGitHub <noreply@github.com>
Fri, 19 Jun 2020 14:21:39 +0000 (10:21 -0400)
* Split time scale into time and timeseries
* Address comment

17 files changed:
docs/docs/axes/cartesian/time.md
docs/docs/axes/cartesian/timeseries.md [new file with mode: 0644]
docs/docs/getting-started/v3-migration.md
src/helpers/helpers.collection.js
src/scales/index.js
src/scales/scale.time.js
src/scales/scale.timeseries.js [new file with mode: 0644]
test/fixtures/scale.time/source-auto-series.js
test/fixtures/scale.time/source-data-series-offset-min-max.js
test/fixtures/scale.time/source-data-series.js
test/fixtures/scale.time/source-labels-series-offset-min-max.js
test/fixtures/scale.time/source-labels-series.js
test/fixtures/scale.time/ticks-reverse-series-max.js
test/fixtures/scale.time/ticks-reverse-series-min-max.js
test/fixtures/scale.time/ticks-reverse-series-min.js
test/fixtures/scale.time/ticks-reverse-series.js
test/specs/scale.time.tests.js

index 61b1b2cf28ca23ef41a40ce01149336a5f95b301..7254725b102fd3f31f1641a56431e9174a992746 100644 (file)
@@ -2,7 +2,7 @@
 title: Time Cartesian Axis
 ---
 
-The time scale is used to display times and dates. When building its ticks, it will automatically calculate the most comfortable unit base on the size of the scale.
+The time scale is used to display times and dates. Data are spread according to the amount of time between data points. When building its ticks, it will automatically calculate the most comfortable unit base on the size of the scale.
 
 ## Date Adapters
 
@@ -25,7 +25,6 @@ The following options are provided by the time scale. You may also set options p
 | Name | Type | Default | Description
 | ---- | ---- | ------- | -----------
 | `adapters.date` | `object` | `{}` | Options for adapter for external date library if that adapter needs or supports options
-| `distribution` | `string` | `'linear'` | How data is plotted. [more...](#scale-distribution)
 | `bounds` | `string` | `'data'` | Determines the scale bounds. [more...](#scale-bounds)
 | `ticks.source` | `string` | `'auto'` | How ticks are generated. [more...](#ticks-source)
 | `time.displayFormats` | `object` | | Sets how different time units are displayed. [more...](#display-formats)
@@ -107,30 +106,6 @@ var chart = new Chart(ctx, {
 });
 ```
 
-### Scale Distribution
-
-The `distribution` property controls the data distribution along the scale:
-
-* `'linear'`: data are spread according to their time (distances can vary)
-* `'series'`: data are spread at the same distance from each other
-
-```javascript
-var chart = new Chart(ctx, {
-    type: 'line',
-    data: data,
-    options: {
-        scales: {
-            x: {
-                type: 'time',
-                distribution: 'series'
-            }
-        }
-    }
-});
-```
-
-When the scale is in `series` mode, the data indices are expected to be unique, sorted, and consistent across datasets.
-
 ### Scale Bounds
 
 The `bounds` property controls the scale boundary strategy (bypassed by `min`/`max` time options).
diff --git a/docs/docs/axes/cartesian/timeseries.md b/docs/docs/axes/cartesian/timeseries.md
new file mode 100644 (file)
index 0000000..986c4db
--- /dev/null
@@ -0,0 +1,25 @@
+---
+title: Time Series Axis
+---
+
+The time series scale extends from the time scale and supports all the same options. However, for the time series scale, each data point is spread equidistant. Also, the data indices are expected to be unique, sorted, and consistent across datasets.
+
+## Example
+
+```javascript
+var chart = new Chart(ctx, {
+    type: 'line',
+    data: data,
+    options: {
+        scales: {
+            x: {
+                type: 'timeseries',
+            }
+        }
+    }
+});
+```
+
+## More details
+
+Please see [the time scale documentation](./time.md) for all other details.
index c93c0fb01b1c98a8f82fea3a27dc20add1d56715..3f0d3f5f5b5eac9081610fefcd1ffe5f405e131f 100644 (file)
@@ -156,6 +156,8 @@ options: {
 }
 ```
 
+Also, the time scale option `distribution: 'series'` was removed and a new scale type `timeseries` was introduced in its place.
+
 #### Animations
 
 Animation system was completely rewritten in Chart.js v3. Each property can now be animated separately. Please see [animations](../configuration/animations.mdx) docs for details.
index 95ee06b628e922577ec73b7d37b89069ea6bf5e9..2671810c2049ee9aa84abb78da17f568aea0753a 100644 (file)
@@ -162,3 +162,25 @@ export function unlistenArrayEvents(array, listener) {
 
        delete array._chartjs;
 }
+
+/**
+ * @param {Array} items
+ */
+export function _arrayUnique(items) {
+       const set = new Set();
+       let i, ilen;
+
+       for (i = 0, ilen = items.length; i < ilen; ++i) {
+               set.add(items[i]);
+       }
+
+       if (set.size === ilen) {
+               return items;
+       }
+
+       const result = [];
+       set.forEach(item => {
+               result.push(item);
+       });
+       return result;
+}
index a3f8c34cf58bf75de47b872701f6ca6b178d963c..bc81fda4562706fb5541a8e2ed0c8ad90703c71c 100644 (file)
@@ -3,3 +3,4 @@ export {default as LinearScale} from './scale.linear';
 export {default as LogarithmicScale} from './scale.logarithmic';
 export {default as RadialLinearScale} from './scale.radialLinear';
 export {default as TimeScale} from './scale.time';
+export {default as TimeSeriesScale} from './scale.timeseries';
index 322a7c706cad8b25d2aeeda694fba730064548bc..2f82656de5d66a5cad105b9eea1566f31e94b361 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 {_filterBetween, _lookup, _lookupByKey} from '../helpers/helpers.collection';
+import {_arrayUnique, _filterBetween, _lookup, _lookupByKey} from '../helpers/helpers.collection';
 
 /**
  * @typedef { import("../core/core.adapters").Unit } Unit
@@ -40,31 +40,10 @@ function sorter(a, b) {
        return a - b;
 }
 
-/**
- * @param {number[]} items
- */
-function arrayUnique(items) {
-       const set = new Set();
-       let i, ilen;
-
-       for (i = 0, ilen = items.length; i < ilen; ++i) {
-               set.add(items[i]);
-       }
-
-       if (set.size === ilen) {
-               return items;
-       }
-
-       const result = [];
-       set.forEach(item => {
-               result.push(item);
-       });
-       return result;
-}
-
 /**
  * @param {TimeScale} scale
  * @param {*} input
+ * @return {number}
  */
 function parse(scale, input) {
        if (isNullOrUndef(input)) {
@@ -100,133 +79,6 @@ function parse(scale, input) {
        return +value;
 }
 
-/**
- * @param {TimeScale} scale
- */
-function getDataTimestamps(scale) {
-       const isSeries = scale.options.distribution === 'series';
-       let timestamps = scale._cache.data || [];
-       let i, ilen;
-
-       if (timestamps.length) {
-               return timestamps;
-       }
-
-       const metas = scale.getMatchingVisibleMetas();
-
-       if (isSeries && metas.length) {
-               return metas[0].controller.getAllParsedValues(scale);
-       }
-
-       for (i = 0, ilen = metas.length; i < ilen; ++i) {
-               timestamps = timestamps.concat(metas[i].controller.getAllParsedValues(scale));
-       }
-
-       // We can not assume data is in order or unique - not even for single dataset
-       // It seems to be somewhat faster to do sorting first
-       return (scale._cache.data = arrayUnique(timestamps.sort(sorter)));
-}
-
-/**
- * @param {TimeScale} scale
- */
-function getLabelTimestamps(scale) {
-       const isSeries = scale.options.distribution === 'series';
-       const timestamps = scale._cache.labels || [];
-       let i, ilen;
-
-       if (timestamps.length) {
-               return timestamps;
-       }
-
-       const labels = scale.getLabels();
-       for (i = 0, ilen = labels.length; i < ilen; ++i) {
-               timestamps.push(parse(scale, labels[i]));
-       }
-
-       // We could assume labels are in order and unique - but let's not
-       return (scale._cache.labels = isSeries ? timestamps : arrayUnique(timestamps.sort(sorter)));
-}
-
-/**
- * @param {TimeScale} scale
- */
-function getAllTimestamps(scale) {
-       let timestamps = scale._cache.all || [];
-
-       if (timestamps.length) {
-               return timestamps;
-       }
-
-       const data = getDataTimestamps(scale);
-       const label = getLabelTimestamps(scale);
-       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 = scale._cache.all = timestamps;
-
-       return timestamps;
-}
-
-/**
- * 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 {string} distribution - If 'linear', timestamps will be spread linearly along the min
- * and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}.
- * If 'series', timestamps will be positioned at the same distance from each other. In this
- * case, only timestamps that break the time linearity are registered, meaning that in the
- * best case, all timestamps are linear, the table contains only min and max.
- * @param {number[]} timestamps
- * @param {number} min
- * @param {number} max
- * @param {string} distribution
- * @return {object[]}
- */
-function buildLookupTable(timestamps, min, max, distribution) {
-       if (distribution === 'linear' || !timestamps.length) {
-               return [
-                       {time: min, pos: 0},
-                       {time: max, pos: 1}
-               ];
-       }
-
-       const table = [];
-       const items = [min];
-       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);
-               }
-       }
-
-       items.push(max);
-
-       for (i = 0, ilen = items.length; i < ilen; ++i) {
-               next = items[i + 1];
-               prev = items[i - 1];
-               curr = items[i];
-
-               // only add points that breaks the scale linearity
-               if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) {
-                       table.push({time: curr, pos: i / (ilen - 1)});
-               }
-       }
-
-       return table;
-}
-
 /**
  * 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')
@@ -321,64 +173,6 @@ function addTick(timestamps, ticks, time) {
        ticks[timestamp] = true;
 }
 
-/**
- * Generates a maximum of `capacity` timestamps between min and max, rounded to the
- * `minor` unit using the given scale time `options`.
- * Important: this method can return ticks outside the min and max range, it's the
- * responsibility of the calling code to clamp values if needed.
- * @param {TimeScale} scale
- */
-function generate(scale) {
-       const adapter = scale._adapter;
-       const min = scale.min;
-       const max = scale.max;
-       const options = scale.options;
-       const timeOpts = options.time;
-       // @ts-ignore
-       const minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, scale._getLabelCapacity(min));
-       const stepSize = valueOrDefault(timeOpts.stepSize, 1);
-       const weekday = minor === 'week' ? timeOpts.isoWeekday : false;
-       const ticks = {};
-       let first = min;
-       let time;
-
-       // For 'week' unit, handle the first day of week option
-       if (weekday) {
-               first = +adapter.startOf(first, 'isoWeek', weekday);
-       }
-
-       // Align first ticks on unit
-       first = +adapter.startOf(first, weekday ? 'day' : minor);
-
-       // Prevent browser from freezing in case user options request millions of milliseconds
-       if (adapter.diff(max, min, minor) > 100000 * stepSize) {
-               throw new Error(min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor);
-       }
-
-       if (scale.options.ticks.source === 'data') {
-               // need to make sure ticks are in data in this case
-               const timestamps = getDataTimestamps(scale);
-
-               for (time = first; time < max; time = +adapter.add(time, stepSize, minor)) {
-                       addTick(timestamps, ticks, time);
-               }
-
-               if (time === max || options.bounds === 'ticks') {
-                       addTick(timestamps, ticks, time);
-               }
-       } else {
-               for (time = first; time < max; time = +adapter.add(time, stepSize, minor)) {
-                       ticks[time] = true;
-               }
-
-               if (time === max || options.bounds === 'ticks') {
-                       ticks[time] = true;
-               }
-       }
-
-       return Object.keys(ticks).map(x => +x);
-}
-
 /**
  * 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.
@@ -464,50 +258,7 @@ function ticksFromTimestamps(scale, values, majorUnit) {
        return (ilen === 0 || !majorUnit) ? ticks : setMajorTicks(scale, ticks, map, majorUnit);
 }
 
-/**
- * @param {TimeScale} scale
- */
-function getTimestampsForTicks(scale) {
-       if (scale.options.ticks.source === 'labels') {
-               return getLabelTimestamps(scale);
-       }
-
-       return generate(scale);
-}
-
-/**
- * @param {TimeScale} scale
- */
-function getTimestampsForTable(scale) {
-       return scale.options.distribution === 'series'
-               ? getAllTimestamps(scale)
-               : [scale.min, scale.max];
-}
-
-/**
- * @param {TimeScale} scale
- */
-function getLabelBounds(scale) {
-       const arr = getLabelTimestamps(scale);
-       let min = Number.POSITIVE_INFINITY;
-       let max = Number.NEGATIVE_INFINITY;
-
-       if (arr.length) {
-               min = arr[0];
-               max = arr[arr.length - 1];
-       }
-       return {min, max};
-}
-
 const defaultConfig = {
-       /**
-        * Data distribution along the scale:
-        * - 'linear': data are spread according to their time (distances can vary),
-        * - 'series': data are spread at the same distance from each other.
-        * @see https://github.com/chartjs/Chart.js/pull/4507
-        * @since 2.7.0
-        */
-       distribution: 'linear',
 
        /**
         * Scale boundary strategy (bypassed by min/max time options)
@@ -586,7 +337,7 @@ class TimeScale extends Scale {
 
        /**
         * @param {*} raw
-        * @param {number} index
+        * @param {number?} [index]
         * @return {number}
         */
        parse(raw, index) { // eslint-disable-line no-unused-vars
@@ -604,6 +355,13 @@ class TimeScale extends Scale {
                };
        }
 
+       /**
+        * @protected
+        */
+       getTimestampsForTable() {
+               return [this.min, this.max];
+       }
+
        determineDataLimits() {
                const me = this;
                const options = me.options;
@@ -627,7 +385,7 @@ class TimeScale extends Scale {
                // If we have user provided `min` and `max` labels / data bounds can be ignored
                if (!minDefined || !maxDefined) {
                        // Labels are always considered, when user did not force bounds
-                       _applyBounds(getLabelBounds(me));
+                       _applyBounds(me._getLabelBounds());
 
                        // If `bounds` is `'ticks'` and `ticks.source` is `'labels'`,
                        // data bounds are ignored (and don't need to be determined)
@@ -644,6 +402,21 @@ class TimeScale extends Scale {
                me.max = Math.max(min + 1, max);
        }
 
+       /**
+        * @private
+        */
+       _getLabelBounds() {
+               const arr = this.getLabelTimestamps();
+               let min = Number.POSITIVE_INFINITY;
+               let max = Number.NEGATIVE_INFINITY;
+
+               if (arr.length) {
+                       min = arr[0];
+                       max = arr[arr.length - 1];
+               }
+               return {min, max};
+       }
+
        /**
         * @return {object[]}
         */
@@ -652,8 +425,7 @@ class TimeScale extends Scale {
                const options = me.options;
                const timeOpts = options.time;
                const tickOpts = options.ticks;
-               const distribution = options.distribution;
-               const timestamps = getTimestampsForTicks(me);
+               const timestamps = tickOpts.source === 'labels' ? me.getLabelTimestamps() : me._generate();
 
                if (options.bounds === 'ticks' && timestamps.length) {
                        me.min = me._userMin || timestamps[0];
@@ -673,7 +445,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 = buildLookupTable(getTimestampsForTable(me), min, max, distribution);
+               me._table = me.buildLookupTable(me.getTimestampsForTable(), min, max);
                me._offsets = computeOffsets(me._table, timestamps, min, max, options);
 
                if (options.reverse) {
@@ -683,6 +455,86 @@ class TimeScale extends Scale {
                return ticksFromTimestamps(me, ticks, me._majorUnit);
        }
 
+       /**
+        * Generates a maximum of `capacity` timestamps between min and max, rounded to the
+        * `minor` unit using the given scale time `options`.
+        * Important: this method can return ticks outside the min and max range, it's the
+        * responsibility of the calling code to clamp values if needed.
+        * @private
+        */
+       _generate() {
+               const me = this;
+               const adapter = me._adapter;
+               const min = me.min;
+               const max = me.max;
+               const options = me.options;
+               const timeOpts = options.time;
+               // @ts-ignore
+               const minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, me._getLabelCapacity(min));
+               const stepSize = valueOrDefault(timeOpts.stepSize, 1);
+               const weekday = minor === 'week' ? timeOpts.isoWeekday : false;
+               const ticks = {};
+               let first = min;
+               let time;
+
+               // For 'week' unit, handle the first day of week option
+               if (weekday) {
+                       first = +adapter.startOf(first, 'isoWeek', weekday);
+               }
+
+               // Align first ticks on unit
+               first = +adapter.startOf(first, weekday ? 'day' : minor);
+
+               // Prevent browser from freezing in case user options request millions of milliseconds
+               if (adapter.diff(max, min, minor) > 100000 * stepSize) {
+                       throw new Error(min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor);
+               }
+
+               if (me.options.ticks.source === 'data') {
+                       // need to make sure ticks are in data in this case
+                       const timestamps = me.getDataTimestamps();
+
+                       for (time = first; time < max; time = +adapter.add(time, stepSize, minor)) {
+                               addTick(timestamps, ticks, time);
+                       }
+
+                       if (time === max || options.bounds === 'ticks') {
+                               addTick(timestamps, ticks, time);
+                       }
+               } else {
+                       for (time = first; time < max; time = +adapter.add(time, stepSize, minor)) {
+                               ticks[time] = true;
+                       }
+
+                       if (time === max || options.bounds === 'ticks') {
+                               ticks[time] = true;
+                       }
+               }
+
+               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}
@@ -795,6 +647,50 @@ class TimeScale extends Scale {
                const capacity = Math.floor(me.isHorizontal() ? me.width / size.w : me.height / size.h) - 1;
                return capacity > 0 ? capacity : 1;
        }
+
+       /**
+        * @protected
+        */
+       getDataTimestamps() {
+               const me = this;
+               let timestamps = me._cache.data || [];
+               let i, ilen;
+
+               if (timestamps.length) {
+                       return timestamps;
+               }
+
+               const metas = me.getMatchingVisibleMetas();
+
+               for (i = 0, ilen = metas.length; i < ilen; ++i) {
+                       timestamps = timestamps.concat(metas[i].controller.getAllParsedValues(me));
+               }
+
+               // We can not assume data is in order or unique - not even for single dataset
+               // It seems to be somewhat faster to do sorting first
+               return (me._cache.data = _arrayUnique(timestamps.sort(sorter)));
+       }
+
+       /**
+        * @protected
+        */
+       getLabelTimestamps() {
+               const me = this;
+               const timestamps = me._cache.labels || [];
+               let i, ilen;
+
+               if (timestamps.length) {
+                       return timestamps;
+               }
+
+               const labels = me.getLabels();
+               for (i = 0, ilen = labels.length; i < ilen; ++i) {
+                       timestamps.push(parse(me, labels[i]));
+               }
+
+               // We could assume labels are in order and unique - but let's not
+               return (me._cache.labels = _arrayUnique(timestamps.sort(sorter)));
+       }
 }
 
 TimeScale.id = 'time';
diff --git a/src/scales/scale.timeseries.js b/src/scales/scale.timeseries.js
new file mode 100644 (file)
index 0000000..009d8df
--- /dev/null
@@ -0,0 +1,131 @@
+import TimeScale from './scale.time';
+import {_arrayUnique} from '../helpers/helpers.collection';
+
+/**
+ * @param {number} a
+ * @param {number} b
+ */
+function sorter(a, b) {
+       return a - b;
+}
+
+class TimeSeriesScale extends TimeScale {
+
+       /**
+        * Returns all timestamps
+        * @protected
+        */
+       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;
+       }
+
+       /**
+        * 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) {
+               if (!timestamps.length) {
+                       return [
+                               {time: min, pos: 0},
+                               {time: max, pos: 1}
+                       ];
+               }
+
+               const table = [];
+               const items = [min];
+               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);
+                       }
+               }
+
+               items.push(max);
+
+               for (i = 0, ilen = items.length; i < ilen; ++i) {
+                       next = items[i + 1];
+                       prev = items[i - 1];
+                       curr = items[i];
+
+                       // only add points that breaks the scale linearity
+                       if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) {
+                               table.push({time: curr, pos: i / (ilen - 1)});
+                       }
+               }
+
+               return table;
+       }
+
+       /**
+        * @protected
+        */
+       getDataTimestamps() {
+               const me = this;
+               const timestamps = me._cache.data || [];
+
+               if (timestamps.length) {
+                       return timestamps;
+               }
+
+               const metas = me.getMatchingVisibleMetas();
+               return (me._cache.data = metas.length ? metas[0].controller.getAllParsedValues(me) : []);
+       }
+
+       /**
+        * @protected
+        */
+       getLabelTimestamps() {
+               const me = this;
+               const timestamps = me._cache.labels || [];
+               let i, ilen;
+
+               if (timestamps.length) {
+                       return timestamps;
+               }
+
+               const labels = me.getLabels();
+               for (i = 0, ilen = labels.length; i < ilen; ++i) {
+                       timestamps.push(me.parse(labels[i]));
+               }
+
+               // We could assume labels are in order and unique - but let's not
+               return (me._cache.labels = timestamps);
+       }
+}
+
+TimeSeriesScale.id = 'timeseries';
+
+// INTERNAL: default options, registered in src/index.js
+TimeSeriesScale.defaults = TimeScale.defaults;
+
+export default TimeSeriesScale;
index 7962581238429504b21684750e4a5d8e776d5b48..205a9cfca2d4775a5c41e95c19de0e83b53f4da2 100644 (file)
@@ -9,15 +9,14 @@ module.exports = {
                options: {
                        scales: {
                                x: {
-                                       type: 'time',
+                                       type: 'timeseries',
                                        time: {
                                                parser: 'YYYY',
                                                unit: 'year'
                                        },
                                        ticks: {
                                                source: 'auto'
-                                       },
-                                       distribution: 'series'
+                                       }
                                },
                                y: {
                                        display: false
index e651864b3042c1edcf43d34db8e77e1dc67b67e9..7a08941f800db33e27f87bb48e23ec4563243675 100644 (file)
@@ -9,7 +9,7 @@ module.exports = {
                options: {
                        scales: {
                                x: {
-                                       type: 'time',
+                                       type: 'timeseries',
                                        min: '2012',
                                        max: '2051',
                                        offset: true,
@@ -18,8 +18,7 @@ module.exports = {
                                        },
                                        ticks: {
                                                source: 'data'
-                                       },
-                                       distribution: 'series'
+                                       }
                                },
                                y: {
                                        display: false
index 15f74a19729af70a09daa9b089609c22a929d258..a9b6f28da8cfdbdc62861797df8de903d4755b44 100644 (file)
@@ -9,15 +9,14 @@ module.exports = {
                options: {
                        scales: {
                                x: {
-                                       type: 'time',
+                                       type: 'timeseries',
                                        time: {
                                                parser: 'YYYY',
                                                unit: 'year'
                                        },
                                        ticks: {
                                                source: 'data'
-                                       },
-                                       distribution: 'series'
+                                       }
                                },
                                y: {
                                        display: false
index 612fd5378364cbe43f2b7f4c82653d8bd46a17e0..17c8784fbcf55ded447c26380ddb4a9a1c5802b8 100644 (file)
@@ -9,7 +9,7 @@ module.exports = {
                options: {
                        scales: {
                                x: {
-                                       type: 'time',
+                                       type: 'timeseries',
                                        min: '2012',
                                        max: '2051',
                                        offset: true,
@@ -18,8 +18,7 @@ module.exports = {
                                        },
                                        ticks: {
                                                source: 'labels'
-                                       },
-                                       distribution: 'series'
+                                       }
                                },
                                y: {
                                        display: false
index f5b22e244cff47ebe8e9e09a7a0096b3f1504177..55e5486ed2d9cf3a0cf77d559de48eb067740a70 100644 (file)
@@ -9,15 +9,14 @@ module.exports = {
                options: {
                        scales: {
                                x: {
-                                       type: 'time',
+                                       type: 'timeseries',
                                        time: {
                                                parser: 'YYYY',
                                                unit: 'year'
                                        },
                                        ticks: {
                                                source: 'labels'
-                                       },
-                                       distribution: 'series'
+                                       }
                                },
                                y: {
                                        display: false
index 72840e07d9af5d074f7af3855f07ef69182e63af..7b893a78449e2e486a33ebd329a97f9a02585d44 100644 (file)
@@ -9,12 +9,11 @@ module.exports = {
                options: {
                        scales: {
                                x: {
-                                       type: 'time',
+                                       type: 'timeseries',
                                        max: '2050',
                                        time: {
                                                parser: 'YYYY'
                                        },
-                                       distribution: 'series',
                                        reverse: true,
                                        ticks: {
                                                source: 'labels'
index 4e67f7278da8ad7f83a36079ca54634cbd891e18..e3216c668834a2d6e0017e65f47b8d9c5bf41978 100644 (file)
@@ -9,13 +9,12 @@ module.exports = {
                options: {
                        scales: {
                                x: {
-                                       type: 'time',
+                                       type: 'timeseries',
                                        min: '2012',
                                        max: '2050',
                                        time: {
                                                parser: 'YYYY'
                                        },
-                                       distribution: 'series',
                                        reverse: true,
                                        ticks: {
                                                source: 'labels'
index 89ddb832ef3a8fca1b4a56a1f561deb3c955444d..fbd62b1615054cfa776352ff9afd72f971864f43 100644 (file)
@@ -9,12 +9,11 @@ module.exports = {
                options: {
                        scales: {
                                x: {
-                                       type: 'time',
+                                       type: 'timeseries',
                                        min: '2012',
                                        time: {
                                                parser: 'YYYY'
                                        },
-                                       distribution: 'series',
                                        reverse: true,
                                        ticks: {
                                                source: 'labels'
index ba2dd3e21c14e482709e83c10ae698572c5d586d..ce9628e8044968ea0b62513910699ed7bacb8dc9 100644 (file)
@@ -9,11 +9,10 @@ module.exports = {
                options: {
                        scales: {
                                x: {
-                                       type: 'time',
+                                       type: 'timeseries',
                                        time: {
                                                parser: 'YYYY'
                                        },
-                                       distribution: 'series',
                                        reverse: true,
                                        ticks: {
                                                source: 'labels'
index fe172fd392366f96c15d6887f31eb16e3fbbe739..4d15aa38f687e85d3f21ba6731071ce79ba47822 100644 (file)
@@ -79,7 +79,6 @@ describe('Time scale tests', function() {
                        beginAtZero: false,
                        scaleLabel: Chart.defaults.scale.scaleLabel,
                        bounds: 'data',
-                       distribution: 'linear',
                        adapters: {},
                        ticks: {
                                minRotation: 0,
@@ -786,8 +785,8 @@ describe('Time scale tests', function() {
                });
        });
 
-       describe('when distribution', function() {
-               describe('is "series"', function() {
+       describe('when scale type', function() {
+               describe('is "timeseries"', function() {
                        beforeEach(function() {
                                this.chart = window.acquireChart({
                                        type: 'line',
@@ -798,11 +797,10 @@ describe('Time scale tests', function() {
                                        options: {
                                                scales: {
                                                        x: {
-                                                               type: 'time',
+                                                               type: 'timeseries',
                                                                time: {
                                                                        parser: 'YYYY'
                                                                },
-                                                               distribution: 'series',
                                                                ticks: {
                                                                        source: 'labels'
                                                                }
@@ -870,7 +868,7 @@ describe('Time scale tests', function() {
                                expect(scale.getPixelForValue(moment('2042').valueOf())).toBeCloseToPixel(start + slice * 5);
                        });
                });
-               describe('is "linear"', function() {
+               describe('is "time"', function() {
                        beforeEach(function() {
                                this.chart = window.acquireChart({
                                        type: 'line',
@@ -885,7 +883,6 @@ describe('Time scale tests', function() {
                                                                time: {
                                                                        parser: 'YYYY'
                                                                },
-                                                               distribution: 'linear',
                                                                ticks: {
                                                                        source: 'labels'
                                                                }
@@ -1087,8 +1084,8 @@ describe('Time scale tests', function() {
        });
 
        ['auto', 'data', 'labels'].forEach(function(source) {
-               ['series', 'linear'].forEach(function(distribution) {
-                       describe('when ticks.source is "' + source + '" and distribution is "' + distribution + '"', function() {
+               ['timeseries', 'time'].forEach(function(type) {
+                       describe('when ticks.source is "' + source + '" and scale type is "' + type + '"', function() {
                                beforeEach(function() {
                                        this.chart = window.acquireChart({
                                                type: 'line',
@@ -1099,15 +1096,14 @@ describe('Time scale tests', function() {
                                                options: {
                                                        scales: {
                                                                x: {
-                                                                       type: 'time',
+                                                                       type: type,
                                                                        time: {
                                                                                parser: 'YYYY',
                                                                                unit: 'year'
                                                                        },
                                                                        ticks: {
                                                                                source: source
-                                                                       },
-                                                                       distribution: distribution
+                                                                       }
                                                                }
                                                        }
                                                }
@@ -1154,8 +1150,8 @@ describe('Time scale tests', function() {
        });
 
        ['data', 'labels'].forEach(function(source) {
-               ['series', 'linear'].forEach(function(distribution) {
-                       describe('when ticks.source is "' + source + '" and distribution is "' + distribution + '"', function() {
+               ['timeseries', 'time'].forEach(function(type) {
+                       describe('when ticks.source is "' + source + '" and scale type is "' + type + '"', function() {
                                beforeEach(function() {
                                        this.chart = window.acquireChart({
                                                type: 'line',
@@ -1167,14 +1163,13 @@ describe('Time scale tests', function() {
                                                        scales: {
                                                                x: {
                                                                        id: 'x',
-                                                                       type: 'time',
+                                                                       type: type,
                                                                        time: {
                                                                                parser: 'YYYY'
                                                                        },
                                                                        ticks: {
                                                                                source: source
-                                                                       },
-                                                                       distribution: distribution
+                                                                       }
                                                                }
                                                        }
                                                }