mirror of
https://github.com/chartjs/Chart.js.git
synced 2024-10-06 04:09:08 +02:00
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:
parent
dab0a7f699
commit
f7f177f5ad
@ -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",
|
||||
|
@ -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()
|
||||
],
|
||||
}, {
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
Loading…
Reference in New Issue
Block a user