Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from
Jul 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 28 additions & 13 deletions src/scales/scale.time.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,19 +260,24 @@ 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);
var last = moment(max);
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);
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -472,15 +478,22 @@ module.exports = function(Chart) {
var majorUnit = determineMajorUnit(unit);
var timestamps = [];
var ticks = [];
var i, ilen, timestamp, stepSize;

if (ticksOpts.source === 'auto') {
stepSize = helpers.valueOrDefault(timeOpts.stepSize, timeOpts.unitStepSize)
|| determineStepSize(min, max, unit, capacity);
var hash = {};
var i, ilen, timestamp;

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) {
Expand All @@ -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);
}
}
Expand Down
106 changes: 91 additions & 15 deletions test/specs/scale.time.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -694,33 +694,100 @@ 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() {
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 = '2022';
options.time.max = '2032';
options.time.min = '2017';
options.time.max = '2042';
chart.update();

expect(scale.min).toEqual(+moment('2022', 'YYYY'));
expect(scale.max).toEqual(+moment('2032', 'YYYY'));
expect(scale.min).toEqual(+moment('2017', 'YYYY'));
expect(scale.max).toEqual(+moment('2042', 'YYYY'));
expect(getTicksValues(scale.ticks)).toEqual([
'2025']);
'2017', '2019', '2020', '2025', '2042']);
});
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().startOf('day'));
expect(scale.max).toEqual(+moment().endOf('day') + 1);
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 = '2042';
options.time.max = '2043';
chart.update();

expect(scale.min).toEqual(+moment('2017', 'YYYY'));
expect(scale.max).toEqual(+moment('2042', 'YYYY'));
expect(scale.max).toEqual(+moment('2043', 'YYYY'));
expect(getTicksValues(scale.ticks)).toEqual([
'2017', '2019', '2020', '2025', '2042']);
'2017', '2018', '2019', '2020', '2025', '2042', '2043']);
});
it ('should correctly handle empty `data.labels`', function() {
var chart = this.chart;
Expand All @@ -729,9 +796,10 @@ describe('Time scale tests', function() {
chart.data.labels = [];
chart.update();

expect(scale.min).toEqual(+moment().startOf('day'));
expect(scale.max).toEqual(+moment().endOf('day') + 1);
expect(getTicksValues(scale.ticks)).toEqual([]);
expect(scale.min).toEqual(+moment('2018', 'YYYY'));
expect(scale.max).toEqual(+moment('2043', 'YYYY'));
expect(getTicksValues(scale.ticks)).toEqual([
'2018', '2020', '2043']);
});
});
});
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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;
Expand All @@ -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();
});
});
});
});
Expand Down