Make autoskip aware of major ticks (#6509)

* Make autoskip aware of major ticks
* Address review comments
* Fix codeclimate warning
* Add test for major and minor tick autoskipping
* Revert change for determining _majorUnit and fix sample
This commit is contained in:
Ben McCann 2019-10-19 13:13:04 -07:00 committed by Evert Timberg
parent 6bc6630b87
commit 69a5082bd6
4 changed files with 271 additions and 129 deletions

View File

@ -76,7 +76,7 @@
var now = moment(); var now = moment();
var data = []; var data = [];
var lessThanDay = unitLessThanDay(); var lessThanDay = unitLessThanDay();
for (; data.length < 60 && date.isBefore(now); date = date.clone().add(1, unit).startOf(unit)) { for (; data.length < 600 && date.isBefore(now); date = date.clone().add(1, unit).startOf(unit)) {
if (outsideMarketHours(date)) { if (outsideMarketHours(date)) {
if (!lessThanDay || !beforeNineThirty(date)) { if (!lessThanDay || !beforeNineThirty(date)) {
date = date.clone().add(date.isoWeekday() >= 5 ? 8 - date.isoWeekday() : 1, 'day'); date = date.clone().add(date.isoWeekday() >= 5 ? 8 - date.isoWeekday() : 1, 'day');
@ -112,13 +112,49 @@
}] }]
}, },
options: { options: {
animation: {
duration: 0
},
scales: { scales: {
xAxes: [{ xAxes: [{
type: 'time', type: 'time',
distribution: 'series', distribution: 'series',
ticks: { ticks: {
major: {
enabled: true,
fontStyle: 'bold'
},
source: 'data', source: 'data',
autoSkip: true autoSkip: true,
autoSkipPadding: 75,
maxRotation: 0,
sampleSize: 100
},
afterBuildTicks: function(scale, ticks) {
var majorUnit = scale._majorUnit;
var firstTick = ticks[0];
var i, ilen, val, tick, currMajor, lastMajor;
val = moment(ticks[0].value);
if ((majorUnit === 'minute' && val.second() === 0)
|| (majorUnit === 'hour' && val.minute() === 0)
|| (majorUnit === 'day' && val.hour() === 9)
|| (majorUnit === 'month' && val.date() <= 3 && val.isoWeekday() === 1)
|| (majorUnit === 'year' && val.month() === 0)) {
firstTick.major = true;
} else {
firstTick.major = false;
}
lastMajor = val.get(majorUnit);
for (i = 1, ilen = ticks.length; i < ilen; i++) {
tick = ticks[i];
val = moment(tick.value);
currMajor = val.get(majorUnit);
tick.major = currMajor !== lastMajor;
lastMajor = currMajor;
}
return ticks;
} }
}], }],
yAxes: [{ yAxes: [{

View File

@ -214,6 +214,109 @@ function parseTickFontOptions(options) {
return {minor: minor, major: major}; return {minor: minor, major: major};
} }
function nonSkipped(ticksToFilter) {
var filtered = [];
var item, index, len;
for (index = 0, len = ticksToFilter.length; index < len; ++index) {
item = ticksToFilter[index];
if (typeof item._index !== 'undefined') {
filtered.push(item);
}
}
return filtered;
}
function getEvenSpacing(arr) {
var len = arr.length;
var i, diff;
if (len < 2) {
return false;
}
for (diff = arr[0], i = 1; i < len; ++i) {
if (arr[i] - arr[i - 1] !== diff) {
return false;
}
}
return diff;
}
function calculateSpacing(majorIndices, ticks, axisLength, ticksLimit) {
var evenMajorSpacing = getEvenSpacing(majorIndices);
var spacing = (ticks.length - 1) / ticksLimit;
var factors, factor, i, ilen;
// If the major ticks are evenly spaced apart, place the minor ticks
// so that they divide the major ticks into even chunks
if (!evenMajorSpacing) {
return Math.max(spacing, 1);
}
factors = helpers.math._factorize(evenMajorSpacing);
for (i = 0, ilen = factors.length - 1; i < ilen; i++) {
factor = factors[i];
if (factor > spacing) {
return factor;
}
}
return Math.max(spacing, 1);
}
function getMajorIndices(ticks) {
var result = [];
var i, ilen;
for (i = 0, ilen = ticks.length; i < ilen; i++) {
if (ticks[i].major) {
result.push(i);
}
}
return result;
}
function skipMajors(ticks, majorIndices, spacing) {
var count = 0;
var next = majorIndices[0];
var i, tick;
spacing = Math.ceil(spacing);
for (i = 0; i < ticks.length; i++) {
tick = ticks[i];
if (i === next) {
tick._index = i;
count++;
next = majorIndices[count * spacing];
} else {
delete tick.label;
}
}
}
function skip(ticks, spacing, majorStart, majorEnd) {
var start = valueOrDefault(majorStart, 0);
var end = Math.min(valueOrDefault(majorEnd, ticks.length), ticks.length);
var count = 0;
var length, i, tick, next;
spacing = Math.ceil(spacing);
if (majorEnd) {
length = majorEnd - majorStart;
spacing = length / Math.floor(length / spacing);
}
next = start;
for (i = Math.max(start, 0); i < end; i++) {
tick = ticks[i];
if (i === next) {
tick._index = i;
count++;
next = Math.round(start + count * spacing);
} else {
delete tick.label;
}
}
}
var Scale = Element.extend({ var Scale = Element.extend({
zeroLineIndex: 0, zeroLineIndex: 0,
@ -364,7 +467,7 @@ var Scale = Element.extend({
me.afterFit(); me.afterFit();
// Auto-skip // Auto-skip
me._ticksToDraw = tickOpts.display && tickOpts.autoSkip ? me._autoSkip(ticks) : ticks; me._ticksToDraw = tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto') ? me._autoSkip(ticks) : ticks;
if (samplingEnabled) { if (samplingEnabled) {
// Generate labels using all non-skipped ticks // Generate labels using all non-skipped ticks
@ -848,40 +951,34 @@ var Scale = Element.extend({
*/ */
_autoSkip: function(ticks) { _autoSkip: function(ticks) {
var me = this; var me = this;
var optionTicks = me.options.ticks; var tickOpts = me.options.ticks;
var tickCount = ticks.length;
var skipRatio = false;
var maxTicks = optionTicks.maxTicksLimit;
// Total space needed to display all ticks. First and last ticks are
// drawn as their center at end of axis, so tickCount-1
var ticksLength = me._tickSize() * (tickCount - 1);
var axisLength = me._length; var axisLength = me._length;
var result = []; var ticksLimit = tickOpts.maxTicksLimit || axisLength / me._tickSize() + 1;
var i, tick; var majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : [];
var numMajorIndices = majorIndices.length;
var first = majorIndices[0];
var last = majorIndices[numMajorIndices - 1];
var i, ilen, spacing, avgMajorSpacing;
if (ticksLength > axisLength) { // If there are too many major ticks to display them all
skipRatio = 1 + Math.floor(ticksLength / axisLength); if (numMajorIndices > ticksLimit) {
skipMajors(ticks, majorIndices, numMajorIndices / ticksLimit);
return nonSkipped(ticks);
} }
// if they defined a max number of optionTicks, spacing = calculateSpacing(majorIndices, ticks, axisLength, ticksLimit);
// increase skipRatio until that number is met
if (tickCount > maxTicks) {
skipRatio = Math.max(skipRatio, 1 + Math.floor(tickCount / maxTicks));
}
for (i = 0; i < tickCount; i++) { if (numMajorIndices > 0) {
tick = ticks[i]; for (i = 0, ilen = numMajorIndices - 1; i < ilen; i++) {
skip(ticks, spacing, majorIndices[i], majorIndices[i + 1]);
if (skipRatio <= 1 || i % skipRatio === 0) {
tick._index = i;
result.push(tick);
} else {
delete tick.label;
} }
avgMajorSpacing = numMajorIndices > 1 ? (last - first) / (numMajorIndices - 1) : null;
skip(ticks, spacing, helpers.isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first);
skip(ticks, spacing, last, helpers.isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing);
return nonSkipped(ticks);
} }
return result; skip(ticks, spacing);
return nonSkipped(ticks);
}, },
/** /**
@ -955,7 +1052,7 @@ var Scale = Element.extend({
var alignBorderValue = function(pixel) { var alignBorderValue = function(pixel) {
return alignPixel(chart, pixel, axisWidth); return alignPixel(chart, pixel, axisWidth);
}; };
var borderValue, i, tick, label, lineValue, alignedLineValue; var borderValue, i, tick, lineValue, alignedLineValue;
var tx1, ty1, tx2, ty2, x1, y1, x2, y2, lineWidth, lineColor, borderDash, borderDashOffset; var tx1, ty1, tx2, ty2, x1, y1, x2, y2, lineWidth, lineColor, borderDash, borderDashOffset;
if (position === 'top') { if (position === 'top') {
@ -986,10 +1083,9 @@ var Scale = Element.extend({
for (i = 0; i < ticksLength; ++i) { for (i = 0; i < ticksLength; ++i) {
tick = ticks[i] || {}; tick = ticks[i] || {};
label = tick.label;
// autoskipper skipped this tick (#4635) // autoskipper skipped this tick (#4635)
if (isNullOrUndef(label) && i < ticks.length) { if (isNullOrUndef(tick.label) && i < ticks.length) {
continue; continue;
} }

View File

@ -5,8 +5,8 @@ var defaults = require('../core/core.defaults');
var helpers = require('../helpers/index'); var helpers = require('../helpers/index');
var Scale = require('../core/core.scale'); var Scale = require('../core/core.scale');
var resolve = helpers.options.resolve;
var valueOrDefault = helpers.valueOrDefault; var valueOrDefault = helpers.valueOrDefault;
var factorize = helpers.math._factorize;
// Integer constants are from the ES6 spec. // Integer constants are from the ES6 spec.
var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991; var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991;
@ -16,42 +16,42 @@ var INTERVALS = {
millisecond: { millisecond: {
common: true, common: true,
size: 1, size: 1,
steps: factorize(1000) steps: 1000
}, },
second: { second: {
common: true, common: true,
size: 1000, size: 1000,
steps: factorize(60) steps: 60
}, },
minute: { minute: {
common: true, common: true,
size: 60000, size: 60000,
steps: factorize(60) steps: 60
}, },
hour: { hour: {
common: true, common: true,
size: 3600000, size: 3600000,
steps: factorize(24) steps: 24
}, },
day: { day: {
common: true, common: true,
size: 86400000, size: 86400000,
steps: factorize(10) steps: 30
}, },
week: { week: {
common: false, common: false,
size: 604800000, size: 604800000,
steps: factorize(4) steps: 4
}, },
month: { month: {
common: true, common: true,
size: 2.628e9, size: 2.628e9,
steps: factorize(12) steps: 12
}, },
quarter: { quarter: {
common: false, common: false,
size: 7.884e9, size: 7.884e9,
steps: factorize(4) steps: 4
}, },
year: { year: {
common: true, common: true,
@ -248,31 +248,6 @@ function parse(scale, input) {
return value; return value;
} }
/**
* Returns the number of unit to skip to be able to display up to `capacity` number of ticks
* in `unit` for the given `min` / `max` range and respecting the interval steps constraints.
*/
function determineStepSize(min, max, unit, capacity) {
var range = max - min;
var interval = INTERVALS[unit];
var milliseconds = interval.size;
var steps = interval.steps;
var i, ilen, factor;
if (!steps) {
return Math.ceil(range / (capacity * milliseconds));
}
for (i = 0, ilen = steps.length; i < ilen; ++i) {
factor = steps[i];
if (Math.ceil(range / (milliseconds * factor)) <= capacity) {
break;
}
}
return factor;
}
/** /**
* Figures out what unit results in an appropriate number of auto-generated ticks * Figures out what unit results in an appropriate number of auto-generated ticks
*/ */
@ -282,7 +257,7 @@ function determineUnitForAutoTicks(minUnit, min, max, capacity) {
for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) { for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) {
interval = INTERVALS[UNITS[i]]; interval = INTERVALS[UNITS[i]];
factor = interval.steps ? interval.steps[interval.steps.length - 1] : MAX_INTEGER; factor = interval.steps ? interval.steps / 2 : MAX_INTEGER;
if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) { if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
return UNITS[i]; return UNITS[i];
@ -296,10 +271,9 @@ function determineUnitForAutoTicks(minUnit, min, max, capacity) {
* Figures out what unit to format a set of ticks with * Figures out what unit to format a set of ticks with
*/ */
function determineUnitForFormatting(scale, ticks, minUnit, min, max) { function determineUnitForFormatting(scale, ticks, minUnit, min, max) {
var ilen = UNITS.length;
var i, unit; var i, unit;
for (i = ilen - 1; i >= UNITS.indexOf(minUnit); i--) { for (i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--) {
unit = UNITS[i]; unit = UNITS[i];
if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= ticks.length - 1) { if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= ticks.length - 1) {
return unit; return unit;
@ -319,7 +293,7 @@ function determineMajorUnit(unit) {
/** /**
* Generates a maximum of `capacity` timestamps between min and max, rounded to the * 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`. * `minor` unit using the given scale time `options`.
* Important: this method can return ticks outside the min and max range, it's the * 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. * responsibility of the calling code to clamp values if needed.
*/ */
@ -328,51 +302,33 @@ function generate(scale, min, max, capacity) {
var options = scale.options; var options = scale.options;
var timeOpts = options.time; var timeOpts = options.time;
var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity); var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity);
var major = determineMajorUnit(minor); var stepSize = resolve([timeOpts.stepSize, timeOpts.unitStepSize, 1]);
var stepSize = valueOrDefault(timeOpts.stepSize, timeOpts.unitStepSize);
var weekday = minor === 'week' ? timeOpts.isoWeekday : false; var weekday = minor === 'week' ? timeOpts.isoWeekday : false;
var majorTicksEnabled = options.ticks.major.enabled;
var interval = INTERVALS[minor];
var first = min; var first = min;
var last = max;
var ticks = []; var ticks = [];
var time; var time;
if (!stepSize) {
stepSize = determineStepSize(min, max, minor, capacity);
}
// For 'week' unit, handle the first day of week option // For 'week' unit, handle the first day of week option
if (weekday) { if (weekday) {
first = +adapter.startOf(first, 'isoWeek', weekday); first = +adapter.startOf(first, 'isoWeek', weekday);
last = +adapter.startOf(last, 'isoWeek', weekday);
} }
// Align first/last ticks on unit // Align first ticks on unit
first = +adapter.startOf(first, weekday ? 'day' : minor); first = +adapter.startOf(first, weekday ? 'day' : minor);
last = +adapter.startOf(last, weekday ? 'day' : minor);
// Make sure that the last tick include max // Prevent browser from freezing in case user options request millions of milliseconds
if (last < max) { if (adapter.diff(max, min, minor) > 100000 * stepSize) {
last = +adapter.add(last, 1, minor); throw min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor;
} }
time = first; for (time = first; time < max; time = +adapter.add(time, stepSize, minor)) {
ticks.push(time);
if (majorTicksEnabled && major && !weekday && !timeOpts.round) {
// Align the first tick on the previous `minor` unit aligned on the `major` unit:
// we first aligned time on the previous `major` unit then add the number of full
// stepSize there is between first and the previous major time.
time = +adapter.startOf(time, major);
time = +adapter.add(time, ~~((first - time) / (interval.size * stepSize)) * stepSize, minor);
} }
for (; time < last; time = +adapter.add(time, stepSize, minor)) { if (time === max || options.bounds === 'ticks') {
ticks.push(+time); ticks.push(time);
} }
ticks.push(+time);
return ticks; return ticks;
} }
@ -609,18 +565,17 @@ module.exports = Scale.extend({
var timeOpts = options.time; var timeOpts = options.time;
var timestamps = me._timestamps; var timestamps = me._timestamps;
var ticks = []; var ticks = [];
var capacity = me.getLabelCapacity(min);
var source = options.ticks.source;
var distribution = options.distribution;
var i, ilen, timestamp; var i, ilen, timestamp;
switch (options.ticks.source) { if (source === 'data' || (source === 'auto' && distribution === 'series')) {
case 'data':
timestamps = timestamps.data; timestamps = timestamps.data;
break; } else if (source === 'labels') {
case 'labels':
timestamps = timestamps.labels; timestamps = timestamps.labels;
break; } else {
case 'auto': timestamps = generate(me, min, max, capacity, options);
default:
timestamps = generate(me, min, max, me.getLabelCapacity(min), options);
} }
if (options.bounds === 'ticks' && timestamps.length) { if (options.bounds === 'ticks' && timestamps.length) {
@ -645,8 +600,9 @@ module.exports = Scale.extend({
// PRIVATE // PRIVATE
me._unit = timeOpts.unit || determineUnitForFormatting(me, ticks, timeOpts.minUnit, me.min, me.max); me._unit = timeOpts.unit || determineUnitForFormatting(me, ticks, timeOpts.minUnit, me.min, me.max);
me._majorUnit = determineMajorUnit(me._unit); me._majorUnit = !options.ticks.major.enabled || me._unit === 'year' ? undefined
me._table = buildLookupTable(me._timestamps.data, min, max, options.distribution); : determineMajorUnit(me._unit);
me._table = buildLookupTable(me._timestamps.data, min, max, distribution);
me._offsets = computeOffsets(me._table, ticks, min, max, options); me._offsets = computeOffsets(me._table, ticks, min, max, options);
if (options.ticks.reverse) { if (options.ticks.reverse) {
@ -690,11 +646,10 @@ module.exports = Scale.extend({
var majorFormat = formats[majorUnit]; var majorFormat = formats[majorUnit];
var tick = ticks[index]; var tick = ticks[index];
var tickOpts = options.ticks; var tickOpts = options.ticks;
var majorTickOpts = tickOpts.major; var major = majorUnit && majorFormat && tick && tick.major;
var major = majorTickOpts.enabled && majorUnit && majorFormat && tick && tick.major;
var label = adapter.format(time, format ? format : major ? majorFormat : minorFormat); var label = adapter.format(time, format ? format : major ? majorFormat : minorFormat);
var nestedTickOpts = major ? majorTickOpts : tickOpts.minor; var nestedTickOpts = major ? tickOpts.major : tickOpts.minor;
var formatter = helpers.options.resolve([ var formatter = resolve([
nestedTickOpts.callback, nestedTickOpts.callback,
nestedTickOpts.userCallback, nestedTickOpts.userCallback,
tickOpts.callback, tickOpts.callback,

View File

@ -617,21 +617,22 @@ describe('Time scale tests', function() {
}); });
it('should build the correct ticks', function() { it('should build the correct ticks', function() {
// Where 'correct' is a two year spacing. expect(getTicksLabels(this.scale)).toEqual(['2005', '2006', '2007', '2008', '2009', '2010', '2011', '2012', '2013', '2014', '2015', '2016', '2017', '2018']);
expect(getTicksLabels(this.scale)).toEqual(['2005', '2007', '2009', '2011', '2013', '2015', '2017', '2019']);
}); });
it('should have ticks with accurate labels', function() { it('should have ticks with accurate labels', function() {
var scale = this.scale; var scale = this.scale;
var ticks = scale.getTicks(); var ticks = scale.getTicks();
var pixelsPerYear = scale.width / 14; // pixelsPerTick is an aproximation which assumes same number of milliseconds per year (not true)
// we use a threshold of 1 day so that we still match these values
var pixelsPerTick = scale.width / (ticks.length - 1);
for (var i = 0; i < ticks.length - 1; i++) { for (var i = 0; i < ticks.length - 1; i++) {
var offset = 2 * pixelsPerYear * i; var offset = pixelsPerTick * i;
expect(scale.getValueForPixel(scale.left + offset)).toBeCloseToTime({ expect(scale.getValueForPixel(scale.left + offset)).toBeCloseToTime({
value: moment(ticks[i].label + '-01-01'), value: moment(ticks[i].label + '-01-01'),
unit: 'day', unit: 'day',
threshold: 0.5, threshold: 1,
}); });
} }
}); });
@ -700,10 +701,9 @@ describe('Time scale tests', function() {
it('should get the correct labels for ticks', function() { it('should get the correct labels for ticks', function() {
var scale = this.scale; var scale = this.scale;
expect(scale._ticks.map(function(tick) { expect(scale.ticks.length).toEqual(61);
return tick.major; expect(scale.ticks[0]).toEqual('<8:00:00>');
})).toEqual([true, false, false, false, false, false, true]); expect(scale.ticks[scale.ticks.length - 1]).toEqual('<8:01:00>');
expect(scale.ticks).toEqual(['<8:00:00>', '<8:00:10>', '<8:00:20>', '<8:00:30>', '<8:00:40>', '<8:00:50>', '<8:01:00>']);
}); });
it('should update ticks.callback correctly', function() { it('should update ticks.callback correctly', function() {
@ -714,7 +714,9 @@ describe('Time scale tests', function() {
return '{' + value + '}'; return '{' + value + '}';
}; };
chart.update(); chart.update();
expect(scale.ticks).toEqual(['{8:00:00}', '{8:00:10}', '{8:00:20}', '{8:00:30}', '{8:00:40}', '{8:00:50}', '{8:01:00}']); expect(scale.ticks.length).toEqual(61);
expect(scale.ticks[0]).toEqual('{8:00:00}');
expect(scale.ticks[scale.ticks.length - 1]).toEqual('{8:01:00}');
}); });
}); });
@ -760,10 +762,10 @@ describe('Time scale tests', function() {
it('should get the correct labels for major and minor ticks', function() { it('should get the correct labels for major and minor ticks', function() {
var scale = this.scale; var scale = this.scale;
expect(scale._ticks.map(function(tick) { expect(scale.ticks.length).toEqual(61);
return tick.major; expect(scale.ticks[0]).toEqual('[[8:00 pm]]');
})).toEqual([true, false, false, false, false, false, true]); expect(scale.ticks[Math.floor(scale.ticks.length / 2)]).toEqual('(8:00:30 pm)');
expect(scale.ticks).toEqual(['[[8:00 pm]]', '(8:00:10 pm)', '(8:00:20 pm)', '(8:00:30 pm)', '(8:00:40 pm)', '(8:00:50 pm)', '[[8:01 pm]]']); expect(scale.ticks[scale.ticks.length - 1]).toEqual('[[8:01 pm]]');
}); });
it('should only use ticks.minor callback if ticks.major.enabled is false', function() { it('should only use ticks.minor callback if ticks.major.enabled is false', function() {
@ -772,7 +774,9 @@ describe('Time scale tests', function() {
chart.options.scales.xAxes[0].ticks.major.enabled = false; chart.options.scales.xAxes[0].ticks.major.enabled = false;
chart.update(); chart.update();
expect(scale.ticks).toEqual(['(8:00:00 pm)', '(8:00:10 pm)', '(8:00:20 pm)', '(8:00:30 pm)', '(8:00:40 pm)', '(8:00:50 pm)', '(8:01:00 pm)']); expect(scale.ticks.length).toEqual(61);
expect(scale.ticks[0]).toEqual('(8:00:00 pm)');
expect(scale.ticks[scale.ticks.length - 1]).toEqual('(8:01:00 pm)');
}); });
it('should use ticks.callback if ticks.major.callback is omitted', function() { it('should use ticks.callback if ticks.major.callback is omitted', function() {
@ -781,7 +785,9 @@ describe('Time scale tests', function() {
chart.options.scales.xAxes[0].ticks.major.callback = undefined; chart.options.scales.xAxes[0].ticks.major.callback = undefined;
chart.update(); chart.update();
expect(scale.ticks).toEqual(['<8:00 pm>', '(8:00:10 pm)', '(8:00:20 pm)', '(8:00:30 pm)', '(8:00:40 pm)', '(8:00:50 pm)', '<8:01 pm>']); expect(scale.ticks.length).toEqual(61);
expect(scale.ticks[0]).toEqual('<8:00 pm>');
expect(scale.ticks[scale.ticks.length - 1]).toEqual('<8:01 pm>');
}); });
it('should use ticks.callback if ticks.minor.callback is omitted', function() { it('should use ticks.callback if ticks.minor.callback is omitted', function() {
@ -790,7 +796,10 @@ describe('Time scale tests', function() {
chart.options.scales.xAxes[0].ticks.minor.callback = undefined; chart.options.scales.xAxes[0].ticks.minor.callback = undefined;
chart.update(); chart.update();
expect(scale.ticks).toEqual(['[[8:00 pm]]', '<8:00:10 pm>', '<8:00:20 pm>', '<8:00:30 pm>', '<8:00:40 pm>', '<8:00:50 pm>', '[[8:01 pm]]']); expect(scale.ticks.length).toEqual(61);
expect(scale.ticks[0]).toEqual('[[8:00 pm]]');
expect(scale.ticks[Math.floor(scale.ticks.length / 2)]).toEqual('<8:00:30 pm>');
expect(scale.ticks[scale.ticks.length - 1]).toEqual('[[8:01 pm]]');
}); });
}); });
@ -1766,6 +1775,52 @@ describe('Time scale tests', function() {
}); });
}); });
it('should handle autoskip with major and minor ticks', function() {
var date = moment('Jan 01 1990', 'MMM DD YYYY');
var data = [];
for (var i = 0; i < 60; i++) {
data.push({x: date.valueOf(), y: Math.random()});
date = date.clone().add(1, 'month');
}
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
xAxisID: 'xScale0',
data: data
}],
},
options: {
scales: {
xAxes: [{
id: 'xScale0',
type: 'time',
ticks: {
major: {
enabled: true
},
source: 'data',
autoSkip: true
}
}],
}
}
});
var scale = chart.scales.xScale0;
var labels = scale._ticksToDraw.map(function(t) {
return t.label;
});
expect(labels).toEqual([
'1990', 'Apr 1990', 'Jul 1990', 'Oct 1990',
'1991', 'Apr 1991', 'Jul 1991', 'Oct 1991',
'1992', 'Apr 1992', 'Jul 1992', 'Oct 1992',
'1993', 'Apr 1993', 'Jul 1993', 'Oct 1993',
'1994', 'Apr 1994', 'Jul 1994', 'Oct 1994']);
});
describe('Deprecations', function() { describe('Deprecations', function() {
describe('options.time.displayFormats', function() { describe('options.time.displayFormats', function() {
it('should generate defaults from adapter presets', function() { it('should generate defaults from adapter presets', function() {