Split time scale into time and timeseries (#7525)

* Split time scale into time and timeseries
* Address comment
This commit is contained in:
Ben McCann
2020-06-19 07:21:39 -07:00
committed by GitHub
parent 6cf97859f0
commit c2cd00ca90
17 changed files with 360 additions and 322 deletions

View 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).

View File

@@ -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.

View 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.

View 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;
}

View 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';

View 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';

View File

@@ -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;

View 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

View 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

View 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

View 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

View 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

View 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'

View 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'

View 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'

View File

@@ -9,11 +9,10 @@ module.exports = {
options: {
scales: {
x: {
type: 'time',
type: 'timeseries',
time: {
parser: 'YYYY'
},
distribution: 'series',
reverse: true,
ticks: {
source: 'labels'

View 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
}
}
}
}