Skip to content
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
5 changes: 5 additions & 0 deletions docs/01-Scales.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,13 @@ The time scale extends the core scale class with the following tick template:
parser: false,
// string - By default, unit will automatically be detected. Override with 'week', 'month', 'year', etc. (see supported time measurements)
unit: false,

// Number - The number of steps of the above unit between ticks
unitStepSize: 1

// string - By default, no rounding is applied. To round, set to a supported time unit eg. 'week', 'month', 'year', etc.
round: false,

// Moment js for each of the units. Replaces `displayFormat`
// To override, use a pattern string from http://momentjs.com/docs/#/displaying/format/
displayFormats: {
Expand Down
8 changes: 4 additions & 4 deletions src/core/core.scale.js
Original file line number Diff line number Diff line change
Expand Up @@ -476,10 +476,6 @@ module.exports = function(Chart) {
skipRatio = 1 + Math.floor((((longestRotatedLabel / 2) + this.options.ticks.autoSkipPadding) * this.ticks.length) / (this.width - (this.paddingLeft + this.paddingRight)));
}

if (!useAutoskipper) {
skipRatio = false;
}

// if they defined a max number of ticks,
// increase skipRatio until that number is met
if (maxTicks && this.ticks.length > maxTicks) {
Expand All @@ -491,6 +487,10 @@ module.exports = function(Chart) {
}
}

if (!useAutoskipper) {
skipRatio = false;
}

helpers.each(this.ticks, function(label, index) {
// Blank ticks
var isLastTick = this.ticks.length === index + 1;
Expand Down
69 changes: 42 additions & 27 deletions src/scales/scale.time.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,22 +138,30 @@ module.exports = function(Chart) {

this.ticks = [];
this.unitScale = 1; // How much we scale the unit by, ie 2 means 2x unit per step
this.scaleSizeInUnits = 0; // How large the scale is in the base unit (seconds, minutes, etc)

// Set unit override if applicable
if (this.options.time.unit) {
this.tickUnit = this.options.time.unit || 'day';
this.displayFormat = this.options.time.displayFormats[this.tickUnit];
this.tickRange = Math.ceil(this.lastTick.diff(this.firstTick, this.tickUnit, true));
this.scaleSizeInUnits = this.lastTick.diff(this.firstTick, this.tickUnit, true);
this.unitScale = helpers.getValueOrDefault(this.options.time.unitStepSize, 1);
} else {
// Determine the smallest needed unit of the time
var tickFontSize = helpers.getValueOrDefault(this.options.ticks.fontSize, Chart.defaults.global.defaultFontSize);
var innerWidth = this.isHorizontal() ? this.width - (this.paddingLeft + this.paddingRight) : this.height - (this.paddingTop + this.paddingBottom);
var labelCapacity = innerWidth / (tickFontSize + 10);
var buffer = this.options.time.round ? 0 : 1;

// Crude approximation of what the label length might be
var tempFirstLabel = this.tickFormatFunction(this.firstTick, 0, []);
var tickLabelWidth = tempFirstLabel.length * tickFontSize;
var cosRotation = Math.cos(helpers.toRadians(this.options.ticks.maxRotation));
var sinRotation = Math.sin(helpers.toRadians(this.options.ticks.maxRotation));
tickLabelWidth = (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation);
var labelCapacity = innerWidth / (tickLabelWidth + 10);

// Start as small as possible
this.tickUnit = 'millisecond';
this.tickRange = Math.ceil(this.lastTick.diff(this.firstTick, this.tickUnit, true) + buffer);
this.scaleSizeInUnits = this.lastTick.diff(this.firstTick, this.tickUnit, true);
this.displayFormat = this.options.time.displayFormats[this.tickUnit];

var unitDefinitionIndex = 0;
Expand All @@ -164,27 +172,27 @@ module.exports = function(Chart) {
// Can we scale this unit. If `false` we can scale infinitely
this.unitScale = 1;

if (helpers.isArray(unitDefinition.steps) && Math.ceil(this.tickRange / labelCapacity) < helpers.max(unitDefinition.steps)) {
if (helpers.isArray(unitDefinition.steps) && Math.ceil(this.scaleSizeInUnits / labelCapacity) < helpers.max(unitDefinition.steps)) {
// Use one of the prefedined steps
for (var idx = 0; idx < unitDefinition.steps.length; ++idx) {
if (unitDefinition.steps[idx] > Math.ceil(this.tickRange / labelCapacity)) {
this.unitScale = unitDefinition.steps[idx];
if (unitDefinition.steps[idx] > Math.ceil(this.scaleSizeInUnits / labelCapacity)) {
this.unitScale = helpers.getValueOrDefault(this.options.time.unitStepSize, unitDefinition.steps[idx]);
break;
}
}

break;
} else if ((unitDefinition.maxStep === false) || (Math.ceil(this.tickRange / labelCapacity) < unitDefinition.maxStep)) {
} else if ((unitDefinition.maxStep === false) || (Math.ceil(this.scaleSizeInUnits / labelCapacity) < unitDefinition.maxStep)) {
// We have a max step. Scale this unit
this.unitScale = Math.ceil(this.tickRange / labelCapacity);
this.unitScale = helpers.getValueOrDefault(this.options.time.unitStepSize, Math.ceil(this.scaleSizeInUnits / labelCapacity));
break;
} else {
// Move to the next unit up
++unitDefinitionIndex;
unitDefinition = time.units[unitDefinitionIndex];

this.tickUnit = unitDefinition.name;
this.tickRange = Math.ceil(this.lastTick.diff(this.firstTick, this.tickUnit) + buffer);
this.scaleSizeInUnits = this.lastTick.diff(this.firstTick, this.tickUnit, true);
this.displayFormat = this.options.time.displayFormats[unitDefinition.name];
}
}
Expand Down Expand Up @@ -222,7 +230,7 @@ module.exports = function(Chart) {
this.ticks.push(this.firstTick.clone());

// For every unit in between the first and last moment, create a moment and add it to the ticks tick
for (var i = 1; i < this.tickRange; ++i) {
for (var i = 1; i < this.scaleSizeInUnits; ++i) {
var newTick = roundedStart.clone().add(i, this.tickUnit);

// Are we greater than the max time
Expand All @@ -236,12 +244,17 @@ module.exports = function(Chart) {
}

// Always show the right tick
if (this.options.time.max) {
this.ticks.push(this.lastTick.clone());
} else if (this.ticks[this.ticks.length - 1].diff(this.lastTick, this.tickUnit, true) !== 0) {
this.tickRange = Math.ceil(this.tickRange / this.unitScale) * this.unitScale;
this.ticks.push(this.firstTick.clone().add(this.tickRange, this.tickUnit));
this.lastTick = this.ticks[this.ticks.length - 1].clone();
if (this.ticks[this.ticks.length - 1].diff(this.lastTick, this.tickUnit) !== 0 || this.scaleSizeInUnits === 0) {
// this is a weird case. If the <max> option is the same as the end option, we can't just diff the times because the tick was created from the roundedStart
// but the last tick was not rounded.
if (this.options.time.max) {
this.ticks.push(this.lastTick.clone());
this.scaleSizeInUnits = this.lastTick.diff(this.ticks[0], this.tickUnit, true);
} else {
this.scaleSizeInUnits = Math.ceil(this.scaleSizeInUnits / this.unitScale) * this.unitScale;
this.ticks.push(this.firstTick.clone().add(this.scaleSizeInUnits, this.tickUnit));
this.lastTick = this.ticks[this.ticks.length - 1].clone();
}
}
},
// Get tooltip label
Expand All @@ -259,24 +272,26 @@ module.exports = function(Chart) {

return label;
},
convertTicksToLabels: function() {
this.ticks = this.ticks.map(function(tick, index, ticks) {
var formattedTick = tick.format(this.displayFormat);
// Function to format an individual tick mark
tickFormatFunction: function tickFormatFunction(tick, index, ticks) {
var formattedTick = tick.format(this.displayFormat);

if (this.options.ticks.userCallback) {
return this.options.ticks.userCallback(formattedTick, index, ticks);
} else {
return formattedTick;
}
}, this);
if (this.options.ticks.userCallback) {
return this.options.ticks.userCallback(formattedTick, index, ticks);
} else {
return formattedTick;
}
},
convertTicksToLabels: function() {
this.ticks = this.ticks.map(this.tickFormatFunction, this);
},
getPixelForValue: function(value, index, datasetIndex, includeOffset) {
var labelMoment = this.getLabelMoment(datasetIndex, index);

if (labelMoment) {
var offset = labelMoment.diff(this.firstTick, this.tickUnit, true);

var decimal = offset / this.tickRange;
var decimal = offset / this.scaleSizeInUnits;

if (this.isHorizontal()) {
var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
Expand Down
24 changes: 23 additions & 1 deletion test/scale.time.tests.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
// Time scale tests
describe('Time scale tests', function() {

beforeEach(function() {
jasmine.addMatchers({
toEqualOneOf: function() {
return {
compare: function(actual, expecteds) {
var result = false;
for (var i = 0, l = expecteds.length; i < l; i++) {
if (actual === expecteds[i]) {
result = true;
break;
}
}
return {
pass: result
};
}
};
}
});
});

it('Should load moment.js as a dependency', function() {
expect(window.moment).not.toBe(undefined);
});
Expand Down Expand Up @@ -202,7 +223,8 @@ describe('Time scale tests', function() {
scale.update(400, 50);

// Counts down because the lines are drawn top to bottom
expect(scale.ticks).toEqual(['Nov 20, 1981', 'Nov 20, 1981']);
expect(scale.ticks[0]).toEqualOneOf(['Nov 19, 1981', 'Nov 20, 1981']); // handle time zone changes
expect(scale.ticks[1]).toEqualOneOf(['Nov 19, 1981', 'Nov 20, 1981']); // handle time zone changes
});

it('should build ticks using the config unit', function() {
Expand Down