From 6535e7ff4829168b33a62b483eda909225bc94d1 Mon Sep 17 00:00:00 2001 From: Akihiko Kusanagi Date: Sat, 15 Dec 2018 07:05:46 +0800 Subject: [PATCH 1/4] Add scale.pointLabels.lineHeight and scale.ticks.lineHeight options --- docs/axes/radial/linear.md | 1 + docs/axes/styling.md | 3 + src/core/core.js | 1 + src/core/core.scale.js | 71 +++------- src/helpers/helpers.options.js | 16 +++ src/plugins/plugin.legend.js | 21 ++- src/plugins/plugin.title.js | 21 ++- src/scales/scale.radialLinear.js | 133 +++++++----------- .../fixtures/controller.radar/point-style.png | Bin 7679 -> 7679 bytes .../fill-radar-boundary-origin-spline.png | Bin 10472 -> 11449 bytes .../fill-radar-boundary-origin.png | Bin 9297 -> 10129 bytes test/specs/controller.radar.tests.js | 30 ++-- test/specs/core.controller.tests.js | 12 +- test/specs/core.scale.tests.js | 3 - test/specs/plugin.legend.tests.js | 2 +- test/specs/plugin.title.tests.js | 1 - test/specs/scale.category.tests.js | 6 +- test/specs/scale.linear.tests.js | 14 +- test/specs/scale.radialLinear.tests.js | 10 +- 19 files changed, 145 insertions(+), 200 deletions(-) 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.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..e4abbb1f716 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -5,6 +5,8 @@ var Element = require('./core.element'); var helpers = require('../helpers/index'); var Ticks = require('./core.ticks'); +var globalDefaults = defaults.global; + defaults._set('scale', { display: true, position: 'left', @@ -36,9 +38,6 @@ defaults._set('scale', { // actual label labelString: '', - // line height - lineHeight: 1.2, - // top/bottom padding padding: { top: 4, @@ -99,27 +98,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,7 +319,7 @@ 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); + var tickFont = helpers.options.parseFontOptions(tickOpts, globalDefaults); context.font = tickFont.font; var labelRotation = tickOpts.minRotation || 0; @@ -400,7 +378,7 @@ module.exports = Element.extend({ var position = opts.position; var isHorizontal = me.isHorizontal(); - var tickFont = parseFontOptions(tickOpts); + var tickFont = helpers.options.parseFontOptions(tickOpts, globalDefaults); var tickMarkLength = opts.gridLines.tickMarkLength; // Width @@ -420,9 +398,9 @@ module.exports = Element.extend({ // Are we showing a title for the scale? if (scaleLabelOpts.display && display) { - var scaleLabelLineHeight = parseLineHeight(scaleLabelOpts); + var scaleLabelFont = helpers.options.parseFontOptions(scaleLabelOpts, globalDefaults); var scaleLabelPadding = helpers.options.toPadding(scaleLabelOpts.padding); - var deltaHeight = scaleLabelLineHeight + scaleLabelPadding.height; + var deltaHeight = scaleLabelFont.lineHeight + scaleLabelPadding.height; if (isHorizontal) { minSize.height += deltaHeight; @@ -448,8 +426,7 @@ 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); @@ -689,7 +666,6 @@ module.exports = Element.extend({ var chart = me.chart; var context = me.ctx; - var globalDefaults = defaults.global; var optionTicks = options.ticks.minor; var optionMajorTicks = options.ticks.major || optionTicks; var gridLines = options.gridLines; @@ -702,16 +678,16 @@ module.exports = Element.extend({ var ticks = optionTicks.autoSkip ? me._autoSkip(me.getTicks()) : me.getTicks(); var tickFontColor = helpers.valueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor); - var tickFont = parseFontOptions(optionTicks); + var tickFont = helpers.options.parseFontOptions(optionTicks, globalDefaults); var majorTickFontColor = helpers.valueOrDefault(optionMajorTicks.fontColor, globalDefaults.defaultFontColor); - var majorTickFont = parseFontOptions(optionMajorTicks); + var majorTickFont = helpers.options.parseFontOptions(optionMajorTicks, globalDefaults); 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 scaleLabelFont = helpers.options.parseFontOptions(scaleLabel, globalDefaults); var scaleLabelPadding = helpers.options.toPadding(scaleLabel.padding); var labelRotationRadians = helpers.toRadians(me.labelRotation); @@ -763,8 +739,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 +758,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) * tickFont.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) * tickFont.lineHeight; textAlign = !isRotated ? 'center' : 'right'; labelY = me.top + labelYOffset; } @@ -803,6 +779,7 @@ module.exports = Element.extend({ tx2 = tickEnd; ty1 = ty2 = y1 = y2 = alignPixel(chart, lineValue, lineWidth); labelY = me.getPixelForTick(index) + labelOffset; + textOffset = (1 - labelCount) * tickFont.lineHeight / 2; if (position === 'left') { x1 = alignPixel(chart, chartArea.left, axisWidth) + axisWidth / 2; @@ -835,7 +812,7 @@ module.exports = Element.extend({ rotation: -1 * labelRotationRadians, label: label, major: tick.major, - textBaseline: textBaseline, + textOffset: textOffset, textAlign: textAlign }); }); @@ -877,23 +854,19 @@ module.exports = Element.extend({ context.rotate(itemToDraw.rotation); context.font = itemToDraw.major ? majorTickFont.font : tickFont.font; 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; + y += tickFont.lineHeight; } } else { - context.fillText(label, 0, 0); + context.fillText(label, 0, y); } context.restore(); } @@ -904,7 +877,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 diff --git a/src/helpers/helpers.options.js b/src/helpers/helpers.options.js index 8e6c0aadfae..c2092311730 100644 --- a/src/helpers/helpers.options.js +++ b/src/helpers/helpers.options.js @@ -65,6 +65,22 @@ module.exports = { }; }, + parseFontOptions: function(options, globalDefaults) { + var valueOrDefault = helpers.valueOrDefault; + var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize); + var style = valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle); + var family = valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily); + var lineHeight = valueOrDefault(options.lineHeight, globalDefaults.defaultlineHeight); + + return { + size: size, + style: style, + family: family, + font: helpers.fontString(size, style, family), + lineHeight: this.toLineHeight(lineHeight, size) + }; + }, + /** * 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..666cc834fc4 100644 --- a/src/plugins/plugin.legend.js +++ b/src/plugins/plugin.legend.js @@ -7,6 +7,8 @@ var layouts = require('../core/core.layouts'); var noop = helpers.noop; +var globalDefault = defaults.global; + defaults._set('global', { legend: { display: true, @@ -211,12 +213,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.parseFontOptions(labelOpts, globalDefault); + var fontSize = labelFont.size; // Reset hit boxes var hitboxes = me.legendHitBoxes = []; @@ -234,7 +232,7 @@ var Legend = Element.extend({ // Increase sizes here if (display) { - ctx.font = labelFont; + ctx.font = labelFont.font; if (isHorizontal) { // Labels @@ -323,7 +321,6 @@ 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 legendWidth = me.width; var lineWidths = me.lineWidths; @@ -332,10 +329,8 @@ var Legend = Element.extend({ 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 labelFont = helpers.options.parseFontOptions(labelOpts, globalDefault); + 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.font; var boxWidth = getBoxWidth(labelOpts, fontSize); var hitboxes = me.legendHitBoxes; diff --git a/src/plugins/plugin.title.js b/src/plugins/plugin.title.js index 47588844d4c..46e94100aa4 100644 --- a/src/plugins/plugin.title.js +++ b/src/plugins/plugin.title.js @@ -7,12 +7,13 @@ var layouts = require('../core/core.layouts'); var noop = helpers.noop; +var globalDefaults = defaults.global; + defaults._set('global', { title: { display: false, fontStyle: 'bold', fullWidth: true, - lineHeight: 1.2, padding: 10, position: 'top', text: '', @@ -111,14 +112,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.parseFontOptions(opts, globalDefaults); + var textSize = display ? (lineCount * fontOpts.lineHeight) + (opts.padding * 2) : 0; if (me.isHorizontal()) { minSize.width = me.maxWidth; // fill all the width @@ -146,14 +145,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.parseFontOptions(opts, globalDefaults); + var lineHeight = fontOpts.lineHeight; var offset = lineHeight / 2 + opts.padding; var rotation = 0; var top = me.top; @@ -163,7 +158,7 @@ var Title = Element.extend({ var maxWidth, titleX, titleY; ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour - ctx.font = titleFont; + ctx.font = fontOpts.font; // Horizontal if (me.isHorizontal()) { @@ -236,7 +231,7 @@ module.exports = { var titleBlock = chart.titleBlock; if (titleOpts) { - helpers.mergeIf(titleOpts, defaults.global.title); + helpers.mergeIf(titleOpts, globalDefaults.title); if (titleBlock) { layouts.configure(chart, titleBlock, titleOpts); diff --git a/src/scales/scale.radialLinear.js b/src/scales/scale.radialLinear.js index 4a76a773c40..63a1f5f10f0 100644 --- a/src/scales/scale.radialLinear.js +++ b/src/scales/scale.radialLinear.js @@ -64,42 +64,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, globalDefaults.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 +95,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,17 +138,15 @@ 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.parseFontOptions(scale.options.pointLabels, globalDefaults); // 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; @@ -174,8 +156,8 @@ module.exports = function(Chart) { 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 +187,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 +200,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 +230,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 +243,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.parseFontOptions(pointLabelOpts, globalDefaults); ctx.font = plFont.font; - ctx.textBaseline = 'top'; + ctx.textBaseline = 'middle'; for (var i = getValueCount(scale) - 1; i >= 0; i--) { if (angleLineOpts.display && lineWidth && lineColor) { @@ -290,8 +258,9 @@ 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); @@ -301,7 +270,7 @@ module.exports = function(Chart) { 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 +322,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 +360,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 +377,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 +395,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 +412,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 +477,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.parseFontOptions(tickOpts, globalDefaults); if (opts.angleLines.display || opts.pointLabels.display) { drawPointLabels(me); @@ -530,7 +495,7 @@ module.exports = function(Chart) { if (tickOpts.display) { var tickFontColor = valueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor); - ctx.font = tickLabelFont; + ctx.font = tickFont.font; ctx.save(); ctx.translate(me.xCenter, me.yCenter); @@ -541,9 +506,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 c64bf330755377abdca1aac7fc18d0c94a5269fa..723fdb826472b2dbc18f6de68d96c2a8e9dd7a7c 100644 GIT binary patch literal 7679 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 B7!Cjc 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 5896239e98686b193446f4c036873ce533d95e44..bff65ad3c309f813325eae06c3a5d2202d1da717 100644 GIT binary patch literal 11449 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 PNd~-;Q+-t_V;1~BTAtcf literal 10472 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 IUKSGmKWDH5cK`qY diff --git a/test/fixtures/plugin.filler/fill-radar-boundary-origin.png b/test/fixtures/plugin.filler/fill-radar-boundary-origin.png index 66c6e563a796d1c50039329dafff1e339a841f53..82b1f60478d1e0f83d5900986b43d400d906a1d9 100644 GIT binary patch literal 10129 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 literal 9297 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 X^#@Wqt2ppeAwc7yu3EK Date: Sun, 16 Dec 2018 08:36:43 +0800 Subject: [PATCH 2/4] Add helpers.options.toFontString and helpers.options._parseFont --- src/core/core.defaults.js | 2 +- src/core/core.scale.js | 49 ++++---- src/elements/element.line.js | 7 +- src/elements/element.point.js | 4 +- src/elements/element.rectangle.js | 6 +- src/helpers/helpers.options.js | 45 ++++++-- src/plugins/plugin.legend.js | 20 ++-- src/plugins/plugin.title.js | 12 +- src/scales/scale.radialLinear.js | 20 ++-- test/specs/helpers.options.tests.js | 170 ++++++++++++++++++++-------- 10 files changed, 218 insertions(+), 117 deletions(-) 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.scale.js b/src/core/core.scale.js index e4abbb1f716..c538de50a27 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -5,8 +5,6 @@ var Element = require('./core.element'); var helpers = require('../helpers/index'); var Ticks = require('./core.ticks'); -var globalDefaults = defaults.global; - defaults._set('scale', { display: true, position: 'left', @@ -319,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 = helpers.options.parseFontOptions(tickOpts, globalDefaults); - 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; @@ -378,7 +376,8 @@ module.exports = Element.extend({ var position = opts.position; var isHorizontal = me.isHorizontal(); - var tickFont = helpers.options.parseFontOptions(tickOpts, globalDefaults); + var parseFont = helpers.options._parseFont; + var tickFont = parseFont(tickOpts); var tickMarkLength = opts.gridLines.tickMarkLength; // Width @@ -398,7 +397,7 @@ module.exports = Element.extend({ // Are we showing a title for the scale? if (scaleLabelOpts.display && display) { - var scaleLabelFont = helpers.options.parseFontOptions(scaleLabelOpts, globalDefaults); + var scaleLabelFont = parseFont(scaleLabelOpts); var scaleLabelPadding = helpers.options.toPadding(scaleLabelOpts.padding); var deltaHeight = scaleLabelFont.lineHeight + scaleLabelPadding.height; @@ -411,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; @@ -431,9 +430,9 @@ module.exports = Element.extend({ 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; @@ -666,6 +665,8 @@ 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; @@ -676,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 = helpers.options.parseFontOptions(optionTicks, globalDefaults); - var majorTickFontColor = helpers.valueOrDefault(optionMajorTicks.fontColor, globalDefaults.defaultFontColor); - var majorTickFont = helpers.options.parseFontOptions(optionMajorTicks, globalDefaults); + 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 = helpers.options.parseFontOptions(scaleLabel, globalDefaults); + var scaleLabelFontColor = helpers.valueOrDefault(scaleLabel.fontColor, defaultFontColor); + var scaleLabelFont = parseFont(scaleLabel); var scaleLabelPadding = helpers.options.toPadding(scaleLabel.padding); var labelRotationRadians = helpers.toRadians(me.labelRotation); @@ -758,13 +761,13 @@ module.exports = Element.extend({ if (position === 'top') { y1 = alignPixel(chart, chartArea.top, axisWidth) + axisWidth / 2; y2 = chartArea.bottom; - textOffset = ((!isRotated ? 0.5 : 1) - labelCount) * tickFont.lineHeight; + 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; - textOffset = (!isRotated ? 0.5 : 0) * tickFont.lineHeight; + textOffset = (!isRotated ? 0.5 : 0) * lineHeight; textAlign = !isRotated ? 'center' : 'right'; labelY = me.top + labelYOffset; } @@ -779,7 +782,7 @@ module.exports = Element.extend({ tx2 = tickEnd; ty1 = ty2 = y1 = y2 = alignPixel(chart, lineValue, lineWidth); labelY = me.getPixelForTick(index) + labelOffset; - textOffset = (1 - labelCount) * tickFont.lineHeight / 2; + textOffset = (1 - labelCount) * lineHeight / 2; if (position === 'left') { x1 = alignPixel(chart, chartArea.left, axisWidth) + axisWidth / 2; @@ -852,7 +855,7 @@ 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 = 'middle'; context.textAlign = itemToDraw.textAlign; @@ -863,7 +866,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 += tickFont.lineHeight; + y += lineHeight; } } else { context.fillText(label, 0, y); @@ -899,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 c2092311730..1bce95192f6 100644 --- a/src/helpers/helpers.options.js +++ b/src/helpers/helpers.options.js @@ -1,5 +1,6 @@ 'use strict'; +var defaults = require('../core/core.defaults'); var helpers = require('./helpers.core'); /** @@ -65,20 +66,44 @@ module.exports = { }; }, - parseFontOptions: function(options, globalDefaults) { + /** + * 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 + */ + toFontString: function(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; + }, + + /** + * 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 style = valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle); - var family = valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily); - var lineHeight = valueOrDefault(options.lineHeight, globalDefaults.defaultlineHeight); - - return { + var font = { + family: valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily), + lineHeight: helpers.options.toLineHeight(valueOrDefault(options.lineHeight, globalDefaults.defaultLineHeight), size), size: size, - style: style, - family: family, - font: helpers.fontString(size, style, family), - lineHeight: this.toLineHeight(lineHeight, size) + style: valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle), + weight: null, + string: '' }; + + font.string = helpers.options.toFontString(font); + return font; }, /** diff --git a/src/plugins/plugin.legend.js b/src/plugins/plugin.legend.js index 666cc834fc4..d258fcd38ae 100644 --- a/src/plugins/plugin.legend.js +++ b/src/plugins/plugin.legend.js @@ -7,8 +7,6 @@ var layouts = require('../core/core.layouts'); var noop = helpers.noop; -var globalDefault = defaults.global; - defaults._set('global', { legend: { display: true, @@ -213,7 +211,7 @@ var Legend = Element.extend({ var ctx = me.ctx; - var labelFont = helpers.options.parseFontOptions(labelOpts, globalDefault); + var labelFont = helpers.options._parseFont(labelOpts); var fontSize = labelFont.size; // Reset hit boxes @@ -232,7 +230,7 @@ var Legend = Element.extend({ // Increase sizes here if (display) { - ctx.font = labelFont.font; + ctx.font = labelFont.string; if (isHorizontal) { // Labels @@ -321,15 +319,17 @@ var Legend = Element.extend({ var me = this; var opts = me.options; var labelOpts = opts.labels; - 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 labelFont = helpers.options.parseFontOptions(labelOpts, globalDefault); + var fontColor = valueOrDefault(labelOpts.fontColor, globalDefaults.defaultFontColor); + var labelFont = helpers.options._parseFont(labelOpts); var fontSize = labelFont.size; var cursor; @@ -339,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.font; + ctx.font = labelFont.string; var boxWidth = getBoxWidth(labelOpts, fontSize); var hitboxes = me.legendHitBoxes; @@ -353,12 +353,12 @@ var Legend = Element.extend({ // Set the ctx for the box ctx.save(); - ctx.fillStyle = valueOrDefault(legendItem.fillStyle, globalDefault.defaultColor); + 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); + ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, defaultColor); var isLineWidthZero = (valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth) === 0); if (ctx.setLineDash) { diff --git a/src/plugins/plugin.title.js b/src/plugins/plugin.title.js index 46e94100aa4..eb09aa0e203 100644 --- a/src/plugins/plugin.title.js +++ b/src/plugins/plugin.title.js @@ -7,8 +7,6 @@ var layouts = require('../core/core.layouts'); var noop = helpers.noop; -var globalDefaults = defaults.global; - defaults._set('global', { title: { display: false, @@ -116,7 +114,7 @@ var Title = Element.extend({ var display = opts.display; var minSize = me.minSize; var lineCount = helpers.isArray(opts.text) ? opts.text.length : 1; - var fontOpts = helpers.options.parseFontOptions(opts, globalDefaults); + var fontOpts = helpers.options._parseFont(opts); var textSize = display ? (lineCount * fontOpts.lineHeight) + (opts.padding * 2) : 0; if (me.isHorizontal()) { @@ -147,7 +145,7 @@ var Title = Element.extend({ var opts = me.options; if (opts.display) { - var fontOpts = helpers.options.parseFontOptions(opts, globalDefaults); + var fontOpts = helpers.options._parseFont(opts); var lineHeight = fontOpts.lineHeight; var offset = lineHeight / 2 + opts.padding; var rotation = 0; @@ -157,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 = fontOpts.font; + ctx.fillStyle = valueOrDefault(opts.fontColor, defaults.global.defaultFontColor); // render in correct colour + ctx.font = fontOpts.string; // Horizontal if (me.isHorizontal()) { @@ -231,7 +229,7 @@ module.exports = { var titleBlock = chart.titleBlock; if (titleOpts) { - helpers.mergeIf(titleOpts, globalDefaults.title); + helpers.mergeIf(titleOpts, defaults.global.title); if (titleBlock) { layouts.configure(chart, titleBlock, titleOpts); diff --git a/src/scales/scale.radialLinear.js b/src/scales/scale.radialLinear.js index 63a1f5f10f0..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, @@ -68,7 +66,7 @@ module.exports = function(Chart) { var tickOpts = opts.ticks; if (tickOpts.display && opts.display) { - return helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize) + tickOpts.backdropPaddingY * 2; + return helpers.valueOrDefault(tickOpts.fontSize, defaults.global.defaultFontSize) + tickOpts.backdropPaddingY * 2; } return 0; } @@ -138,7 +136,7 @@ module.exports = function(Chart) { * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif */ - var plFont = helpers.options.parseFontOptions(scale.options.pointLabels, globalDefaults); + 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 @@ -151,7 +149,7 @@ module.exports = function(Chart) { var furthestAngles = {}; var i, textSize, pointPosition; - scale.ctx.font = plFont.font; + scale.ctx.font = plFont.string; scale._pointLabelSizes = []; var valueCount = getValueCount(scale); @@ -243,9 +241,9 @@ module.exports = function(Chart) { var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max); // Point Label Font - var plFont = helpers.options.parseFontOptions(pointLabelOpts, globalDefaults); + var plFont = helpers.options._parseFont(pointLabelOpts); - ctx.font = plFont.font; + ctx.font = plFont.string; ctx.textBaseline = 'middle'; for (var i = getValueCount(scale) - 1; i >= 0; i--) { @@ -263,7 +261,7 @@ module.exports = function(Chart) { 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); @@ -477,7 +475,7 @@ module.exports = function(Chart) { if (opts.display) { var ctx = me.ctx; var startAngle = this.getIndexAngle(0); - var tickFont = helpers.options.parseFontOptions(tickOpts, globalDefaults); + var tickFont = helpers.options._parseFont(tickOpts); if (opts.angleLines.display || opts.pointLabels.display) { drawPointLabels(me); @@ -494,8 +492,8 @@ module.exports = function(Chart) { } if (tickOpts.display) { - var tickFontColor = valueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor); - ctx.font = tickFont.font; + var tickFontColor = valueOrDefault(tickOpts.fontColor, defaults.global.defaultFontColor); + ctx.font = tickFont.string; ctx.save(); ctx.translate(me.xCenter, me.yCenter); diff --git a/test/specs/helpers.options.tests.js b/test/specs/helpers.options.tests.js index 3188888cb12..b459aa73ead 100644 --- a/test/specs/helpers.options.tests.js +++ b/test/specs/helpers.options.tests.js @@ -4,122 +4,194 @@ 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('toFontString', function() { + var toFontString = options.toFontString; + + it('should return null if the given font is invalid', function() { + expect(toFontString({})).toBeNull(); + expect(toFontString(null)).toBeNull(); + expect(toFontString(undefined)).toBeNull(); + expect(toFontString(42)).toBeNull(); + expect(toFontString('foo')).toBeNull(); + expect(toFontString(new Date())).toBeNull(); + }); + it('should return null if size or family are missing', function() { + expect(toFontString({style: 'italic', weight: 300, size: 12})).toBeNull(); + expect(toFontString({style: 'italic', weight: 300, family: 'serif'})).toBeNull(); + }); + it('should return the string representation of the given font', function() { + expect(toFontString({style: 'italic', weight: 300, size: 12, family: 'serif'})).toBe('italic 300 12px serif'); + }); + it('weigth and style should be optional', function() { + expect(toFontString({size: 12, family: 'serif'})).toBe('12px serif'); + expect(toFontString({style: 'italic', size: 12, family: 'serif'})).toBe('italic 12px serif'); + expect(toFontString({weight: 300, size: 12, family: 'serif'})).toBe('300 12px serif'); + }); + }); + + 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 + }); + }); + }); + 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'); }); }); }); From b6f1d94aa30c340a6915db3e769e0cf5997bb087 Mon Sep 17 00:00:00 2001 From: Akihiko Kusanagi Date: Tue, 18 Dec 2018 20:39:29 +0900 Subject: [PATCH 3/4] Move toFontString outside the export to make it fully private --- src/helpers/helpers.options.js | 35 ++++++++++--------- test/specs/helpers.options.tests.js | 53 +++++++++++++++-------------- 2 files changed, 46 insertions(+), 42 deletions(-) diff --git a/src/helpers/helpers.options.js b/src/helpers/helpers.options.js index 1bce95192f6..0b107e40d84 100644 --- a/src/helpers/helpers.options.js +++ b/src/helpers/helpers.options.js @@ -3,6 +3,23 @@ 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 @@ -66,22 +83,6 @@ module.exports = { }; }, - /** - * 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 - */ - toFontString: function(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; - }, - /** * Parses font options and returns the font object. * @param {Object} options - A object that contains font opttons to be parsed. @@ -102,7 +103,7 @@ module.exports = { string: '' }; - font.string = helpers.options.toFontString(font); + font.string = toFontString(font); return font; }, diff --git a/test/specs/helpers.options.tests.js b/test/specs/helpers.options.tests.js index b459aa73ead..1afbd59bd45 100644 --- a/test/specs/helpers.options.tests.js +++ b/test/specs/helpers.options.tests.js @@ -66,31 +66,6 @@ describe('Chart.helpers.options', function() { }); }); - describe('toFontString', function() { - var toFontString = options.toFontString; - - it('should return null if the given font is invalid', function() { - expect(toFontString({})).toBeNull(); - expect(toFontString(null)).toBeNull(); - expect(toFontString(undefined)).toBeNull(); - expect(toFontString(42)).toBeNull(); - expect(toFontString('foo')).toBeNull(); - expect(toFontString(new Date())).toBeNull(); - }); - it('should return null if size or family are missing', function() { - expect(toFontString({style: 'italic', weight: 300, size: 12})).toBeNull(); - expect(toFontString({style: 'italic', weight: 300, family: 'serif'})).toBeNull(); - }); - it('should return the string representation of the given font', function() { - expect(toFontString({style: 'italic', weight: 300, size: 12, family: 'serif'})).toBe('italic 300 12px serif'); - }); - it('weigth and style should be optional', function() { - expect(toFontString({size: 12, family: 'serif'})).toBe('12px serif'); - expect(toFontString({style: 'italic', size: 12, family: 'serif'})).toBe('italic 12px serif'); - expect(toFontString({weight: 300, size: 12, family: 'serif'})).toBe('300 12px serif'); - }); - }); - describe('_parseFont', function() { var parseFont = options._parseFont; @@ -130,6 +105,34 @@ describe('Chart.helpers.options', function() { 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() { From 47808329ac23320a32ad82aaba5957c77fe27690 Mon Sep 17 00:00:00 2001 From: Akihiko Kusanagi Date: Thu, 20 Dec 2018 10:13:32 +0900 Subject: [PATCH 4/4] Remove code duplication --- src/plugins/plugin.legend.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/plugin.legend.js b/src/plugins/plugin.legend.js index d258fcd38ae..f11b823ea58 100644 --- a/src/plugins/plugin.legend.js +++ b/src/plugins/plugin.legend.js @@ -353,13 +353,13 @@ var Legend = Element.extend({ // Set the ctx for the box ctx.save(); + 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.lineWidth = lineWidth; ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, defaultColor); - var isLineWidthZero = (valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth) === 0); if (ctx.setLineDash) { // IE 9 and 10 do not support line dash @@ -378,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);