From a984162c7e8492b22fa857bf701a4f2adfffd457 Mon Sep 17 00:00:00 2001 From: Akihiko Kusanagi Date: Mon, 7 Jan 2019 03:13:20 +0800 Subject: [PATCH] Fix ticks.minor and ticks.major configuration issues --- docs/axes/styling.md | 3 +- src/core/core.scale.js | 281 ++++++++++++++++++------------ src/scales/scale.time.js | 10 +- test/specs/plugin.legend.tests.js | 53 +++--- test/specs/scale.time.tests.js | 125 +++++++++++++ 5 files changed, 329 insertions(+), 143 deletions(-) diff --git a/docs/axes/styling.md b/docs/axes/styling.md index 1f06ca7af4a..4d7c1e08594 100644 --- a/docs/axes/styling.md +++ b/docs/axes/styling.md @@ -54,10 +54,11 @@ The minorTick configuration is nested under the ticks configuration in the `mino | `lineHeight` | `Number/String` | `1.2` | Height of an individual line of text (see [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/line-height)). ## Major Tick Configuration -The majorTick configuration is nested under the ticks configuration in the `major` key. It defines options for the major tick marks that are generated by the axis. Omitted options are inherited from `ticks` configuration. +The majorTick configuration is nested under the ticks configuration in the `major` key. It defines options for the major tick marks that are generated by the axis. Omitted options are inherited from `ticks` configuration. These options are disabled by default. | Name | Type | Default | Description | -----| ---- | --------| ----------- +| `enabled` | `Boolean` | `false` | If true, major tick options are used to show major ticks. | `callback` | `Function` | | Returns the string representation of the tick value as it should be displayed on the chart. See [callback](../axes/labelling.md#creating-custom-tick-formats). | `fontColor` | `Color` | `'#666'` | Font color for tick labels. | `fontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family for the tick labels, follows CSS font-family options. diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 690491fbc60..3b50d279546 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -62,17 +62,6 @@ defaults._set('scale', { } }); -function labelsFromTicks(ticks) { - var labels = []; - var i, ilen; - - for (i = 0, ilen = ticks.length; i < ilen; ++i) { - labels.push(ticks[i].label); - } - - return labels; -} - function getPixelForGridLine(scale, index, offsetGridLines) { var lineValue = scale.getPixelForTick(index); @@ -90,10 +79,108 @@ function getPixelForGridLine(scale, index, offsetGridLines) { return lineValue; } -function computeTextSize(context, tick, font) { - return helpers.isArray(tick) ? - helpers.longestText(context, font, tick) : - context.measureText(tick).width; +function measureText(ctx, data, gc, string) { + var key = ctx.font + ';' + string; + var textWidth = data[key]; + + if (!textWidth) { + textWidth = data[key] = ctx.measureText(string).width; + gc.push(key); + } + return textWidth; +} + +function computeLabelSizes(ctx, tickFonts, ticks, cache) { + var length = ticks.length; + var widths = []; + var heights = []; + var data, gc, gcLen, i, j, jlen, tick, label, tickFont, width, height, nestedLabel, widest, highest; + + cache = cache || {}; + data = cache.data = cache.data || {}; + gc = cache.garbageCollect = cache.garbageCollect || []; + + for (i = 0; i < length; ++i) { + tick = ticks[i]; + label = tick.label; + tickFont = tick.major ? tickFonts.major : tickFonts.minor; + width = height = 0; + ctx.font = tickFont.string; + // Undefined labels and arrays should not be measured + if (!helpers.isNullOrUndef(label) && !helpers.isArray(label)) { + width = measureText(ctx, data, gc, label); + height = tickFont.lineHeight; + } else if (helpers.isArray(label)) { + // if it is an array lets measure each element + for (j = 0, jlen = label.length; j < jlen; ++j) { + nestedLabel = label[j]; + // Undefined labels and arrays should not be measured + if (!helpers.isNullOrUndef(nestedLabel) && !helpers.isArray(nestedLabel)) { + width = Math.max(width, measureText(ctx, data, gc, nestedLabel)); + height += tickFont.lineHeight; + } + } + } + widths.push(width); + heights.push(height); + } + + gcLen = gc.length / 2; + if (gcLen > length) { + for (i = 0; i < gcLen; ++i) { + delete data[gc[i]]; + } + gc.splice(0, gcLen); + } + + widest = widths.indexOf(Math.max.apply(null, widths)); + highest = heights.indexOf(Math.max.apply(null, heights)); + + return { + first: { + width: widths[0] || 0, + height: heights[0] || 0 + }, + last: { + width: widths[length - 1] || 0, + height: heights[length - 1] || 0 + }, + widest: { + width: widths[widest] || 0, + height: heights[widest] || 0 + }, + highest: { + width: widths[highest] || 0, + height: heights[highest] || 0 + } + }; +} + +function resolve(value1, value2, defaultValue) { + return value1 !== undefined ? value1 : value2 !== undefined ? value2 : defaultValue; +} + +function parseFontOptions(options, nestedOpts) { + var globalDefaults = defaults.global; + var size = resolve(nestedOpts.fontSize, options.fontSize, globalDefaults.defaultFontSize); + var style = resolve(nestedOpts.fontStyle, options.fontStyle, globalDefaults.defaultFontStyle); + var family = resolve(nestedOpts.fontFamily, options.fontFamily, globalDefaults.defaultFontFamily); + + return { + size: size, + style: style, + family: family, + string: helpers.fontString(size, style, family), + lineHeight: helpers.options.toLineHeight(resolve(nestedOpts.lineHeight, options.lineHeight, globalDefaults.defaultLineHeight), size), + color: resolve(nestedOpts.fontColor, options.fontColor, globalDefaults.defaultFontColor) + }; +} + +function parseTickFontOptions(options) { + var minor = parseFontOptions(options, options.minor); + var major = options.major.enabled ? parseFontOptions(options, options.major) : minor; + + return {minor: minor, major: major}; } module.exports = Element.extend({ @@ -126,27 +213,7 @@ module.exports = Element.extend({ // 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]; - } - } - } + // noop }, beforeUpdate: function() { helpers.callback(this.options.beforeUpdate, [this]); @@ -311,39 +378,37 @@ module.exports = Element.extend({ }, calculateTickRotation: function() { var me = this; - var context = me.ctx; - var tickOpts = me.options.ticks; - var labels = labelsFromTicks(me._ticks); + var options = me.options; + var tickOpts = options.ticks; + var ticks = me.getTicks(); + var labelSizes, maxLabelWidth, maxLabelHeight, tickWidth, angleRadians, cosRotation, sinRotation; // Get the width of each grid by calculating the difference // between x offsets between 0 and 1. - var tickFont = helpers.options._parseFont(tickOpts); - context.font = tickFont.string; - var labelRotation = tickOpts.minRotation || 0; - if (labels.length && me.options.display && me.isHorizontal()) { - var originalLabelWidth = helpers.longestText(context, tickFont.string, labels, me.longestTextCache); - var labelWidth = originalLabelWidth; - var cosRotation, sinRotation; + if (ticks.length > 1 && options.display && me.isHorizontal()) { + labelSizes = computeLabelSizes(me.ctx, parseTickFontOptions(tickOpts), ticks, me.longestTextCache); + maxLabelWidth = labelSizes.widest.width; + maxLabelHeight = labelSizes.highest.height; + + tickWidth = (me.chart.width - maxLabelWidth) / (ticks.length - 1); // Allow 3 pixels x2 padding either side for label readability - var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6; - - // Max label rotation can be set or default to 90 - also act as a loop counter - while (labelWidth > tickWidth && labelRotation < tickOpts.maxRotation) { - var angleRadians = helpers.toRadians(labelRotation); - cosRotation = Math.cos(angleRadians); - sinRotation = Math.sin(angleRadians); - - if (sinRotation * originalLabelWidth > me.maxHeight) { - // go back one step - labelRotation--; - break; + if (maxLabelWidth > tickWidth - 6) { + // Max label rotation can be set or default to 90 - also act as a loop counter + while (labelRotation < tickOpts.maxRotation) { + angleRadians = helpers.toRadians(labelRotation); + cosRotation = Math.cos(angleRadians); + sinRotation = Math.sin(angleRadians); + if (sinRotation * maxLabelWidth + cosRotation * maxLabelHeight > me.maxHeight) { + labelRotation--; + break; + } else if (maxLabelHeight <= tickWidth * sinRotation - 6) { + break; + } + labelRotation++; } - - labelRotation++; - labelWidth = cosRotation * originalLabelWidth; } } @@ -366,8 +431,7 @@ module.exports = Element.extend({ height: 0 }; - var labels = labelsFromTicks(me._ticks); - + var ticks = me.getTicks(); var opts = me.options; var tickOpts = opts.ticks; var scaleLabelOpts = opts.scaleLabel; @@ -376,8 +440,6 @@ module.exports = Element.extend({ var position = opts.position; var isHorizontal = me.isHorizontal(); - var parseFont = helpers.options._parseFont; - var tickFont = parseFont(tickOpts); var tickMarkLength = opts.gridLines.tickMarkLength; // Width @@ -397,7 +459,7 @@ module.exports = Element.extend({ // Are we showing a title for the scale? if (scaleLabelOpts.display && display) { - var scaleLabelFont = parseFont(scaleLabelOpts); + var scaleLabelFont = helpers.options._parseFont(scaleLabelOpts); var scaleLabelPadding = helpers.options.toPadding(scaleLabelOpts.padding); var deltaHeight = scaleLabelFont.lineHeight + scaleLabelPadding.height; @@ -410,62 +472,60 @@ module.exports = Element.extend({ // Don't bother fitting the ticks if we are not showing them if (tickOpts.display && display) { - var largestTextWidth = helpers.longestText(me.ctx, tickFont.string, labels, me.longestTextCache); - var tallestLabelHeightInLines = helpers.numberOfLabelLines(labels); - var lineSpace = tickFont.size * 0.5; - var tickPadding = me.options.ticks.padding; + var tickFonts = parseTickFontOptions(tickOpts); + var labelSizes = computeLabelSizes(me.ctx, tickFonts, ticks, me.longestTextCache); + var lineSpace = tickFonts.minor.size * 0.5; + var tickPadding = tickOpts.padding; - // Store max number of lines used in labels for _autoSkip - me._maxLabelLines = tallestLabelHeightInLines; + // Store the sizes of labels for _autoSkip + me._labelSizes = labelSizes; if (isHorizontal) { // A horizontal axis is more constrained by the height. - me.longestLabelWidth = largestTextWidth; + me.longestLabelWidth = labelSizes.widest.width; + var isRotated = me.labelRotation !== 0; var angleRadians = helpers.toRadians(me.labelRotation); var cosRotation = Math.cos(angleRadians); var sinRotation = Math.sin(angleRadians); - // TODO - improve this calculation - var labelHeight = (sinRotation * largestTextWidth) - + (tickFont.lineHeight * tallestLabelHeightInLines) + var labelHeight = sinRotation * labelSizes.widest.width + + cosRotation * labelSizes.highest.height * (isRotated ? 0.5 : 1) + lineSpace; // padding minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding); - me.ctx.font = tickFont.string; - var firstLabelWidth = computeTextSize(me.ctx, labels[0], tickFont.string); - var lastLabelWidth = computeTextSize(me.ctx, labels[labels.length - 1], tickFont.string); + var firstLabelSize = labelSizes.first; + var lastLabelSize = labelSizes.last; var offsetLeft = me.getPixelForTick(0) - me.left; - var offsetRight = me.right - me.getPixelForTick(labels.length - 1); + var offsetRight = me.right - me.getPixelForTick(ticks.length - 1); var paddingLeft, paddingRight; // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned // which means that the right padding is dominated by the font height - if (me.labelRotation !== 0) { - paddingLeft = position === 'bottom' ? (cosRotation * firstLabelWidth) : (cosRotation * lineSpace); - paddingRight = position === 'bottom' ? (cosRotation * lineSpace) : (cosRotation * lastLabelWidth); + if (isRotated) { + paddingLeft = (position === 'bottom' ? cosRotation * firstLabelSize.width : 0) + + sinRotation * firstLabelSize.height / 2; + paddingRight = (position === 'bottom' ? 0 : cosRotation * lastLabelSize.width) + + sinRotation * lastLabelSize.height / 2; } else { - paddingLeft = firstLabelWidth / 2; - paddingRight = lastLabelWidth / 2; + paddingLeft = firstLabelSize.width / 2; + paddingRight = lastLabelSize.width / 2; } me.paddingLeft = Math.max(paddingLeft - offsetLeft, 0) + 3; // add 3 px to move away from canvas edges me.paddingRight = Math.max(paddingRight - offsetRight, 0) + 3; } else { // A vertical axis is more constrained by the width. Labels are the // dominant factor here, so get that length first and account for padding - if (tickOpts.mirror) { - largestTextWidth = 0; - } else { + var labelWidth = tickOpts.mirror ? 0 : // use lineSpace for consistency with horizontal axis // tickPadding is not implemented for horizontal - largestTextWidth += tickPadding + lineSpace; - } + labelSizes.widest.width + tickPadding + lineSpace; - minSize.width = Math.min(me.maxWidth, minSize.width + largestTextWidth); + minSize.width = Math.min(me.maxWidth, minSize.width + labelWidth); - me.paddingTop = tickFont.size / 2; - me.paddingBottom = tickFont.size / 2; + me.paddingTop = labelSizes.first.height / 2; + me.paddingBottom = labelSizes.last.height / 2; } } @@ -617,7 +677,7 @@ module.exports = Element.extend({ var skipRatio; var me = this; var isHorizontal = me.isHorizontal(); - var optionTicks = me.options.ticks.minor; + var optionTicks = me.options.ticks; var tickCount = ticks.length; // Calculate space needed by label in axis direction. @@ -625,11 +685,10 @@ module.exports = Element.extend({ var cos = Math.abs(Math.cos(rot)); var sin = Math.abs(Math.sin(rot)); + var labelSizes = me._labelSizes; var padding = optionTicks.autoSkipPadding; - var w = me.longestLabelWidth + padding || 0; - - var tickFont = helpers.options._parseFont(optionTicks); - var h = me._maxLabelLines * tickFont.lineHeight + padding; + var w = labelSizes ? labelSizes.widest.width + padding : 0; + var h = labelSizes ? labelSizes.highest.height + padding : 0; // Calculate space needed for 1 tick in axis direction. var tickSize = isHorizontal @@ -716,10 +775,7 @@ module.exports = Element.extend({ var chart = me.chart; var context = me.ctx; - var globalDefaults = defaults.global; - var defaultFontColor = globalDefaults.defaultFontColor; - var optionTicks = options.ticks.minor; - var optionMajorTicks = options.ticks.major || optionTicks; + var optionTicks = options.ticks; var gridLines = options.gridLines; var scaleLabel = options.scaleLabel; var position = options.position; @@ -728,20 +784,15 @@ module.exports = Element.extend({ var isMirrored = optionTicks.mirror; var isHorizontal = me.isHorizontal(); - var parseFont = helpers.options._parseFont; var ticks = optionTicks.autoSkip ? me._autoSkip(me.getTicks()) : me.getTicks(); - var tickFontColor = helpers.valueOrDefault(optionTicks.fontColor, defaultFontColor); - var tickFont = parseFont(optionTicks); - var lineHeight = tickFont.lineHeight; - var majorTickFontColor = helpers.valueOrDefault(optionMajorTicks.fontColor, defaultFontColor); - var majorTickFont = parseFont(optionMajorTicks); + var tickFonts = parseTickFontOptions(optionTicks); var tickPadding = optionTicks.padding; var labelOffset = optionTicks.labelOffset; var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0; - var scaleLabelFontColor = helpers.valueOrDefault(scaleLabel.fontColor, defaultFontColor); - var scaleLabelFont = parseFont(scaleLabel); + var scaleLabelFontColor = helpers.valueOrDefault(scaleLabel.fontColor, defaults.global.defaultFontColor); + var scaleLabelFont = helpers.options._parseFont(scaleLabel); var scaleLabelPadding = helpers.options.toPadding(scaleLabel.padding); var labelRotationRadians = helpers.toRadians(me.labelRotation); @@ -778,6 +829,8 @@ module.exports = Element.extend({ } var label = tick.label; + var tickFont = tick.major ? tickFonts.major : tickFonts.minor; + var lineHeight = tickFont.lineHeight; var lineWidth, lineColor, borderDash, borderDashOffset; if (index === me.zeroLineIndex && options.offset === gridLines.offsetGridLines) { // Draw the first index specially @@ -902,12 +955,14 @@ module.exports = Element.extend({ } if (optionTicks.display) { + var tickFont = itemToDraw.major ? tickFonts.major : tickFonts.minor; + // 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 = itemToDraw.major ? majorTickFont.string : tickFont.string; - context.fillStyle = itemToDraw.major ? majorTickFontColor : tickFontColor; + context.font = tickFont.string; + context.fillStyle = tickFont.color; context.textBaseline = 'middle'; context.textAlign = itemToDraw.textAlign; @@ -917,7 +972,7 @@ module.exports = Element.extend({ for (var i = 0; i < label.length; ++i) { // We just make sure the multiline element is a string here.. context.fillText('' + label[i], 0, y); - y += lineHeight; + y += tickFont.lineHeight; } } else { context.fillText(label, 0, y); diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index c7c49eb6b1b..b59a6cf6453 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -686,11 +686,15 @@ module.exports = Scale.extend({ var majorUnit = me._majorUnit; var majorFormat = formats[majorUnit]; var majorTime = tick.clone().startOf(majorUnit).valueOf(); - var majorTickOpts = options.ticks.major; + var tickOpts = options.ticks; + var majorTickOpts = tickOpts.major; var major = majorTickOpts.enabled && majorUnit && majorFormat && time === majorTime; var label = tick.format(formatOverride ? formatOverride : major ? majorFormat : minorFormat); - var tickOpts = major ? majorTickOpts : options.ticks.minor; - var formatter = helpers.valueOrDefault(tickOpts.callback, tickOpts.userCallback); + var nestedTickOpts = major ? majorTickOpts : tickOpts.minor; + var formatter = helpers.valueOrDefault( + helpers.valueOrDefault(nestedTickOpts.callback, nestedTickOpts.userCallback), + helpers.valueOrDefault(tickOpts.callback, tickOpts.userCallback) + ); return formatter ? formatter(label, index, ticks) : label; }, diff --git a/test/specs/plugin.legend.tests.js b/test/specs/plugin.legend.tests.js index fee715bdb13..8f47502f029 100644 --- a/test/specs/plugin.legend.tests.js +++ b/test/specs/plugin.legend.tests.js @@ -476,7 +476,7 @@ describe('Legend block tests', function() { var chart = window.acquireChart({ type: 'bar', data: { - datasets: Array.apply(null, Array(22)).map(function() { + datasets: Array.apply(null, Array(23)).map(function() { return { label: ' ', data: [] @@ -492,34 +492,35 @@ describe('Legend block tests', function() { }); expect(chart.legend.left).toBeCloseToPixel(0); - expect(chart.legend.top).toBeCloseToPixel(6); + expect(chart.legend.top).toBeCloseToPixel(7); expect(chart.legend.width).toBeCloseToPixel(128); - expect(chart.legend.height).toBeCloseToPixel(476); - expect(chart.legend.legendHitBoxes.length).toBe(22); + expect(chart.legend.height).toBeCloseToPixel(488); + expect(chart.legend.legendHitBoxes.length).toBe(23); [ - {h: 12, l: 10, t: 16, w: 49}, - {h: 12, l: 10, t: 38, w: 49}, - {h: 12, l: 10, t: 60, w: 49}, - {h: 12, l: 10, t: 82, w: 49}, - {h: 12, l: 10, t: 104, w: 49}, - {h: 12, l: 10, t: 126, w: 49}, - {h: 12, l: 10, t: 148, w: 49}, - {h: 12, l: 10, t: 170, w: 49}, - {h: 12, l: 10, t: 192, w: 49}, - {h: 12, l: 10, t: 214, w: 49}, - {h: 12, l: 10, t: 236, w: 49}, - {h: 12, l: 10, t: 258, w: 49}, - {h: 12, l: 10, t: 280, w: 49}, - {h: 12, l: 10, t: 302, w: 49}, - {h: 12, l: 10, t: 324, w: 49}, - {h: 12, l: 10, t: 346, w: 49}, - {h: 12, l: 10, t: 368, w: 49}, - {h: 12, l: 10, t: 390, w: 49}, - {h: 12, l: 10, t: 412, w: 49}, - {h: 12, l: 10, t: 434, w: 49}, - {h: 12, l: 10, t: 456, w: 49}, - {h: 12, l: 69, t: 16, w: 49} + {h: 12, l: 10, t: 17, w: 49}, + {h: 12, l: 10, t: 39, w: 49}, + {h: 12, l: 10, t: 61, w: 49}, + {h: 12, l: 10, t: 83, w: 49}, + {h: 12, l: 10, t: 105, w: 49}, + {h: 12, l: 10, t: 127, w: 49}, + {h: 12, l: 10, t: 149, w: 49}, + {h: 12, l: 10, t: 171, w: 49}, + {h: 12, l: 10, t: 193, w: 49}, + {h: 12, l: 10, t: 215, w: 49}, + {h: 12, l: 10, t: 237, w: 49}, + {h: 12, l: 10, t: 259, w: 49}, + {h: 12, l: 10, t: 281, w: 49}, + {h: 12, l: 10, t: 303, w: 49}, + {h: 12, l: 10, t: 325, w: 49}, + {h: 12, l: 10, t: 347, w: 49}, + {h: 12, l: 10, t: 369, w: 49}, + {h: 12, l: 10, t: 391, w: 49}, + {h: 12, l: 10, t: 413, w: 49}, + {h: 12, l: 10, t: 435, w: 49}, + {h: 12, l: 10, t: 457, w: 49}, + {h: 12, l: 10, t: 479, w: 49}, + {h: 12, l: 69, t: 17, w: 49} ].forEach(function(expected, i) { expect(chart.legend.legendHitBoxes[i].height).toBeCloseToPixel(expected.h); expect(chart.legend.legendHitBoxes[i].left).toBeCloseToPixel(expected.l); diff --git a/test/specs/scale.time.tests.js b/test/specs/scale.time.tests.js index c0fd1bda93b..9655f1b3233 100755 --- a/test/specs/scale.time.tests.js +++ b/test/specs/scale.time.tests.js @@ -750,6 +750,131 @@ describe('Time scale tests', function() { expect(chart.width).toEqual(0); }); + describe('when ticks.callback is specified', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + xAxisID: 'xScale0', + data: [0, 0] + }], + labels: ['2015-01-01T20:00:00', '2015-01-01T20:01:00'] + }, + options: { + scales: { + xAxes: [{ + id: 'xScale0', + type: 'time', + ticks: { + callback: function(value) { + return '<' + value + '>'; + } + } + }] + } + } + }); + this.scale = this.chart.scales.xScale0; + }); + + it('should get the correct labels for ticks', function() { + var scale = this.scale; + + expect(scale._ticks.map(function(tick) { + return tick.major; + })).toEqual([true, false, false, false, false, false, true]); + 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>']); + }); + + it('should update ticks.callback correctly', function() { + var chart = this.chart; + var scale = this.scale; + + chart.options.scales.xAxes[0].ticks.callback = function(value) { + return '{' + value + '}'; + }; + 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}']); + }); + }); + + describe('when ticks.major.callback and ticks.minor.callback are specified', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + xAxisID: 'xScale0', + data: [0, 0] + }], + labels: ['2015-01-01T20:00:00', '2015-01-01T20:01:00'] + }, + options: { + scales: { + xAxes: [{ + id: 'xScale0', + type: 'time', + ticks: { + callback: function(value) { + return '<' + value + '>'; + }, + major: { + enabled: true, + callback: function(value) { + return '[' + value + ']'; + } + }, + minor: { + callback: function(value) { + return '(' + value + ')'; + } + } + } + }] + } + } + }); + this.scale = this.chart.scales.xScale0; + }); + + it('should get the correct labels for major and minor ticks', function() { + var scale = this.scale; + + expect(scale._ticks.map(function(tick) { + return tick.major; + })).toEqual([true, false, false, false, false, false, true]); + 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]']); + }); + + it('should only use ticks.minor callback if ticks.major.enabled is false', function() { + var chart = this.chart; + var scale = this.scale; + + chart.options.scales.xAxes[0].ticks.major.enabled = false; + 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)']); + }); + + it('should use ticks.callback if ticks.major.callback is omitted', function() { + var chart = this.chart; + var scale = this.scale; + + chart.options.scales.xAxes[0].ticks.major.callback = undefined; + 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>']); + }); + + it('should use ticks.callback if ticks.minor.callback is omitted', function() { + var chart = this.chart; + var scale = this.scale; + + chart.options.scales.xAxes[0].ticks.minor.callback = undefined; + 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]']); + }); + }); + describe('when ticks.source', function() { describe('is "labels"', function() { beforeEach(function() {