diff --git a/docs/axes/radial/linear.md b/docs/axes/radial/linear.md index d1edd18be54..92f1e35b78a 100644 --- a/docs/axes/radial/linear.md +++ b/docs/axes/radial/linear.md @@ -109,3 +109,4 @@ The following options are used to configure the point labels that are shown on t | `fontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family to use when rendering labels. | `fontSize` | `Number` | 10 | font size in pixels. | `fontStyle` | `String` | `'normal'` | Font style to use when rendering point labels. +| `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)). diff --git a/docs/axes/styling.md b/docs/axes/styling.md index 7184bc96faf..1f06ca7af4a 100644 --- a/docs/axes/styling.md +++ b/docs/axes/styling.md @@ -35,6 +35,7 @@ The tick configuration is nested under the scale configuration in the `ticks` ke | `fontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family for the tick labels, follows CSS font-family options. | `fontSize` | `Number` | `12` | Font size for the tick labels. | `fontStyle` | `String` | `'normal'` | Font style for the tick labels, follows CSS font-style options (i.e. normal, italic, oblique, initial, inherit). +| `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)). | `reverse` | `Boolean` | `false` | Reverses order of tick labels. | `minor` | `object` | `{}` | Minor ticks configuration. Omitted options are inherited from options above. | `major` | `object` | `{}` | Major ticks configuration. Omitted options are inherited from options above. @@ -50,6 +51,7 @@ The minorTick configuration is nested under the ticks configuration in the `mino | `fontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family for the tick labels, follows CSS font-family options. | `fontSize` | `Number` | `12` | Font size for the tick labels. | `fontStyle` | `String` | `'normal'` | Font style for the tick labels, follows CSS font-style options (i.e. normal, italic, oblique, initial, inherit). +| `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. @@ -61,3 +63,4 @@ The majorTick configuration is nested under the ticks configuration in the `majo | `fontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family for the tick labels, follows CSS font-family options. | `fontSize` | `Number` | `12` | Font size for the tick labels. | `fontStyle` | `String` | `'normal'` | Font style for the tick labels, follows CSS font-style options (i.e. normal, italic, oblique, initial, inherit). +| `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)). diff --git a/src/core/core.defaults.js b/src/core/core.defaults.js index 29bb040d4f0..4a2bb8e1b40 100644 --- a/src/core/core.defaults.js +++ b/src/core/core.defaults.js @@ -1,6 +1,6 @@ 'use strict'; -var helpers = require('../helpers/index'); +var helpers = require('../helpers/helpers.core'); module.exports = { /** diff --git a/src/core/core.js b/src/core/core.js index 906b897c699..8860b9bbcf4 100644 --- a/src/core/core.js +++ b/src/core/core.js @@ -19,6 +19,7 @@ defaults._set('global', { defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", defaultFontSize: 12, defaultFontStyle: 'normal', + defaultLineHeight: 1.2, showLines: true, // Element defaults defined in element extensions diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 45ba93bd739..c538de50a27 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -36,9 +36,6 @@ defaults._set('scale', { // actual label labelString: '', - // line height - lineHeight: 1.2, - // top/bottom padding padding: { top: 4, @@ -99,27 +96,6 @@ function computeTextSize(context, tick, font) { context.measureText(tick).width; } -function parseFontOptions(options) { - var valueOrDefault = helpers.valueOrDefault; - var globalDefaults = defaults.global; - var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize); - var style = valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle); - var family = valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily); - - return { - size: size, - style: style, - family: family, - font: helpers.fontString(size, style, family) - }; -} - -function parseLineHeight(options) { - return helpers.options.toLineHeight( - helpers.valueOrDefault(options.lineHeight, 1.2), - helpers.valueOrDefault(options.fontSize, defaults.global.defaultFontSize)); -} - module.exports = Element.extend({ /** * Get the padding needed for the scale @@ -341,13 +317,13 @@ module.exports = Element.extend({ // Get the width of each grid by calculating the difference // between x offsets between 0 and 1. - var tickFont = parseFontOptions(tickOpts); - context.font = tickFont.font; + 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.font, labels, me.longestTextCache); + var originalLabelWidth = helpers.longestText(context, tickFont.string, labels, me.longestTextCache); var labelWidth = originalLabelWidth; var cosRotation, sinRotation; @@ -400,7 +376,8 @@ module.exports = Element.extend({ var position = opts.position; var isHorizontal = me.isHorizontal(); - var tickFont = parseFontOptions(tickOpts); + var parseFont = helpers.options._parseFont; + var tickFont = parseFont(tickOpts); var tickMarkLength = opts.gridLines.tickMarkLength; // Width @@ -420,9 +397,9 @@ module.exports = Element.extend({ // Are we showing a title for the scale? if (scaleLabelOpts.display && display) { - var scaleLabelLineHeight = parseLineHeight(scaleLabelOpts); + var scaleLabelFont = parseFont(scaleLabelOpts); var scaleLabelPadding = helpers.options.toPadding(scaleLabelOpts.padding); - var deltaHeight = scaleLabelLineHeight + scaleLabelPadding.height; + var deltaHeight = scaleLabelFont.lineHeight + scaleLabelPadding.height; if (isHorizontal) { minSize.height += deltaHeight; @@ -433,7 +410,7 @@ 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.font, labels, me.longestTextCache); + 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; @@ -448,15 +425,14 @@ module.exports = Element.extend({ // TODO - improve this calculation var labelHeight = (sinRotation * largestTextWidth) - + (tickFont.size * tallestLabelHeightInLines) - + (lineSpace * (tallestLabelHeightInLines - 1)) + + (tickFont.lineHeight * tallestLabelHeightInLines) + lineSpace; // padding minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding); - me.ctx.font = tickFont.font; - var firstLabelWidth = computeTextSize(me.ctx, labels[0], tickFont.font); - var lastLabelWidth = computeTextSize(me.ctx, labels[labels.length - 1], tickFont.font); + 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 offsetLeft = me.getPixelForTick(0) - me.left; var offsetRight = me.right - me.getPixelForTick(labels.length - 1); var paddingLeft, paddingRight; @@ -690,6 +666,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 gridLines = options.gridLines; @@ -700,18 +677,20 @@ 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, globalDefaults.defaultFontColor); - var tickFont = parseFontOptions(optionTicks); - var majorTickFontColor = helpers.valueOrDefault(optionMajorTicks.fontColor, globalDefaults.defaultFontColor); - var majorTickFont = parseFontOptions(optionMajorTicks); + 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 tickPadding = optionTicks.padding; var labelOffset = optionTicks.labelOffset; var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0; - var scaleLabelFontColor = helpers.valueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor); - var scaleLabelFont = parseFontOptions(scaleLabel); + var scaleLabelFontColor = helpers.valueOrDefault(scaleLabel.fontColor, defaultFontColor); + var scaleLabelFont = parseFont(scaleLabel); var scaleLabelPadding = helpers.options.toPadding(scaleLabel.padding); var labelRotationRadians = helpers.toRadians(me.labelRotation); @@ -763,8 +742,8 @@ module.exports = Element.extend({ } // Common properties - var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY, textAlign; - var textBaseline = 'middle'; + var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY, textOffset, textAlign; + var labelCount = helpers.isArray(label) ? label.length : 1; var lineValue = getPixelForGridLine(me, index, gridLines.offsetGridLines); if (isHorizontal) { @@ -782,13 +761,13 @@ module.exports = Element.extend({ if (position === 'top') { y1 = alignPixel(chart, chartArea.top, axisWidth) + axisWidth / 2; y2 = chartArea.bottom; - textBaseline = !isRotated ? 'bottom' : 'middle'; + textOffset = ((!isRotated ? 0.5 : 1) - labelCount) * lineHeight; textAlign = !isRotated ? 'center' : 'left'; labelY = me.bottom - labelYOffset; } else { y1 = chartArea.top; y2 = alignPixel(chart, chartArea.bottom, axisWidth) - axisWidth / 2; - textBaseline = !isRotated ? 'top' : 'middle'; + textOffset = (!isRotated ? 0.5 : 0) * lineHeight; textAlign = !isRotated ? 'center' : 'right'; labelY = me.top + labelYOffset; } @@ -803,6 +782,7 @@ module.exports = Element.extend({ tx2 = tickEnd; ty1 = ty2 = y1 = y2 = alignPixel(chart, lineValue, lineWidth); labelY = me.getPixelForTick(index) + labelOffset; + textOffset = (1 - labelCount) * lineHeight / 2; if (position === 'left') { x1 = alignPixel(chart, chartArea.left, axisWidth) + axisWidth / 2; @@ -835,7 +815,7 @@ module.exports = Element.extend({ rotation: -1 * labelRotationRadians, label: label, major: tick.major, - textBaseline: textBaseline, + textOffset: textOffset, textAlign: textAlign }); }); @@ -875,25 +855,21 @@ module.exports = Element.extend({ context.save(); context.translate(itemToDraw.labelX, itemToDraw.labelY); context.rotate(itemToDraw.rotation); - context.font = itemToDraw.major ? majorTickFont.font : tickFont.font; + context.font = itemToDraw.major ? majorTickFont.string : tickFont.string; context.fillStyle = itemToDraw.major ? majorTickFontColor : tickFontColor; - context.textBaseline = itemToDraw.textBaseline; + context.textBaseline = 'middle'; context.textAlign = itemToDraw.textAlign; var label = itemToDraw.label; + var y = itemToDraw.textOffset; if (helpers.isArray(label)) { - var lineCount = label.length; - var lineHeight = tickFont.size * 1.5; - var y = isHorizontal ? 0 : -lineHeight * (lineCount - 1) / 2; - - for (var i = 0; i < lineCount; ++i) { + 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); - // apply same lineSpacing as calculated @ L#320 y += lineHeight; } } else { - context.fillText(label, 0, 0); + context.fillText(label, 0, y); } context.restore(); } @@ -904,7 +880,7 @@ module.exports = Element.extend({ var scaleLabelX; var scaleLabelY; var rotation = 0; - var halfLineHeight = parseLineHeight(scaleLabel) / 2; + var halfLineHeight = scaleLabelFont.lineHeight / 2; if (isHorizontal) { scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width @@ -926,7 +902,7 @@ module.exports = Element.extend({ context.textAlign = 'center'; context.textBaseline = 'middle'; context.fillStyle = scaleLabelFontColor; // render in correct colour - context.font = scaleLabelFont.font; + context.font = scaleLabelFont.string; context.fillText(scaleLabel.labelString, 0, 0); context.restore(); } diff --git a/src/elements/element.line.js b/src/elements/element.line.js index 1500d353c96..96bb5be383e 100644 --- a/src/elements/element.line.js +++ b/src/elements/element.line.js @@ -4,15 +4,15 @@ var defaults = require('../core/core.defaults'); var Element = require('../core/core.element'); var helpers = require('../helpers/index'); -var globalDefaults = defaults.global; +var defaultColor = defaults.global.defaultColor; defaults._set('global', { elements: { line: { tension: 0.4, - backgroundColor: globalDefaults.defaultColor, + backgroundColor: defaultColor, borderWidth: 3, - borderColor: globalDefaults.defaultColor, + borderColor: defaultColor, borderCapStyle: 'butt', borderDash: [], borderDashOffset: 0.0, @@ -30,6 +30,7 @@ module.exports = Element.extend({ var ctx = me._chart.ctx; var spanGaps = vm.spanGaps; var points = me._children.slice(); // clone array + var globalDefaults = defaults.global; var globalOptionLineElements = globalDefaults.elements.line; var lastDrawnIndex = -1; var index, current, previous, currentVM; diff --git a/src/elements/element.point.js b/src/elements/element.point.js index 56eb5796617..b7909116a7c 100644 --- a/src/elements/element.point.js +++ b/src/elements/element.point.js @@ -73,6 +73,8 @@ module.exports = Element.extend({ var x = vm.x; var y = vm.y; var epsilon = 0.0000001; // 0.0000001 is margin in pixels for Accumulated error. + var globalDefaults = defaults.global; + var defaultColor = globalDefaults.defaultColor; // eslint-disable-line no-shadow if (vm.skip) { return; @@ -81,7 +83,7 @@ module.exports = Element.extend({ // Clipping for Points. if (chartArea === undefined || (model.x > chartArea.left - epsilon && chartArea.right + epsilon > model.x && model.y > chartArea.top - epsilon && chartArea.bottom + epsilon > model.y)) { ctx.strokeStyle = vm.borderColor || defaultColor; - ctx.lineWidth = helpers.valueOrDefault(vm.borderWidth, defaults.global.elements.point.borderWidth); + ctx.lineWidth = helpers.valueOrDefault(vm.borderWidth, globalDefaults.elements.point.borderWidth); ctx.fillStyle = vm.backgroundColor || defaultColor; helpers.canvas.drawPoint(ctx, pointStyle, radius, x, y, rotation); } diff --git a/src/elements/element.rectangle.js b/src/elements/element.rectangle.js index ef59ac0dd62..1cb6fbb534f 100644 --- a/src/elements/element.rectangle.js +++ b/src/elements/element.rectangle.js @@ -3,11 +3,13 @@ var defaults = require('../core/core.defaults'); var Element = require('../core/core.element'); +var defaultColor = defaults.global.defaultColor; + defaults._set('global', { elements: { rectangle: { - backgroundColor: defaults.global.defaultColor, - borderColor: defaults.global.defaultColor, + backgroundColor: defaultColor, + borderColor: defaultColor, borderSkipped: 'bottom', borderWidth: 0 } diff --git a/src/helpers/helpers.options.js b/src/helpers/helpers.options.js index 8e6c0aadfae..0b107e40d84 100644 --- a/src/helpers/helpers.options.js +++ b/src/helpers/helpers.options.js @@ -1,7 +1,25 @@ 'use strict'; +var defaults = require('../core/core.defaults'); var helpers = require('./helpers.core'); +/** + * Converts the given font object into a CSS font string. + * @param {Object} font - A font object. + * @return {Stringg} The CSS font string. See https://developer.mozilla.org/en-US/docs/Web/CSS/font + * @private + */ +function toFontString(font) { + if (!font || helpers.isNullOrUndef(font.size) || helpers.isNullOrUndef(font.family)) { + return null; + } + + return (font.style ? font.style + ' ' : '') + + (font.weight ? font.weight + ' ' : '') + + font.size + 'px ' + + font.family; +} + /** * @alias Chart.helpers.options * @namespace @@ -65,6 +83,30 @@ module.exports = { }; }, + /** + * Parses font options and returns the font object. + * @param {Object} options - A object that contains font opttons to be parsed. + * @return {Object} The font object. + * @todo Support font.* options and renamed to toFont(). + * @private + */ + _parseFont: function(options) { + var valueOrDefault = helpers.valueOrDefault; + var globalDefaults = defaults.global; + var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize); + var font = { + family: valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily), + lineHeight: helpers.options.toLineHeight(valueOrDefault(options.lineHeight, globalDefaults.defaultLineHeight), size), + size: size, + style: valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle), + weight: null, + string: '' + }; + + font.string = toFontString(font); + return font; + }, + /** * Evaluates the given `inputs` sequentially and returns the first defined value. * @param {Array[]} inputs - An array of values, falling back to the last value. diff --git a/src/plugins/plugin.legend.js b/src/plugins/plugin.legend.js index 8e6b75f65fe..f11b823ea58 100644 --- a/src/plugins/plugin.legend.js +++ b/src/plugins/plugin.legend.js @@ -211,12 +211,8 @@ var Legend = Element.extend({ var ctx = me.ctx; - var globalDefault = defaults.global; - var valueOrDefault = helpers.valueOrDefault; - var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize); - var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle); - var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily); - var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); + var labelFont = helpers.options._parseFont(labelOpts); + var fontSize = labelFont.size; // Reset hit boxes var hitboxes = me.legendHitBoxes = []; @@ -234,7 +230,7 @@ var Legend = Element.extend({ // Increase sizes here if (display) { - ctx.font = labelFont; + ctx.font = labelFont.string; if (isHorizontal) { // Labels @@ -323,19 +319,18 @@ var Legend = Element.extend({ var me = this; var opts = me.options; var labelOpts = opts.labels; - var globalDefault = defaults.global; - var lineDefault = globalDefault.elements.line; + var globalDefaults = defaults.global; + var defaultColor = globalDefaults.defaultColor; + var lineDefault = globalDefaults.elements.line; var legendWidth = me.width; var lineWidths = me.lineWidths; if (opts.display) { var ctx = me.ctx; var valueOrDefault = helpers.valueOrDefault; - var fontColor = valueOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor); - var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize); - var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle); - var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily); - var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); + var fontColor = valueOrDefault(labelOpts.fontColor, globalDefaults.defaultFontColor); + var labelFont = helpers.options._parseFont(labelOpts); + var fontSize = labelFont.size; var cursor; // Canvas setup @@ -344,7 +339,7 @@ var Legend = Element.extend({ ctx.lineWidth = 0.5; ctx.strokeStyle = fontColor; // for strikethrough effect ctx.fillStyle = fontColor; // render in correct colour - ctx.font = labelFont; + ctx.font = labelFont.string; var boxWidth = getBoxWidth(labelOpts, fontSize); var hitboxes = me.legendHitBoxes; @@ -358,13 +353,13 @@ var Legend = Element.extend({ // Set the ctx for the box ctx.save(); - ctx.fillStyle = valueOrDefault(legendItem.fillStyle, globalDefault.defaultColor); + var lineWidth = valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth); + ctx.fillStyle = valueOrDefault(legendItem.fillStyle, defaultColor); ctx.lineCap = valueOrDefault(legendItem.lineCap, lineDefault.borderCapStyle); ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset); ctx.lineJoin = valueOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle); - ctx.lineWidth = valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth); - ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, globalDefault.defaultColor); - var isLineWidthZero = (valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth) === 0); + ctx.lineWidth = lineWidth; + ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, defaultColor); if (ctx.setLineDash) { // IE 9 and 10 do not support line dash @@ -383,7 +378,7 @@ var Legend = Element.extend({ helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY); } else { // Draw box as legend symbol - if (!isLineWidthZero) { + if (lineWidth !== 0) { ctx.strokeRect(x, y, boxWidth, fontSize); } ctx.fillRect(x, y, boxWidth, fontSize); diff --git a/src/plugins/plugin.title.js b/src/plugins/plugin.title.js index 47588844d4c..eb09aa0e203 100644 --- a/src/plugins/plugin.title.js +++ b/src/plugins/plugin.title.js @@ -12,7 +12,6 @@ defaults._set('global', { display: false, fontStyle: 'bold', fullWidth: true, - lineHeight: 1.2, padding: 10, position: 'top', text: '', @@ -111,14 +110,12 @@ var Title = Element.extend({ beforeFit: noop, fit: function() { var me = this; - var valueOrDefault = helpers.valueOrDefault; var opts = me.options; var display = opts.display; - var fontSize = valueOrDefault(opts.fontSize, defaults.global.defaultFontSize); var minSize = me.minSize; var lineCount = helpers.isArray(opts.text) ? opts.text.length : 1; - var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize); - var textSize = display ? (lineCount * lineHeight) + (opts.padding * 2) : 0; + var fontOpts = helpers.options._parseFont(opts); + var textSize = display ? (lineCount * fontOpts.lineHeight) + (opts.padding * 2) : 0; if (me.isHorizontal()) { minSize.width = me.maxWidth; // fill all the width @@ -146,14 +143,10 @@ var Title = Element.extend({ var ctx = me.ctx; var valueOrDefault = helpers.valueOrDefault; var opts = me.options; - var globalDefaults = defaults.global; if (opts.display) { - var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize); - var fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle); - var fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily); - var titleFont = helpers.fontString(fontSize, fontStyle, fontFamily); - var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize); + var fontOpts = helpers.options._parseFont(opts); + var lineHeight = fontOpts.lineHeight; var offset = lineHeight / 2 + opts.padding; var rotation = 0; var top = me.top; @@ -162,8 +155,8 @@ var Title = Element.extend({ var right = me.right; var maxWidth, titleX, titleY; - ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour - ctx.font = titleFont; + ctx.fillStyle = valueOrDefault(opts.fontColor, defaults.global.defaultFontColor); // render in correct colour + ctx.font = fontOpts.string; // Horizontal if (me.isHorizontal()) { diff --git a/src/scales/scale.radialLinear.js b/src/scales/scale.radialLinear.js index 4a76a773c40..f0c38317e8a 100644 --- a/src/scales/scale.radialLinear.js +++ b/src/scales/scale.radialLinear.js @@ -7,8 +7,6 @@ var Ticks = require('../core/core.ticks'); module.exports = function(Chart) { - var globalDefaults = defaults.global; - var defaultConfig = { display: true, @@ -64,42 +62,26 @@ module.exports = function(Chart) { return opts.angleLines.display || opts.pointLabels.display ? scale.chart.data.labels.length : 0; } - function getPointLabelFontOptions(scale) { - var pointLabelOptions = scale.options.pointLabels; - var fontSize = helpers.valueOrDefault(pointLabelOptions.fontSize, globalDefaults.defaultFontSize); - var fontStyle = helpers.valueOrDefault(pointLabelOptions.fontStyle, globalDefaults.defaultFontStyle); - var fontFamily = helpers.valueOrDefault(pointLabelOptions.fontFamily, globalDefaults.defaultFontFamily); - var font = helpers.fontString(fontSize, fontStyle, fontFamily); - - return { - size: fontSize, - style: fontStyle, - family: fontFamily, - font: font - }; - } - - function getTickFontSize(scale) { - var opts = scale.options; + function getTickBackdropHeight(opts) { var tickOpts = opts.ticks; if (tickOpts.display && opts.display) { - return helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); + return helpers.valueOrDefault(tickOpts.fontSize, defaults.global.defaultFontSize) + tickOpts.backdropPaddingY * 2; } return 0; } - function measureLabelSize(ctx, fontSize, label) { + function measureLabelSize(ctx, lineHeight, label) { if (helpers.isArray(label)) { return { w: helpers.longestText(ctx, ctx.font, label), - h: (label.length * fontSize) + ((label.length - 1) * 1.5 * fontSize) + h: label.length * lineHeight }; } return { w: ctx.measureText(label).width, - h: fontSize + h: lineHeight }; } @@ -111,14 +93,14 @@ module.exports = function(Chart) { }; } else if (angle < min || angle > max) { return { - start: pos - size - 5, + start: pos - size, end: pos }; } return { start: pos, - end: pos + size + 5 + end: pos + size }; } @@ -154,28 +136,26 @@ module.exports = function(Chart) { * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif */ - var plFont = getPointLabelFontOptions(scale); - var paddingTop = getTickFontSize(scale) / 2; + var plFont = helpers.options._parseFont(scale.options.pointLabels); // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points - var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2); var furthestLimits = { - r: scale.width, l: 0, - t: scale.height, - b: 0 + r: scale.width, + t: 0, + b: scale.height }; var furthestAngles = {}; var i, textSize, pointPosition; - scale.ctx.font = plFont.font; + scale.ctx.font = plFont.string; scale._pointLabelSizes = []; var valueCount = getValueCount(scale); for (i = 0; i < valueCount; i++) { - pointPosition = scale.getPointPosition(i, largestPossibleRadius); - textSize = measureLabelSize(scale.ctx, plFont.size, scale.pointLabels[i] || ''); + pointPosition = scale.getPointPosition(i, scale.drawingArea + 5); + textSize = measureLabelSize(scale.ctx, plFont.lineHeight, scale.pointLabels[i] || ''); scale._pointLabelSizes[i] = textSize; // Add quarter circle to make degree 0 mean top of circle @@ -205,22 +185,7 @@ module.exports = function(Chart) { } } - if (paddingTop && -paddingTop < furthestLimits.t) { - furthestLimits.t = -paddingTop; - furthestAngles.t = 0; - } - - scale.setReductions(largestPossibleRadius, furthestLimits, furthestAngles); - } - - /** - * Helper function to fit a radial linear scale with no point labels - */ - function fit(scale) { - var paddingTop = getTickFontSize(scale) / 2; - var largestPossibleRadius = Math.min((scale.height - paddingTop) / 2, scale.width / 2); - scale.drawingArea = Math.floor(largestPossibleRadius); - scale.setCenterPoint(0, 0, paddingTop, 0); + scale.setReductions(scale.drawingArea, furthestLimits, furthestAngles); } function getTextAlignForAngle(angle) { @@ -233,17 +198,17 @@ module.exports = function(Chart) { return 'right'; } - function fillText(ctx, text, position, fontSize) { - if (helpers.isArray(text)) { - var y = position.y; - var spacing = 1.5 * fontSize; + function fillText(ctx, text, position, lineHeight) { + var y = position.y + lineHeight / 2; + var i, ilen; - for (var i = 0; i < text.length; ++i) { + if (helpers.isArray(text)) { + for (i = 0, ilen = text.length; i < ilen; ++i) { ctx.fillText(text[i], position.x, y); - y += spacing; + y += lineHeight; } } else { - ctx.fillText(text, position.x, position.y); + ctx.fillText(text, position.x, y); } } @@ -263,6 +228,7 @@ module.exports = function(Chart) { var pointLabelOpts = opts.pointLabels; var lineWidth = helpers.valueOrDefault(angleLineOpts.lineWidth, gridLineOpts.lineWidth); var lineColor = helpers.valueOrDefault(angleLineOpts.color, gridLineOpts.color); + var tickBackdropHeight = getTickBackdropHeight(opts); ctx.save(); ctx.lineWidth = lineWidth; @@ -275,10 +241,10 @@ module.exports = function(Chart) { var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max); // Point Label Font - var plFont = getPointLabelFontOptions(scale); + var plFont = helpers.options._parseFont(pointLabelOpts); - ctx.font = plFont.font; - ctx.textBaseline = 'top'; + ctx.font = plFont.string; + ctx.textBaseline = 'middle'; for (var i = getValueCount(scale) - 1; i >= 0; i--) { if (angleLineOpts.display && lineWidth && lineColor) { @@ -290,18 +256,19 @@ module.exports = function(Chart) { } if (pointLabelOpts.display) { - // Extra 3px out for some label spacing - var pointLabelPosition = scale.getPointPosition(i, outerDistance + 5); + // Extra pixels out for some label spacing + var extra = (i === 0 ? tickBackdropHeight / 2 : 0); + var pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + 5); // Keep this in loop since we may support array properties here - var pointLabelFontColor = helpers.valueAtIndexOrDefault(pointLabelOpts.fontColor, i, globalDefaults.defaultFontColor); + var pointLabelFontColor = helpers.valueAtIndexOrDefault(pointLabelOpts.fontColor, i, defaults.global.defaultFontColor); ctx.fillStyle = pointLabelFontColor; var angleRadians = scale.getIndexAngle(i); var angle = helpers.toDegrees(angleRadians); ctx.textAlign = getTextAlignForAngle(angle); adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition); - fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.size); + fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.lineHeight); } } ctx.restore(); @@ -353,17 +320,14 @@ module.exports = function(Chart) { var LinearRadialScale = Chart.LinearScaleBase.extend({ setDimensions: function() { var me = this; - var opts = me.options; - var tickOpts = opts.ticks; + // Set the unconstrained dimension before label rotation me.width = me.maxWidth; me.height = me.maxHeight; + me.paddingTop = getTickBackdropHeight(me.options) / 2; me.xCenter = Math.floor(me.width / 2); - me.yCenter = Math.floor(me.height / 2); - - var minSize = helpers.min([me.height, me.width]); - var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); - me.drawingArea = opts.display ? (minSize / 2) - (tickFontSize / 2 + tickOpts.backdropPaddingY) : (minSize / 2); + me.yCenter = Math.floor((me.height - me.paddingTop) / 2); + me.drawingArea = Math.min(me.height - me.paddingTop, me.width) / 2; }, determineDataLimits: function() { var me = this; @@ -394,9 +358,10 @@ module.exports = function(Chart) { me.handleTickRangeOptions(); }, getTickLimit: function() { - var tickOpts = this.options.ticks; - var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); - return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize))); + var opts = this.options; + var tickOpts = opts.ticks; + var tickBackdropHeight = getTickBackdropHeight(opts); + return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / tickBackdropHeight)); }, convertTicksToLabels: function() { var me = this; @@ -410,10 +375,13 @@ module.exports = function(Chart) { return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); }, fit: function() { - if (this.options.pointLabels.display) { - fitWithPointLabels(this); + var me = this; + var opts = me.options; + + if (opts.display && opts.pointLabels.display) { + fitWithPointLabels(me); } else { - fit(this); + me.setCenterPoint(0, 0, 0, 0); } }, /** @@ -425,7 +393,7 @@ module.exports = function(Chart) { var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l); var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r); var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t); - var radiusReductionBottom = -Math.max(furthestLimits.b - me.height, 0) / Math.cos(furthestAngles.b); + var radiusReductionBottom = -Math.max(furthestLimits.b - (me.height - me.paddingTop), 0) / Math.cos(furthestAngles.b); radiusReductionLeft = numberOrZero(radiusReductionLeft); radiusReductionRight = numberOrZero(radiusReductionRight); @@ -442,10 +410,10 @@ module.exports = function(Chart) { var maxRight = me.width - rightMovement - me.drawingArea; var maxLeft = leftMovement + me.drawingArea; var maxTop = topMovement + me.drawingArea; - var maxBottom = me.height - bottomMovement - me.drawingArea; + var maxBottom = (me.height - me.paddingTop) - bottomMovement - me.drawingArea; me.xCenter = Math.floor(((maxLeft + maxRight) / 2) + me.left); - me.yCenter = Math.floor(((maxTop + maxBottom) / 2) + me.top); + me.yCenter = Math.floor(((maxTop + maxBottom) / 2) + me.top + me.paddingTop); }, getIndexAngle: function(index) { @@ -507,12 +475,7 @@ module.exports = function(Chart) { if (opts.display) { var ctx = me.ctx; var startAngle = this.getIndexAngle(0); - - // Tick Font - var tickFontSize = valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); - var tickFontStyle = valueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle); - var tickFontFamily = valueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily); - var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); + var tickFont = helpers.options._parseFont(tickOpts); if (opts.angleLines.display || opts.pointLabels.display) { drawPointLabels(me); @@ -529,8 +492,8 @@ module.exports = function(Chart) { } if (tickOpts.display) { - var tickFontColor = valueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor); - ctx.font = tickLabelFont; + var tickFontColor = valueOrDefault(tickOpts.fontColor, defaults.global.defaultFontColor); + ctx.font = tickFont.string; ctx.save(); ctx.translate(me.xCenter, me.yCenter); @@ -541,9 +504,9 @@ module.exports = function(Chart) { ctx.fillStyle = tickOpts.backdropColor; ctx.fillRect( -labelWidth / 2 - tickOpts.backdropPaddingX, - -yCenterOffset - tickFontSize / 2 - tickOpts.backdropPaddingY, + -yCenterOffset - tickFont.size / 2 - tickOpts.backdropPaddingY, labelWidth + tickOpts.backdropPaddingX * 2, - tickFontSize + tickOpts.backdropPaddingY * 2 + tickFont.size + tickOpts.backdropPaddingY * 2 ); } diff --git a/test/fixtures/controller.radar/point-style.png b/test/fixtures/controller.radar/point-style.png index c64bf330755..723fdb82647 100644 Binary files a/test/fixtures/controller.radar/point-style.png and b/test/fixtures/controller.radar/point-style.png differ diff --git a/test/fixtures/plugin.filler/fill-radar-boundary-origin-spline.png b/test/fixtures/plugin.filler/fill-radar-boundary-origin-spline.png index 5896239e986..bff65ad3c30 100644 Binary files a/test/fixtures/plugin.filler/fill-radar-boundary-origin-spline.png and b/test/fixtures/plugin.filler/fill-radar-boundary-origin-spline.png differ diff --git a/test/fixtures/plugin.filler/fill-radar-boundary-origin.png b/test/fixtures/plugin.filler/fill-radar-boundary-origin.png index 66c6e563a79..82b1f60478d 100644 Binary files a/test/fixtures/plugin.filler/fill-radar-boundary-origin.png and b/test/fixtures/plugin.filler/fill-radar-boundary-origin.png differ diff --git a/test/specs/controller.radar.tests.js b/test/specs/controller.radar.tests.js index a013274bb78..a911f93020e 100644 --- a/test/specs/controller.radar.tests.js +++ b/test/specs/controller.radar.tests.js @@ -131,10 +131,10 @@ describe('Chart.controllers.radar', function() { })); [ - {x: 256, y: 256, cppx: 256, cppy: 256, cpnx: 256, cpny: 256}, - {x: 256, y: 256, cppx: 256, cppy: 256, cpnx: 256, cpny: 256}, - {x: 256, y: 256, cppx: 256, cppy: 256, cpnx: 256, cpny: 256}, - {x: 256, y: 256, cppx: 256, cppy: 256, cpnx: 256, cpny: 256}, + {x: 256, y: 260, cppx: 256, cppy: 260, cpnx: 256, cpny: 260}, + {x: 256, y: 260, cppx: 256, cppy: 260, cpnx: 256, cpny: 260}, + {x: 256, y: 260, cppx: 256, cppy: 260, cpnx: 256, cpny: 260}, + {x: 256, y: 260, cppx: 256, cppy: 260, cpnx: 256, cpny: 260}, ].forEach(function(expected, i) { expect(meta.data[i]._model.x).toBeCloseToPixel(expected.x); expect(meta.data[i]._model.y).toBeCloseToPixel(expected.y); @@ -158,10 +158,10 @@ describe('Chart.controllers.radar', function() { meta.controller.update(); [ - {x: 256, y: 117, cppx: 246, cppy: 117, cpnx: 272, cpny: 117}, - {x: 464, y: 256, cppx: 464, cppy: 248, cpnx: 464, cpny: 262}, - {x: 256, y: 256, cppx: 277, cppy: 256, cpnx: 250, cpny: 256}, - {x: 200, y: 256, cppx: 200, cppy: 259, cpnx: 200, cpny: 245}, + {x: 256, y: 120, cppx: 246, cppy: 120, cpnx: 272, cpny: 120}, + {x: 464, y: 260, cppx: 464, cppy: 252, cpnx: 464, cpny: 266}, + {x: 256, y: 260, cppx: 277, cppy: 260, cpnx: 250, cpny: 260}, + {x: 200, y: 260, cppx: 200, cppy: 264, cpnx: 200, cpny: 250}, ].forEach(function(expected, i) { expect(meta.data[i]._model.x).toBeCloseToPixel(expected.x); expect(meta.data[i]._model.y).toBeCloseToPixel(expected.y); @@ -215,10 +215,10 @@ describe('Chart.controllers.radar', function() { // Since tension is now 0, we don't care about the control points [ - {x: 256, y: 117}, - {x: 464, y: 256}, - {x: 256, y: 256}, - {x: 200, y: 256}, + {x: 256, y: 120}, + {x: 464, y: 260}, + {x: 256, y: 260}, + {x: 200, y: 260}, ].forEach(function(expected, i) { expect(meta.data[i]._model.x).toBeCloseToPixel(expected.x); expect(meta.data[i]._model.y).toBeCloseToPixel(expected.y); @@ -274,11 +274,11 @@ describe('Chart.controllers.radar', function() { })); expect(meta.data[0]._model.x).toBeCloseToPixel(256); - expect(meta.data[0]._model.y).toBeCloseToPixel(117); + expect(meta.data[0]._model.y).toBeCloseToPixel(120); expect(meta.data[0]._model.controlPointPreviousX).toBeCloseToPixel(241); - expect(meta.data[0]._model.controlPointPreviousY).toBeCloseToPixel(117); + expect(meta.data[0]._model.controlPointPreviousY).toBeCloseToPixel(120); expect(meta.data[0]._model.controlPointNextX).toBeCloseToPixel(281); - expect(meta.data[0]._model.controlPointNextY).toBeCloseToPixel(117); + expect(meta.data[0]._model.controlPointNextY).toBeCloseToPixel(120); expect(meta.data[0]._model).toEqual(jasmine.objectContaining({ radius: 2.2, backgroundColor: 'rgb(0, 1, 3)', diff --git a/test/specs/core.controller.tests.js b/test/specs/core.controller.tests.js index cf9eb30a22b..443cceace57 100644 --- a/test/specs/core.controller.tests.js +++ b/test/specs/core.controller.tests.js @@ -761,16 +761,16 @@ describe('Chart', function() { // then we will reset and see that they moved expect(meta.data[0]._model.y).toBeCloseToPixel(333); expect(meta.data[1]._model.y).toBeCloseToPixel(183); - expect(meta.data[2]._model.y).toBe(32); - expect(meta.data[3]._model.y).toBe(484); + expect(meta.data[2]._model.y).toBeCloseToPixel(32); + expect(meta.data[3]._model.y).toBeCloseToPixel(482); chart.reset(); // For a line chart, the animation state is the bottom - expect(meta.data[0]._model.y).toBe(484); - expect(meta.data[1]._model.y).toBe(484); - expect(meta.data[2]._model.y).toBe(484); - expect(meta.data[3]._model.y).toBe(484); + expect(meta.data[0]._model.y).toBeCloseToPixel(482); + expect(meta.data[1]._model.y).toBeCloseToPixel(482); + expect(meta.data[2]._model.y).toBeCloseToPixel(482); + expect(meta.data[3]._model.y).toBeCloseToPixel(482); }); }); diff --git a/test/specs/core.scale.tests.js b/test/specs/core.scale.tests.js index 35ef3d6babb..4cfa753831e 100644 --- a/test/specs/core.scale.tests.js +++ b/test/specs/core.scale.tests.js @@ -9,9 +9,6 @@ describe('Core.scale', function() { // actual label labelString: '', - // actual label - lineHeight: 1.2, - // top/bottom padding padding: { top: 4, diff --git a/test/specs/helpers.options.tests.js b/test/specs/helpers.options.tests.js index 3188888cb12..1afbd59bd45 100644 --- a/test/specs/helpers.options.tests.js +++ b/test/specs/helpers.options.tests.js @@ -4,122 +4,197 @@ describe('Chart.helpers.options', function() { var options = Chart.helpers.options; describe('toLineHeight', function() { + var toLineHeight = options.toLineHeight; + it ('should support keyword values', function() { - expect(options.toLineHeight('normal', 16)).toBe(16 * 1.2); + expect(toLineHeight('normal', 16)).toBe(16 * 1.2); }); it ('should support unitless values', function() { - expect(options.toLineHeight(1.4, 16)).toBe(16 * 1.4); - expect(options.toLineHeight('1.4', 16)).toBe(16 * 1.4); + expect(toLineHeight(1.4, 16)).toBe(16 * 1.4); + expect(toLineHeight('1.4', 16)).toBe(16 * 1.4); }); it ('should support length values', function() { - expect(options.toLineHeight('42px', 16)).toBe(42); - expect(options.toLineHeight('1.4em', 16)).toBe(16 * 1.4); + expect(toLineHeight('42px', 16)).toBe(42); + expect(toLineHeight('1.4em', 16)).toBe(16 * 1.4); }); it ('should support percentage values', function() { - expect(options.toLineHeight('140%', 16)).toBe(16 * 1.4); + expect(toLineHeight('140%', 16)).toBe(16 * 1.4); }); it ('should fallback to default (1.2) for invalid values', function() { - expect(options.toLineHeight(null, 16)).toBe(16 * 1.2); - expect(options.toLineHeight(undefined, 16)).toBe(16 * 1.2); - expect(options.toLineHeight('foobar', 16)).toBe(16 * 1.2); + expect(toLineHeight(null, 16)).toBe(16 * 1.2); + expect(toLineHeight(undefined, 16)).toBe(16 * 1.2); + expect(toLineHeight('foobar', 16)).toBe(16 * 1.2); }); }); describe('toPadding', function() { + var toPadding = options.toPadding; + it ('should support number values', function() { - expect(options.toPadding(4)).toEqual( + expect(toPadding(4)).toEqual( {top: 4, right: 4, bottom: 4, left: 4, height: 8, width: 8}); - expect(options.toPadding(4.5)).toEqual( + expect(toPadding(4.5)).toEqual( {top: 4.5, right: 4.5, bottom: 4.5, left: 4.5, height: 9, width: 9}); }); it ('should support string values', function() { - expect(options.toPadding('4')).toEqual( + expect(toPadding('4')).toEqual( {top: 4, right: 4, bottom: 4, left: 4, height: 8, width: 8}); - expect(options.toPadding('4.5')).toEqual( + expect(toPadding('4.5')).toEqual( {top: 4.5, right: 4.5, bottom: 4.5, left: 4.5, height: 9, width: 9}); }); it ('should support object values', function() { - expect(options.toPadding({top: 1, right: 2, bottom: 3, left: 4})).toEqual( + expect(toPadding({top: 1, right: 2, bottom: 3, left: 4})).toEqual( {top: 1, right: 2, bottom: 3, left: 4, height: 4, width: 6}); - expect(options.toPadding({top: 1.5, right: 2.5, bottom: 3.5, left: 4.5})).toEqual( + expect(toPadding({top: 1.5, right: 2.5, bottom: 3.5, left: 4.5})).toEqual( {top: 1.5, right: 2.5, bottom: 3.5, left: 4.5, height: 5, width: 7}); - expect(options.toPadding({top: '1', right: '2', bottom: '3', left: '4'})).toEqual( + expect(toPadding({top: '1', right: '2', bottom: '3', left: '4'})).toEqual( {top: 1, right: 2, bottom: 3, left: 4, height: 4, width: 6}); }); it ('should fallback to 0 for invalid values', function() { - expect(options.toPadding({top: 'foo', right: 'foo', bottom: 'foo', left: 'foo'})).toEqual( + expect(toPadding({top: 'foo', right: 'foo', bottom: 'foo', left: 'foo'})).toEqual( {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0}); - expect(options.toPadding({top: null, right: null, bottom: null, left: null})).toEqual( + expect(toPadding({top: null, right: null, bottom: null, left: null})).toEqual( {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0}); - expect(options.toPadding({})).toEqual( + expect(toPadding({})).toEqual( {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0}); - expect(options.toPadding('foo')).toEqual( + expect(toPadding('foo')).toEqual( {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0}); - expect(options.toPadding(null)).toEqual( + expect(toPadding(null)).toEqual( {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0}); - expect(options.toPadding(undefined)).toEqual( + expect(toPadding(undefined)).toEqual( {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0}); }); }); + describe('_parseFont', function() { + var parseFont = options._parseFont; + + it ('should return a font with default values', function() { + var global = Chart.defaults.global; + + Chart.defaults.global = { + defaultFontFamily: 'foobar', + defaultFontSize: 42, + defaultFontStyle: 'xxxyyy', + defaultLineHeight: 1.5 + }; + + expect(parseFont({})).toEqual({ + family: 'foobar', + lineHeight: 63, + size: 42, + string: 'xxxyyy 42px foobar', + style: 'xxxyyy', + weight: null + }); + + Chart.defaults.global = global; + }); + it ('should return a font with given values', function() { + expect(parseFont({ + fontFamily: 'bla', + lineHeight: 8, + fontSize: 21, + fontStyle: 'zzz' + })).toEqual({ + family: 'bla', + lineHeight: 8 * 21, + size: 21, + string: 'zzz 21px bla', + style: 'zzz', + weight: null + }); + }); + it('should return null as a font string if fontSize or fontFamily are missing', function() { + var global = Chart.defaults.global; + + Chart.defaults.global = {}; + + expect(parseFont({ + fontStyle: 'italic', + fontSize: 12 + }).string).toBeNull(); + expect(parseFont({ + fontStyle: 'italic', + fontFamily: 'serif' + }).string).toBeNull(); + + Chart.defaults.global = global; + }); + it('fontStyle should be optional for font strings', function() { + var global = Chart.defaults.global; + + Chart.defaults.global = {}; + + expect(parseFont({ + fontSize: 12, + fontFamily: 'serif' + }).string).toBe('12px serif'); + + Chart.defaults.global = global; + }); + }); + describe('resolve', function() { + var resolve = options.resolve; + it ('should fallback to the first defined input', function() { - expect(options.resolve([42])).toBe(42); - expect(options.resolve([42, 'foo'])).toBe(42); - expect(options.resolve([undefined, 42, 'foo'])).toBe(42); - expect(options.resolve([42, 'foo', undefined])).toBe(42); - expect(options.resolve([undefined])).toBe(undefined); + expect(resolve([42])).toBe(42); + expect(resolve([42, 'foo'])).toBe(42); + expect(resolve([undefined, 42, 'foo'])).toBe(42); + expect(resolve([42, 'foo', undefined])).toBe(42); + expect(resolve([undefined])).toBe(undefined); }); it ('should correctly handle empty values (null, 0, "")', function() { - expect(options.resolve([0, 'foo'])).toBe(0); - expect(options.resolve(['', 'foo'])).toBe(''); - expect(options.resolve([null, 'foo'])).toBe(null); + expect(resolve([0, 'foo'])).toBe(0); + expect(resolve(['', 'foo'])).toBe(''); + expect(resolve([null, 'foo'])).toBe(null); }); it ('should support indexable options if index is provided', function() { var input = [42, 'foo', 'bar']; - expect(options.resolve([input], undefined, 0)).toBe(42); - expect(options.resolve([input], undefined, 1)).toBe('foo'); - expect(options.resolve([input], undefined, 2)).toBe('bar'); + expect(resolve([input], undefined, 0)).toBe(42); + expect(resolve([input], undefined, 1)).toBe('foo'); + expect(resolve([input], undefined, 2)).toBe('bar'); }); it ('should fallback if an indexable option value is undefined', function() { var input = [42, undefined, 'bar']; - expect(options.resolve([input], undefined, 5)).toBe(undefined); - expect(options.resolve([input, 'foo'], undefined, 1)).toBe('foo'); - expect(options.resolve([input, 'foo'], undefined, 5)).toBe('foo'); + expect(resolve([input], undefined, 5)).toBe(undefined); + expect(resolve([input, 'foo'], undefined, 1)).toBe('foo'); + expect(resolve([input, 'foo'], undefined, 5)).toBe('foo'); }); it ('should not handle indexable options if index is undefined', function() { var array = [42, 'foo', 'bar']; - expect(options.resolve([array])).toBe(array); - expect(options.resolve([array], undefined, undefined)).toBe(array); + expect(resolve([array])).toBe(array); + expect(resolve([array], undefined, undefined)).toBe(array); }); it ('should support scriptable options if context is provided', function() { var input = function(context) { return context.v * 2; }; - expect(options.resolve([42], {v: 42})).toBe(42); - expect(options.resolve([input], {v: 42})).toBe(84); + expect(resolve([42], {v: 42})).toBe(42); + expect(resolve([input], {v: 42})).toBe(84); }); it ('should fallback if a scriptable option returns undefined', function() { var input = function() {}; - expect(options.resolve([input], {v: 42})).toBe(undefined); - expect(options.resolve([input, 'foo'], {v: 42})).toBe('foo'); - expect(options.resolve([input, undefined, 'foo'], {v: 42})).toBe('foo'); + expect(resolve([input], {v: 42})).toBe(undefined); + expect(resolve([input, 'foo'], {v: 42})).toBe('foo'); + expect(resolve([input, undefined, 'foo'], {v: 42})).toBe('foo'); }); it ('should not handle scriptable options if context is undefined', function() { var input = function(context) { return context.v * 2; }; - expect(options.resolve([input])).toBe(input); - expect(options.resolve([input], undefined)).toBe(input); + expect(resolve([input])).toBe(input); + expect(resolve([input], undefined)).toBe(input); }); it ('should handle scriptable and indexable option', function() { var input = function(context) { return [context.v, undefined, 'bar']; }; - expect(options.resolve([input, 'foo'], {v: 42}, 0)).toBe(42); - expect(options.resolve([input, 'foo'], {v: 42}, 1)).toBe('foo'); - expect(options.resolve([input, 'foo'], {v: 42}, 5)).toBe('foo'); - expect(options.resolve([input, ['foo', 'bar']], {v: 42}, 1)).toBe('bar'); + expect(resolve([input, 'foo'], {v: 42}, 0)).toBe(42); + expect(resolve([input, 'foo'], {v: 42}, 1)).toBe('foo'); + expect(resolve([input, 'foo'], {v: 42}, 5)).toBe('foo'); + expect(resolve([input, ['foo', 'bar']], {v: 42}, 1)).toBe('bar'); }); }); }); diff --git a/test/specs/plugin.legend.tests.js b/test/specs/plugin.legend.tests.js index 7b7159dbb10..48a0a0e98d0 100644 --- a/test/specs/plugin.legend.tests.js +++ b/test/specs/plugin.legend.tests.js @@ -494,7 +494,7 @@ describe('Legend block tests', function() { expect(chart.legend.left).toBeCloseToPixel(0); expect(chart.legend.top).toBeCloseToPixel(6); expect(chart.legend.width).toBeCloseToPixel(128); - expect(chart.legend.height).toBeCloseToPixel(478); + expect(chart.legend.height).toBeCloseToPixel(476); expect(chart.legend.legendHitBoxes.length).toBe(22); [ diff --git a/test/specs/plugin.title.tests.js b/test/specs/plugin.title.tests.js index 28786f05402..4f00ee8925b 100644 --- a/test/specs/plugin.title.tests.js +++ b/test/specs/plugin.title.tests.js @@ -8,7 +8,6 @@ describe('Title block tests', function() { fullWidth: true, weight: 2000, fontStyle: 'bold', - lineHeight: 1.2, padding: 10, text: '' }); diff --git a/test/specs/scale.category.tests.js b/test/specs/scale.category.tests.js index 0ce39e5b33c..7bc6a8a57ad 100644 --- a/test/specs/scale.category.tests.js +++ b/test/specs/scale.category.tests.js @@ -340,8 +340,8 @@ describe('Category scale tests', function() { expect(yScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(77); expect(yScale.getValueForPixel(77)).toBe(0); - expect(yScale.getPixelForValue(0, 4, 0)).toBeCloseToPixel(439); - expect(yScale.getValueForPixel(439)).toBe(4); + expect(yScale.getPixelForValue(0, 4, 0)).toBeCloseToPixel(437); + expect(yScale.getValueForPixel(437)).toBe(4); }); it ('should get the correct pixel for a value when vertical and zoomed', function() { @@ -385,6 +385,6 @@ describe('Category scale tests', function() { chart.update(); expect(yScale.getPixelForValue(0, 1, 0)).toBeCloseToPixel(107); - expect(yScale.getPixelForValue(0, 3, 0)).toBeCloseToPixel(409); + expect(yScale.getPixelForValue(0, 3, 0)).toBeCloseToPixel(407); }); }); diff --git a/test/specs/scale.linear.tests.js b/test/specs/scale.linear.tests.js index 9ec21116e88..ad35e2a4347 100644 --- a/test/specs/scale.linear.tests.js +++ b/test/specs/scale.linear.tests.js @@ -817,9 +817,9 @@ describe('Linear Scale', function() { expect(yScale.getPixelForValue(-1, 0, 0)).toBeCloseToPixel(484); // left + paddingLeft expect(yScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(258); // halfway*/ - expect(yScale.getValueForPixel(32)).toBe(1); - expect(yScale.getValueForPixel(484)).toBe(-1); - expect(yScale.getValueForPixel(258)).toBe(0); + expect(yScale.getValueForPixel(32)).toBeCloseTo(1, 1e-2); + expect(yScale.getValueForPixel(484)).toBeCloseTo(-1, 1e-2); + expect(yScale.getValueForPixel(258)).toBeCloseTo(0, 1e-2); }); it('should fit correctly', function() { @@ -865,7 +865,7 @@ describe('Linear Scale', function() { expect(xScale.paddingLeft).toBeCloseToPixel(0); expect(xScale.paddingRight).toBeCloseToPixel(0); expect(xScale.width).toBeCloseToPixel(468 - 6); // minus lineSpace - expect(xScale.height).toBeCloseToPixel(28); + expect(xScale.height).toBeCloseToPixel(30); var yScale = chart.scales.yScale0; expect(yScale.paddingTop).toBeCloseToPixel(0); @@ -873,7 +873,7 @@ describe('Linear Scale', function() { expect(yScale.paddingLeft).toBeCloseToPixel(0); expect(yScale.paddingRight).toBeCloseToPixel(0); expect(yScale.width).toBeCloseToPixel(30 + 6); // plus lineSpace - expect(yScale.height).toBeCloseToPixel(452); + expect(yScale.height).toBeCloseToPixel(450); // Extra size when scale label showing xScale.options.scaleLabel.display = true; @@ -885,14 +885,14 @@ describe('Linear Scale', function() { expect(xScale.paddingLeft).toBeCloseToPixel(0); expect(xScale.paddingRight).toBeCloseToPixel(0); expect(xScale.width).toBeCloseToPixel(440); - expect(xScale.height).toBeCloseToPixel(50); + expect(xScale.height).toBeCloseToPixel(53); expect(yScale.paddingTop).toBeCloseToPixel(0); expect(yScale.paddingBottom).toBeCloseToPixel(0); expect(yScale.paddingLeft).toBeCloseToPixel(0); expect(yScale.paddingRight).toBeCloseToPixel(0); expect(yScale.width).toBeCloseToPixel(58); - expect(yScale.height).toBeCloseToPixel(430); + expect(yScale.height).toBeCloseToPixel(427); }); it('should fit correctly when display is turned off', function() { diff --git a/test/specs/scale.radialLinear.tests.js b/test/specs/scale.radialLinear.tests.js index 5459eac311f..49ef34b46d6 100644 --- a/test/specs/scale.radialLinear.tests.js +++ b/test/specs/scale.radialLinear.tests.js @@ -349,9 +349,9 @@ describe('Test the radial linear scale', function() { } }); - expect(chart.scale.drawingArea).toBe(232); + expect(chart.scale.drawingArea).toBe(227); expect(chart.scale.xCenter).toBe(256); - expect(chart.scale.yCenter).toBe(279); + expect(chart.scale.yCenter).toBe(284); }); it('should correctly get the label for a given data index', function() { @@ -397,16 +397,16 @@ describe('Test the radial linear scale', function() { }); expect(chart.scale.getDistanceFromCenterForValue(chart.scale.min)).toBe(0); - expect(chart.scale.getDistanceFromCenterForValue(chart.scale.max)).toBe(232); + expect(chart.scale.getDistanceFromCenterForValue(chart.scale.max)).toBe(227); var position = chart.scale.getPointPositionForValue(1, 5); expect(position.x).toBeCloseToPixel(270); - expect(position.y).toBeCloseToPixel(275); + expect(position.y).toBeCloseToPixel(278); chart.scale.options.ticks.reverse = true; chart.update(); - expect(chart.scale.getDistanceFromCenterForValue(chart.scale.min)).toBe(232); + expect(chart.scale.getDistanceFromCenterForValue(chart.scale.min)).toBe(227); expect(chart.scale.getDistanceFromCenterForValue(chart.scale.max)).toBe(0); });