autoSkipPadding: 0,
labelOffset: 0,
// We pass through arrays to be rendered as multiline labels, we convert Others to strings here.
- callback: Chart.Ticks.formatters.values
+ callback: Chart.Ticks.formatters.values,
+ minor: {},
+ major: {}
}
};
// Any function defined here is inherited by all scale types.
// Any function can be extended by the scale type
+ mergeTicksOptions: function() {
+ var ticks = this.options.ticks;
+ if (ticks.minor === false) {
+ ticks.minor = {
+ display: false
+ };
+ }
+ if (ticks.major === false) {
+ ticks.major = {
+ display: false
+ };
+ }
+ for (var key in ticks) {
+ if (key !== 'major' && key !== 'minor') {
+ if (typeof ticks.minor[key] === 'undefined') {
+ ticks.minor[key] = ticks[key];
+ }
+ if (typeof ticks.major[key] === 'undefined') {
+ ticks.major[key] = ticks[key];
+ }
+ }
+ }
+ },
beforeUpdate: function() {
helpers.callback(this.options.beforeUpdate, [this]);
},
var context = me.ctx;
var globalDefaults = Chart.defaults.global;
- var optionTicks = options.ticks;
+ var optionTicks = options.ticks.minor;
+ var optionMajorTicks = options.ticks.major || optionTicks;
var gridLines = options.gridLines;
var scaleLabel = options.scaleLabel;
var tickFontColor = helpers.getValueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor);
var tickFont = parseFontOptions(optionTicks);
+ var majorTickFontColor = helpers.getValueOrDefault(optionMajorTicks.fontColor, globalDefaults.defaultFontColor);
+ var majorTickFont = parseFontOptions(optionMajorTicks);
var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0;
var cosRotation = Math.cos(labelRotationRadians);
var longestRotatedLabel = me.longestLabelWidth * cosRotation;
- // Make sure we draw text in the correct color and font
- context.fillStyle = tickFontColor;
-
var itemsToDraw = [];
if (isHorizontal) {
var yTickStart = options.position === 'bottom' ? me.top : me.bottom - tl;
var yTickEnd = options.position === 'bottom' ? me.top + tl : me.bottom;
- helpers.each(me.ticks, function(label, index) {
+ helpers.each(me.ticks, function(tick, index) {
+ var label = (tick && tick.value) || tick;
// If the callback returned a null or undefined value, do not draw this line
if (label === undefined || label === null) {
return;
glBorderDashOffset: borderDashOffset,
rotation: -1 * labelRotationRadians,
label: label,
+ major: tick.major,
textBaseline: textBaseline,
textAlign: textAlign
});
}
if (optionTicks.display) {
+ // Make sure we draw text in the correct color and font
context.save();
context.translate(itemToDraw.labelX, itemToDraw.labelY);
context.rotate(itemToDraw.rotation);
- context.font = tickFont.font;
+ context.font = itemToDraw.major ? majorTickFont.font : tickFont.font;
+ context.fillStyle = itemToDraw.major ? majorTickFontColor : tickFontColor;
context.textBaseline = itemToDraw.textBaseline;
context.textAlign = itemToDraw.textAlign;
displayFormats: {
millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM,
second: 'h:mm:ss a', // 11:20:01 AM
- minute: 'h:mm:ss a', // 11:20:01 AM
- hour: 'MMM D, hA', // Sept 4, 5PM
- day: 'll', // Sep 4 2015
+ minute: 'h:mm a', // 11:20 AM
+ hour: 'hA', // 5PM
+ day: 'MMM D', // Sep 4
week: 'll', // Week 46, or maybe "[W]WW - YYYY" ?
month: 'MMM YYYY', // Sept 2015
quarter: '[Q]Q - YYYY', // Q3
throw new Error('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com');
}
+ this.mergeTicksOptions();
+
Chart.Scale.prototype.initialize.call(this);
},
determineDataLimits: function() {
},
// Function to format an individual tick mark
tickFormatFunction: function(tick, index, ticks) {
- var formattedTick = tick.format(this.displayFormat);
- var tickOpts = this.options.ticks;
+ var formattedTick;
+ var tickClone = tick.clone();
+ var tickTimestamp = tick.valueOf();
+ var major = false;
+ var tickOpts;
+ if (this.majorUnit && this.majorDisplayFormat && tickTimestamp === tickClone.startOf(this.majorUnit).valueOf()) {
+ // format as major unit
+ formattedTick = tick.format(this.majorDisplayFormat);
+ tickOpts = this.options.ticks.major;
+ major = true;
+ } else {
+ // format as minor (base) unit
+ formattedTick = tick.format(this.displayFormat);
+ tickOpts = this.options.ticks.minor;
+ }
+
var callback = helpers.getValueOrDefault(tickOpts.callback, tickOpts.userCallback);
if (callback) {
- return callback(formattedTick, index, ticks);
+ return {
+ value: callback(formattedTick, index, ticks),
+ major: major
+ };
}
- return formattedTick;
+ return {
+ value: formattedTick,
+ major: major
+ };
},
convertTicksToLabels: function() {
var me = this;
var me = this;
me.displayFormat = me.options.time.displayFormats.millisecond; // Pick the longest format for guestimation
- var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, []);
+ var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, []).value;
var tickLabelWidth = me.getLabelWidth(exampleLabel);
var innerWidth = me.isHorizontal() ? me.width : me.height;
var labelCapacity = innerWidth / tickLabelWidth;
+
return labelCapacity;
}
});
return scale;
}
+ function getTicksValues(ticks) {
+ return ticks.map(function(tick) {
+ return tick.value;
+ });
+ }
+
beforeEach(function() {
// Need a time matcher for getValueFromPixel
jasmine.addMatchers({
callback: defaultConfig.ticks.callback, // make this nicer, then check explicitly below,
autoSkip: false,
autoSkipPadding: 0,
- labelOffset: 0
+ labelOffset: 0,
+ minor: {},
+ major: {},
},
time: {
parser: false,
displayFormats: {
millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM
second: 'h:mm:ss a', // 11:20:01 AM
- minute: 'h:mm:ss a', // 11:20:01 AM
- hour: 'MMM D, hA', // Sept 4, 5PM
- day: 'll', // Sep 4 2015
+ minute: 'h:mm a', // 11:20 AM
+ hour: 'hA', // 5PM
+ day: 'MMM D', // Sep 4
week: 'll', // Week 46, or maybe "[W]WW - YYYY" ?
month: 'MMM YYYY', // Sept 2015
quarter: '[Q]Q - YYYY', // Q3
year: 'YYYY' // 2015
- }
+ },
}
});
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']);
+ var ticks = getTicksValues(scale.ticks);
+
+ expect(ticks).toEqual(['Jan 2015', 'Jan 2', 'Jan 3', 'Jan 4', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11']);
});
it('should accept labels as date objects', function() {
};
var scale = createScale(mockData, Chart.scaleService.getScaleDefaults('time'));
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']);
+ var ticks = getTicksValues(scale.ticks);
+
+ expect(ticks).toEqual(['Jan 2015', 'Jan 2', 'Jan 3', 'Jan 4', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11']);
});
it('should accept data as xy points', function() {
var xScale = chart.scales.xScale0;
xScale.update(800, 200);
- expect(xScale.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']);
+ var ticks = getTicksValues(xScale.ticks);
+
+ expect(ticks).toEqual(['Jan 2015', 'Jan 2', 'Jan 3', 'Jan 4', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11']);
});
});
var xScale = chart.scales.xScale0;
// Counts down because the lines are drawn top to bottom
- expect(xScale.ticks[0]).toEqualOneOf(['Nov 19, 1981', 'Nov 20, 1981', 'Nov 21, 1981']); // handle time zone changes
- expect(xScale.ticks[1]).toEqualOneOf(['Nov 19, 1981', 'Nov 20, 1981', 'Nov 21, 1981']); // handle time zone changes
+ expect(xScale.ticks[0].value).toEqualOneOf(['Nov 19', 'Nov 20', 'Nov 21']); // handle time zone changes
+ expect(xScale.ticks[1].value).toEqualOneOf(['Nov 19', 'Nov 20', 'Nov 21']); // handle time zone changes
});
it('should build ticks using the config unit', function() {
var scale = createScale(mockData, config);
scale.update(2500, 200);
+ var ticks = getTicksValues(scale.ticks);
- 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']);
+ expect(ticks).toEqual(['8PM', '9PM', '10PM', '11PM', 'Jan 2', '1AM', '2AM', '3AM', '4AM', '5AM', '6AM', '7AM', '8AM', '9AM', '10AM', '11AM', '12PM', '1PM', '2PM', '3PM', '4PM', '5PM', '6PM', '7PM', '8PM', '9PM']);
});
it('build ticks honoring the minUnit', function() {
config.time.minUnit = 'day';
var scale = createScale(mockData, config);
- expect(scale.ticks).toEqual(['Jan 1, 2015', 'Jan 2, 2015', 'Jan 3, 2015']);
+ var ticks = getTicksValues(scale.ticks);
+
+ expect(ticks).toEqual(['Jan 2015', 'Jan 2', 'Jan 3']);
});
it('should build ticks using the config diff', function() {
var scale = createScale(mockData, config);
scale.update(800, 200);
+ var ticks = getTicksValues(scale.ticks);
// last date is feb 15 because we round to start of week
- expect(scale.ticks).toEqual(['Dec 28, 2014', 'Jan 4, 2015', 'Jan 11, 2015', 'Jan 18, 2015', 'Jan 25, 2015', 'Feb 1, 2015', 'Feb 8, 2015', 'Feb 15, 2015']);
+ expect(ticks).toEqual(['Dec 28, 2014', 'Jan 4, 2015', 'Jan 11, 2015', 'Jan 18, 2015', 'Jan 25, 2015', 'Feb 2015', 'Feb 8, 2015', 'Feb 15, 2015']);
});
describe('when specifying limits', function() {
config.time.min = '2014-12-29T04:00:00';
var scale = createScale(mockData, config);
- expect(scale.ticks[0]).toEqual('Dec 29, 2014');
+ expect(scale.ticks[0].value).toEqual('Dec 28');
});
it('should use the max option', function() {
config.time.max = '2015-01-05T06:00:00';
var scale = createScale(mockData, config);
- expect(scale.ticks[scale.ticks.length - 1]).toEqual('Jan 6, 2015');
+
+ expect(scale.ticks[scale.ticks.length - 1].value).toEqual('Jan 6');
});
});
// Wednesday
config.time.isoWeekday = 3;
var scale = createScale(mockData, config);
- expect(scale.ticks).toEqual(['Dec 31, 2014', 'Jan 7, 2015']);
+ var ticks = getTicksValues(scale.ticks);
+
+ expect(ticks).toEqual(['Dec 31, 2014', 'Jan 7, 2015']);
});
describe('when rendering several days', function() {
it('should build the correct ticks', function() {
// Where 'correct' is a two year spacing.
- expect(xScale.ticks).toEqual(['2005', '2007', '2009', '2011', '2013', '2015', '2017', '2019']);
+ expect(getTicksValues(xScale.ticks)).toEqual(['2005', '2007', '2009', '2011', '2013', '2015', '2017', '2019']);
});
it('should have ticks with accurate labels', function() {
for (var i = 0; i < ticks.length - 1; i++) {
var offset = 2 * pixelsPerYear * i;
expect(xScale.getValueForPixel(xScale.left + offset)).toBeCloseToTime({
- value: moment(ticks[i] + '-01-01'),
+ value: moment(ticks[i].value + '-01-01'),
unit: 'day',
threshold: 0.5,
});