diff --git a/src/core/core.scale.js b/src/core/core.scale.js index c515347045f..769a1f1ec46 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -7,6 +7,7 @@ var Ticks = require('./core.ticks'); var valueOrDefault = helpers.valueOrDefault; var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault; +var resolve = helpers.options.resolve; defaults._set('scale', { display: true, @@ -93,15 +94,18 @@ function measureText(ctx, data, gc, string) { return textWidth; } +/** + * Returns {width, height, offset} objects for the first, last, widest, highest tick + * labels where offset indicates the anchor point offset from the top in pixels. + */ 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 || []; + var offsets = []; + var data = cache.data = cache.data || {}; + var gc = cache.garbageCollect = cache.garbageCollect || []; + var gcLen, i, j, jlen, tick, label, tickFont, width, height, nestedLabel, widest, highest; for (i = 0; i < length; ++i) { tick = ticks[i]; @@ -126,6 +130,7 @@ function computeLabelSizes(ctx, tickFonts, ticks, cache) { } widths.push(width); heights.push(height); + offsets.push(tickFont.lineHeight / 2); } gcLen = gc.length / 2; @@ -142,40 +147,40 @@ function computeLabelSizes(ctx, tickFonts, ticks, cache) { return { first: { width: widths[0] || 0, - height: heights[0] || 0 + height: heights[0] || 0, + offset: offsets[0] || 0 }, last: { width: widths[length - 1] || 0, - height: heights[length - 1] || 0 + height: heights[length - 1] || 0, + offset: offsets[length - 1] || 0 }, widest: { width: widths[widest] || 0, - height: heights[widest] || 0 + height: heights[widest] || 0, + offset: offsets[widest] || 0 }, highest: { width: widths[highest] || 0, - height: heights[highest] || 0 + height: heights[highest] || 0, + offset: offsets[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); + 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) + lineHeight: helpers.options.toLineHeight(resolve([nestedOpts.lineHeight, options.lineHeight, globalDefaults.defaultLineHeight]), size), + color: resolve([nestedOpts.fontColor, options.fontColor, globalDefaults.defaultFontColor]) }; } @@ -186,6 +191,23 @@ function parseTickFontOptions(options) { return {minor: minor, major: major}; } +function getTickMarkLength(options) { + return options.drawTicks ? options.tickMarkLength : 0; +} + +function getScaleLabelHeight(options) { + var font, padding; + + if (!options.display) { + return 0; + } + + font = helpers.options._parseFont(options); + padding = helpers.options.toPadding(options.padding); + + return font.lineHeight + padding.height; +} + module.exports = Element.extend({ /** * Get the padding needed for the scale @@ -384,33 +406,31 @@ module.exports = Element.extend({ var options = me.options; var tickOpts = options.ticks; var ticks = me.getTicks(); - var labelSizes, maxLabelWidth, maxLabelHeight, tickWidth, angleRadians, cosRotation, sinRotation; + var labelSizes, maxLabelWidth, maxLabelHeight, tickWidth, maxHeight, maxLabelDiagonal; - // Get the width of each grid by calculating the difference - // between x offsets between 0 and 1. var labelRotation = tickOpts.minRotation || 0; - 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 - 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++; + if (me._isVisible() && tickOpts.display) { + labelSizes = me._labelSizes = computeLabelSizes(me.ctx, parseTickFontOptions(tickOpts), ticks, me.longestTextCache); + + if (ticks.length > 1 && me.isHorizontal()) { + maxLabelWidth = labelSizes.widest.width; + maxLabelHeight = labelSizes.highest.height - labelSizes.highest.offset; + + // Estimate the width of each grid based on the canvas width, the maximum + // label width and the number of tick intervals + tickWidth = (me.chart.width - maxLabelWidth) / (ticks.length - 1); + + // Allow 3 pixels x2 padding either side for label readability + if (maxLabelWidth + 6 > tickWidth) { + maxHeight = me.maxHeight - getTickMarkLength(options.gridLines) + - tickOpts.padding - getScaleLabelHeight(options.scaleLabel); + maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight); + labelRotation = helpers.toDegrees(Math.min( + Math.asin(Math.min((labelSizes.highest.height + 6) / tickWidth, 1)), + Math.asin(Math.min(maxHeight / maxLabelDiagonal, 1)) - Math.asin(maxLabelHeight / maxLabelDiagonal) + )); + labelRotation = Math.max(Number.MIN_VALUE, tickOpts.minRotation, Math.min(tickOpts.maxRotation, labelRotation)); } } } @@ -443,63 +463,47 @@ module.exports = Element.extend({ var position = opts.position; var isHorizontal = me.isHorizontal(); - var tickMarkLength = opts.gridLines.tickMarkLength; - // Width if (isHorizontal) { // subtract the margins to line up with the chartArea if we are a full width scale minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth; - } else { - minSize.width = display && gridLineOpts.drawTicks ? tickMarkLength : 0; + } else if (display) { + minSize.width = getTickMarkLength(gridLineOpts) + getScaleLabelHeight(scaleLabelOpts); } // height - if (isHorizontal) { - minSize.height = display && gridLineOpts.drawTicks ? tickMarkLength : 0; - } else { + if (!isHorizontal) { minSize.height = me.maxHeight; // fill all the height - } - - // Are we showing a title for the scale? - if (scaleLabelOpts.display && display) { - var scaleLabelFont = helpers.options._parseFont(scaleLabelOpts); - var scaleLabelPadding = helpers.options.toPadding(scaleLabelOpts.padding); - var deltaHeight = scaleLabelFont.lineHeight + scaleLabelPadding.height; - - if (isHorizontal) { - minSize.height += deltaHeight; - } else { - minSize.width += deltaHeight; - } + } else if (display) { + minSize.height = getTickMarkLength(gridLineOpts) + getScaleLabelHeight(scaleLabelOpts); } // Don't bother fitting the ticks if we are not showing them if (tickOpts.display && display) { var tickFonts = parseTickFontOptions(tickOpts); - var labelSizes = computeLabelSizes(me.ctx, tickFonts, ticks, me.longestTextCache); + var labelSizes = me._labelSizes; + var firstLabelSize = labelSizes.first; + var lastLabelSize = labelSizes.last; + var widestLabelSize = labelSizes.widest; + var highestLabelSize = labelSizes.highest; var lineSpace = tickFonts.minor.size * 0.5; var tickPadding = tickOpts.padding; - // Store the sizes of labels for _autoSkip - me._labelSizes = labelSizes; - if (isHorizontal) { // A horizontal axis is more constrained by the height. - me.longestLabelWidth = labelSizes.widest.width; + me.longestLabelWidth = widestLabelSize.width; var isRotated = me.labelRotation !== 0; var angleRadians = helpers.toRadians(me.labelRotation); var cosRotation = Math.cos(angleRadians); var sinRotation = Math.sin(angleRadians); - var labelHeight = sinRotation * labelSizes.widest.width - + cosRotation * labelSizes.highest.height * (isRotated ? 0.5 : 1) - + lineSpace; // padding + var labelHeight = sinRotation * widestLabelSize.width + + cosRotation * (highestLabelSize.height - (isRotated ? highestLabelSize.offset : 0)) + + (isRotated ? 0 : lineSpace); // padding minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding); - var firstLabelSize = labelSizes.first; - var lastLabelSize = labelSizes.last; var offsetLeft = me.getPixelForTick(0) - me.left; var offsetRight = me.right - me.getPixelForTick(ticks.length - 1); var paddingLeft, paddingRight; @@ -507,10 +511,12 @@ module.exports = Element.extend({ // 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 (isRotated) { - paddingLeft = (position === 'bottom' ? cosRotation * firstLabelSize.width : 0) - + sinRotation * firstLabelSize.height / 2; - paddingRight = (position === 'bottom' ? 0 : cosRotation * lastLabelSize.width) - + sinRotation * lastLabelSize.height / 2; + paddingLeft = position === 'bottom' ? + cosRotation * firstLabelSize.width + sinRotation * firstLabelSize.offset : + sinRotation * (firstLabelSize.height - firstLabelSize.offset); + paddingRight = position === 'bottom' ? + sinRotation * (lastLabelSize.height - lastLabelSize.offset) : + cosRotation * lastLabelSize.width + sinRotation * lastLabelSize.offset; } else { paddingLeft = firstLabelSize.width / 2; paddingRight = lastLabelSize.width / 2; @@ -523,12 +529,12 @@ module.exports = Element.extend({ var labelWidth = tickOpts.mirror ? 0 : // use lineSpace for consistency with horizontal axis // tickPadding is not implemented for horizontal - labelSizes.widest.width + tickPadding + lineSpace; + widestLabelSize.width + tickPadding + lineSpace; minSize.width = Math.min(me.maxWidth, minSize.width + labelWidth); - me.paddingTop = labelSizes.first.height / 2; - me.paddingBottom = labelSizes.last.height / 2; + me.paddingTop = firstLabelSize.height / 2; + me.paddingBottom = lastLabelSize.height / 2; } } @@ -792,10 +798,10 @@ module.exports = Element.extend({ var tickPadding = optionTicks.padding; var labelOffset = optionTicks.labelOffset; - var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0; + var tl = getTickMarkLength(gridLines); - var scaleLabelFontColor = valueOrDefault(scaleLabel.fontColor, defaultFontColor); - var scaleLabelFont = parseFont(scaleLabel); + var scaleLabelFontColor = 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); diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index 5d77777449e..dfa96ab8523 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -693,10 +693,10 @@ module.exports = Scale.extend({ var major = majorTickOpts.enabled && majorUnit && majorFormat && time === majorTime; var label = tick.format(formatOverride ? formatOverride : major ? majorFormat : minorFormat); var nestedTickOpts = major ? majorTickOpts : tickOpts.minor; - var formatter = valueOrDefault( - valueOrDefault(nestedTickOpts.callback, nestedTickOpts.userCallback), - valueOrDefault(tickOpts.callback, tickOpts.userCallback) - ); + var formatter = helpers.options.resolve([ + nestedTickOpts.callback, nestedTickOpts.userCallback, + tickOpts.callback, tickOpts.userCallback + ]); return formatter ? formatter(label, index, ticks) : label; },