New time scale ticks.source: 'data' option (#4568)

This new option value generates ticks from data (including labels from {t|x|y} data objects).
This commit is contained in:
Simon Brunel
2017-07-27 06:41:09 +02:00
committed by GitHub
parent 43baf2fbe0
commit 2cc242d5a3
2 changed files with 121 additions and 30 deletions

View File

@@ -260,12 +260,13 @@ function determineMajorUnit(unit) {
}
/**
* Generates timestamps between min and max, rounded to the `minor` unit, aligned on
* the `major` unit, spaced with `stepSize` and using the given scale time `options`.
* Generates a maximum of `capacity` timestamps between min and max, rounded to the
* `minor` unit, aligned on the `major` unit and 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.
*/
function generate(min, max, minor, major, stepSize, options) {
function generate(min, max, minor, major, capacity, options) {
var stepSize = helpers.valueOrDefault(options.stepSize, options.unitStepSize);
var weekday = minor === 'week' ? options.isoWeekday : false;
var interval = INTERVALS[minor];
var first = moment(min);
@@ -273,6 +274,10 @@ function generate(min, max, minor, major, stepSize, options) {
var ticks = [];
var time;
if (!stepSize) {
stepSize = determineStepSize(min, max, minor, capacity);
}
// For 'week' unit, handle the first day of week option
if (weekday) {
first = first.isoWeekday(weekday);
@@ -348,8 +353,9 @@ module.exports = function(Chart) {
/**
* Ticks generation input values:
* - 'labels': generates ticks from user given `data.labels` values ONLY.
* - 'auto': generates "optimal" ticks based on scale size and time options.
* - 'data': generates ticks from data (including labels from data {t|x|y} objects).
* - 'labels': generates ticks from user given `data.labels` values ONLY.
* @see https://github.com/chartjs/Chart.js/pull/4507
* @since 2.7.0
*/
@@ -472,15 +478,22 @@ module.exports = function(Chart) {
var majorUnit = determineMajorUnit(unit);
var timestamps = [];
var ticks = [];
var i, ilen, timestamp, stepSize;
var hash = {};
var i, ilen, timestamp;
if (ticksOpts.source === 'auto') {
stepSize = helpers.valueOrDefault(timeOpts.stepSize, timeOpts.unitStepSize)
|| determineStepSize(min, max, unit, capacity);
timestamps = generate(min, max, unit, majorUnit, stepSize, timeOpts);
} else {
switch (ticksOpts.source) {
case 'data':
for (i = 0, ilen = me._datasets.length; i < ilen; ++i) {
timestamps.push.apply(timestamps, me._datasets[i]);
}
timestamps.sort(sorter);
break;
case 'labels':
timestamps = me._labels;
break;
case 'auto':
default:
timestamps = generate(min, max, unit, majorUnit, capacity, timeOpts);
}
if (ticksOpts.bounds === 'labels' && timestamps.length) {
@@ -492,10 +505,12 @@ module.exports = function(Chart) {
min = parse(timeOpts.min, me) || min;
max = parse(timeOpts.max, me) || max;
// Remove ticks outside the min/max range
// Remove ticks outside the min/max range and duplicated entries
for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
timestamp = timestamps[i];
if (timestamp >= min && timestamp <= max) {
if (timestamp >= min && timestamp <= max && !hash[timestamp]) {
// hash is used to efficiently detect timestamp duplicates
hash[timestamp] = true;
ticks.push(timestamp);
}
}

View File

@@ -281,8 +281,8 @@ describe('Time scale tests', function() {
round: true,
parser: function(label) {
return label === 'foo' ?
moment(946771200000) : // 02/01/2000 @ 12:00am (UTC)
moment(1462665600000); // 05/08/2016 @ 12:00am (UTC)
moment('2000/01/02', 'YYYY/MM/DD') :
moment('2016/05/08', 'YYYY/MM/DD');
}
},
ticks: {
@@ -694,20 +694,6 @@ describe('Time scale tests', function() {
expect(getTicksValues(scale.ticks)).toEqual([
'2017', '2019', '2020', '2025', '2042']);
});
it ('should remove ticks that are not inside the min and max time range', function() {
var chart = this.chart;
var scale = chart.scales.x;
var options = chart.options.scales.xAxes[0];
options.time.min = '2022';
options.time.max = '2032';
chart.update();
expect(scale.min).toEqual(+moment('2022', 'YYYY'));
expect(scale.max).toEqual(+moment('2032', 'YYYY'));
expect(getTicksValues(scale.ticks)).toEqual([
'2025']);
});
it ('should not duplicate ticks if min and max are the labels limits', function() {
var chart = this.chart;
var scale = chart.scales.x;
@@ -734,6 +720,88 @@ describe('Time scale tests', function() {
expect(getTicksValues(scale.ticks)).toEqual([]);
});
});
describe('is "data"', function() {
beforeEach(function() {
this.chart = window.acquireChart({
type: 'line',
data: {
labels: ['2017', '2019', '2020', '2025', '2042'],
datasets: [
{data: [0, 1, 2, 3, 4, 5]},
{data: [
{t: '2018', y: 6},
{t: '2020', y: 7},
{t: '2043', y: 8}
]}
]
},
options: {
scales: {
xAxes: [{
id: 'x',
type: 'time',
time: {
parser: 'YYYY'
},
ticks: {
source: 'data'
}
}]
}
}
});
});
it ('should generate ticks from "datasets.data"', function() {
var scale = this.chart.scales.x;
expect(scale.min).toEqual(+moment('2017', 'YYYY'));
expect(scale.max).toEqual(+moment('2043', 'YYYY'));
expect(getTicksValues(scale.ticks)).toEqual([
'2017', '2018', '2019', '2020', '2025', '2042', '2043']);
});
it ('should not add ticks for min and max if they extend the labels range', function() {
var chart = this.chart;
var scale = chart.scales.x;
var options = chart.options.scales.xAxes[0];
options.time.min = '2012';
options.time.max = '2051';
chart.update();
expect(scale.min).toEqual(+moment('2012', 'YYYY'));
expect(scale.max).toEqual(+moment('2051', 'YYYY'));
expect(getTicksValues(scale.ticks)).toEqual([
'2017', '2018', '2019', '2020', '2025', '2042', '2043']);
});
it ('should not duplicate ticks if min and max are the labels limits', function() {
var chart = this.chart;
var scale = chart.scales.x;
var options = chart.options.scales.xAxes[0];
options.time.min = '2017';
options.time.max = '2043';
chart.update();
expect(scale.min).toEqual(+moment('2017', 'YYYY'));
expect(scale.max).toEqual(+moment('2043', 'YYYY'));
expect(getTicksValues(scale.ticks)).toEqual([
'2017', '2018', '2019', '2020', '2025', '2042', '2043']);
});
it ('should correctly handle empty `data.labels`', function() {
var chart = this.chart;
var scale = chart.scales.x;
chart.data.labels = [];
chart.update();
expect(scale.min).toEqual(+moment('2018', 'YYYY'));
expect(scale.max).toEqual(+moment('2043', 'YYYY'));
expect(getTicksValues(scale.ticks)).toEqual([
'2018', '2020', '2043']);
});
});
});
describe('when ticks.mode', function() {
@@ -970,7 +1038,7 @@ describe('Time scale tests', function() {
});
describe('when time.min and/or time.max are defined', function() {
['auto', 'labels'].forEach(function(source) {
['auto', 'data', 'labels'].forEach(function(source) {
['data', 'labels'].forEach(function(bounds) {
describe('and source is "' + source + '" and bounds "' + bounds + '"', function() {
beforeEach(function() {
@@ -1017,6 +1085,10 @@ describe('Time scale tests', function() {
expect(scale.max).toEqual(+moment(max, 'MM/DD HH:mm'));
expect(scale.getPixelForValue(min)).toBeCloseToPixel(scale.left);
expect(scale.getPixelForValue(max)).toBeCloseToPixel(scale.left + scale.width);
scale.ticks.forEach(function(tick) {
expect(tick.time >= +moment(min, 'MM/DD HH:mm')).toBeTruthy();
expect(tick.time <= +moment(max, 'MM/DD HH:mm')).toBeTruthy();
});
});
it ('should shrink scale to the min/max range', function() {
var chart = this.chart;
@@ -1033,6 +1105,10 @@ describe('Time scale tests', function() {
expect(scale.max).toEqual(+moment(max, 'MM/DD HH:mm'));
expect(scale.getPixelForValue(min)).toBeCloseToPixel(scale.left);
expect(scale.getPixelForValue(max)).toBeCloseToPixel(scale.left + scale.width);
scale.ticks.forEach(function(tick) {
expect(tick.time >= +moment(min, 'MM/DD HH:mm')).toBeTruthy();
expect(tick.time <= +moment(max, 'MM/DD HH:mm')).toBeTruthy();
});
});
});
});