Implemented aligment by senior unit in time axis. (#4267)

Implemented alignment by major unit in the time scale. This allows showing the first tick of a larger unit like days in a special way and is part of the basis of the time series scale.
This commit is contained in:
Andrew 2017-05-30 01:40:10 +03:00 committed by Evert Timberg
parent dab0a7f699
commit f7f177f5ad
5 changed files with 93 additions and 66 deletions

View File

@ -77,10 +77,10 @@
},
options: {
responsive: true,
title:{
display:true,
text:"Chart.js Time Point Data"
},
title:{
display:true,
text:"Chart.js Time Point Data"
},
scales: {
xAxes: [{
type: "time",

View File

@ -28,7 +28,7 @@
<button id="removeData">Remove Data</button>
<script>
var timeFormat = 'MM/DD/YYYY HH:mm';
function newDate(days) {
return moment().add(days, 'd').toDate();
}
@ -46,12 +46,12 @@
type: 'line',
data: {
labels: [ // Date Objects
newDate(0),
newDate(1),
newDate(2),
newDate(3),
newDate(4),
newDate(5),
newDate(0),
newDate(1),
newDate(2),
newDate(3),
newDate(4),
newDate(5),
newDate(6)
],
datasets: [{
@ -60,12 +60,12 @@
borderColor: window.chartColors.red,
fill: false,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor()
],
}, {
@ -74,12 +74,12 @@
borderColor: window.chartColors.blue,
fill: false,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor()
],
}, {

View File

@ -55,15 +55,25 @@ module.exports = function(Chart) {
var ticks = [];
if (options.maxTicks) {
var stepSize = options.stepSize;
ticks.push(options.min !== undefined ? options.min : niceRange.min);
var cur = moment(niceRange.min);
while (cur.add(stepSize, options.unit).valueOf() < niceRange.max) {
var startTick = options.min !== undefined ? options.min : niceRange.min;
var majorUnit = options.majorUnit;
var majorUnitStart = majorUnit ? moment(startTick).add(1, majorUnit).startOf(majorUnit) : startTick;
var startRange = majorUnitStart.valueOf() - startTick;
var stepValue = interval[options.unit].size * stepSize;
var startFraction = startRange % stepValue;
var alignedTick = startTick;
if (startFraction && majorUnit && !options.timeOpts.round && !options.timeOpts.isoWeekday) {
alignedTick += startFraction - stepValue;
ticks.push(alignedTick);
} else {
ticks.push(startTick);
}
var cur = moment(alignedTick);
var realMax = options.max || niceRange.max;
while (cur.add(stepSize, options.unit).valueOf() < realMax) {
ticks.push(cur.valueOf());
}
var realMax = options.max || niceRange.max;
if (ticks[ticks.length - 1] !== realMax) {
ticks.push(realMax);
}
ticks.push(cur.valueOf());
}
return ticks;
}
@ -128,6 +138,22 @@ module.exports = function(Chart) {
return unit;
},
/**
* Determine major unit accordingly to passed unit
* @param unit {String} relative unit
* @return {String} major unit
*/
determineMajorUnit: function(unit) {
var units = Object.keys(interval);
var majorUnit = null;
var unitIndex = units.indexOf(unit);
if (unitIndex < units.length - 1) {
majorUnit = units[unitIndex + 1];
}
return majorUnit;
},
/**
* Determines how we scale the unit
* @param min {Number} the scale minimum
@ -169,7 +195,7 @@ module.exports = function(Chart) {
generateTicks: function(options, dataRange) {
var niceMin;
var niceMax;
var isoWeekday = options.isoWeekday;
var isoWeekday = options.timeOpts.isoWeekday;
if (options.unit === 'week' && isoWeekday !== false) {
niceMin = moment(dataRange.min).startOf('isoWeek').isoWeekday(isoWeekday).valueOf();
niceMax = moment(dataRange.max).startOf('isoWeek').isoWeekday(isoWeekday);

View File

@ -135,8 +135,14 @@ module.exports = function(Chart) {
}
var maxTicks = me.getLabelCapacity(minTimestamp || dataMin);
var unit = timeOpts.unit || timeHelpers.determineUnit(timeOpts.minUnit, minTimestamp || dataMin, maxTimestamp || dataMax, maxTicks);
var majorUnit = timeHelpers.determineMajorUnit(unit);
me.displayFormat = timeOpts.displayFormats[unit];
me.majorDisplayFormat = timeOpts.displayFormats[majorUnit];
me.unit = unit;
me.majorUnit = majorUnit;
var stepSize = timeOpts.stepSize || timeHelpers.determineStepSize(minTimestamp || dataMin, maxTimestamp || dataMax, unit, maxTicks);
me.ticks = timeHelpers.generateTicks({
@ -144,8 +150,9 @@ module.exports = function(Chart) {
min: minTimestamp,
max: maxTimestamp,
stepSize: stepSize,
majorUnit: majorUnit,
unit: unit,
isoWeekday: timeOpts.isoWeekday
timeOpts: timeOpts
}, {
min: dataMin,
max: dataMax

View File

@ -122,7 +122,8 @@ describe('Time scale tests', function() {
labels: ['2015-01-01T12:00:00', '2015-01-02T21:00:00', '2015-01-03T22:00:00', '2015-01-05T23:00:00', '2015-01-07T03:00', '2015-01-08T10:00', '2015-01-10T12:00'], // days
};
var scale = createScale(mockData, Chart.scaleService.getScaleDefaults('time'));
var scaleOptions = Chart.scaleService.getScaleDefaults('time');
var scale = createScale(mockData, scaleOptions);
scale.update(1000, 200);
expect(scale.ticks).toEqual(['Jan 1, 2015', 'Jan 2, 2015', 'Jan 3, 2015', 'Jan 4, 2015', 'Jan 5, 2015', 'Jan 6, 2015', 'Jan 7, 2015', 'Jan 8, 2015', 'Jan 9, 2015', 'Jan 10, 2015', 'Jan 11, 2015']);
});
@ -231,6 +232,7 @@ describe('Time scale tests', function() {
var scale = createScale(mockData, config);
scale.update(2500, 200);
expect(scale.ticks).toEqual(['Jan 1, 8PM', 'Jan 1, 9PM', 'Jan 1, 10PM', 'Jan 1, 11PM', 'Jan 2, 12AM', 'Jan 2, 1AM', 'Jan 2, 2AM', 'Jan 2, 3AM', 'Jan 2, 4AM', 'Jan 2, 5AM', 'Jan 2, 6AM', 'Jan 2, 7AM', 'Jan 2, 8AM', 'Jan 2, 9AM', 'Jan 2, 10AM', 'Jan 2, 11AM', 'Jan 2, 12PM', 'Jan 2, 1PM', 'Jan 2, 2PM', 'Jan 2, 3PM', 'Jan 2, 4PM', 'Jan 2, 5PM', 'Jan 2, 6PM', 'Jan 2, 7PM', 'Jan 2, 8PM', 'Jan 2, 9PM']);
});
@ -285,7 +287,7 @@ describe('Time scale tests', function() {
config.time.max = '2015-01-05T06:00:00';
var scale = createScale(mockData, config);
expect(scale.ticks[scale.ticks.length - 1]).toEqual('Jan 5, 2015');
expect(scale.ticks[scale.ticks.length - 1]).toEqual('Jan 6, 2015');
});
});
@ -329,34 +331,28 @@ describe('Time scale tests', function() {
var xScale = chart.scales.xScale0;
it('should be bounded by the nearest day beginnings', function() {
expect(xScale.getValueForPixel(xScale.left)).toBeCloseToTime({
value: moment(chart.data.labels[0]).startOf('day'),
unit: 'hour',
});
expect(xScale.getValueForPixel(xScale.right)).toBeCloseToTime({
value: moment(chart.data.labels[chart.data.labels.length - 1]).endOf('day'),
unit: 'hour',
});
it('should be bounded by the nearest week beginnings', function() {
expect(xScale.getValueForPixel(xScale.left)).toBeGreaterThan(moment(chart.data.labels[0]).startOf('week'));
expect(xScale.getValueForPixel(xScale.right)).toBeLessThan(moment(chart.data.labels[chart.data.labels.length - 1]).add(1, 'week').endOf('week'));
});
it('should convert between screen coordinates and times', function() {
var timeRange = moment('2015-01-11').valueOf() - moment('2015-01-01').valueOf();
var msInHour = 3600000;
var firstLabelAlong = 20 * msInHour / timeRange;
var firstLabelPixel = xScale.left + (xScale.width * firstLabelAlong);
var lastLabelAlong = (timeRange - (12 * msInHour)) / timeRange;
var lastLabelPixel = xScale.left + (xScale.width * lastLabelAlong);
var timeRange = moment(xScale.max).valueOf() - moment(xScale.min).valueOf();
var msPerPix = timeRange / xScale.width;
var firstPointOffsetMs = moment(chart.config.data.labels[0]).valueOf() - xScale.min;
var firstPointPixel = xScale.left + firstPointOffsetMs / msPerPix;
var lastPointOffsetMs = moment(chart.config.data.labels[chart.config.data.labels.length - 1]).valueOf() - xScale.min;
var lastPointPixel = xScale.left + lastPointOffsetMs / msPerPix;
expect(xScale.getPixelForValue('', 0, 0)).toBeCloseToPixel(firstLabelPixel);
expect(xScale.getPixelForValue(chart.data.labels[0])).toBeCloseToPixel(firstLabelPixel);
expect(xScale.getValueForPixel(firstLabelPixel)).toBeCloseToTime({
expect(xScale.getPixelForValue('', 0, 0)).toBeCloseToPixel(firstPointPixel);
expect(xScale.getPixelForValue(chart.data.labels[0])).toBeCloseToPixel(firstPointPixel);
expect(xScale.getValueForPixel(firstPointPixel)).toBeCloseToTime({
value: moment(chart.data.labels[0]),
unit: 'hour',
});
expect(xScale.getPixelForValue('', 6, 0)).toBeCloseToPixel(lastLabelPixel);
expect(xScale.getValueForPixel(lastLabelPixel)).toBeCloseToTime({
expect(xScale.getPixelForValue('', 6, 0)).toBeCloseToPixel(lastPointPixel);
expect(xScale.getValueForPixel(lastPointPixel)).toBeCloseToTime({
value: moment(chart.data.labels[6]),
unit: 'hour'
});
@ -382,26 +378,28 @@ describe('Time scale tests', function() {
var xScale = chart.scales.xScale0;
xScale.update(800, 200);
var step = xScale.ticksAsTimestamps[1] - xScale.ticksAsTimestamps[0];
var stepsAmount = Math.floor((xScale.max - xScale.min) / step);
it('should be bounded by nearest year starts', function() {
it('should be bounded by nearest step year starts', function() {
expect(xScale.getValueForPixel(xScale.left)).toBeCloseToTime({
value: moment(chart.data.labels[0]).startOf('year'),
value: moment(xScale.min).startOf('year'),
unit: 'hour',
});
expect(xScale.getValueForPixel(xScale.right)).toBeCloseToTime({
value: moment(chart.data.labels[chart.data.labels - 1]).endOf('year'),
value: moment(xScale.min + step * stepsAmount).endOf('year'),
unit: 'hour',
});
});
it('should build the correct ticks', function() {
// Where 'correct' is a two year spacing, except the last tick which is the year end of the last point.
expect(xScale.ticks).toEqual(['2005', '2007', '2009', '2011', '2013', '2015', '2017', '2018']);
// Where 'correct' is a two year spacing.
expect(xScale.ticks).toEqual(['2005', '2007', '2009', '2011', '2013', '2015', '2017', '2019']);
});
it('should have ticks with accurate labels', function() {
var ticks = xScale.ticks;
var pixelsPerYear = xScale.width / 13;
var pixelsPerYear = xScale.width / 14;
for (var i = 0; i < ticks.length - 1; i++) {
var offset = 2 * pixelsPerYear * i;
@ -463,13 +461,9 @@ describe('Time scale tests', function() {
});
var xScale = chart.scales.xScale0;
var pixel = xScale.getPixelForValue('', 0, 0);
expect(xScale.getPixelForValue('', 0, 0)).toBeCloseToPixel(62);
expect(xScale.getValueForPixel(62)).toBeCloseToTime({
value: moment(chart.data.labels[0]),
unit: 'day',
});
expect(xScale.getValueForPixel(pixel).valueOf()).toEqual(moment(chart.data.labels[0]).valueOf());
});
it('does not create a negative width chart when hidden', function() {