From 9bd9c92158a8898318e663a798abadf43a01dea4 Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Thu, 20 Jul 2017 19:20:54 +0200 Subject: [PATCH] Add support for line height CSS values (#4531) The title plugin and scale title now accept lineHeight specified using unitless value (1.4), length ('1.4em' or '12px'), percentage ('200%') or keyword ('normal' === 1.2). The line height parsing has been refactored under the 'Chart.helpers.options' namespace. Also fix incorrect text positioning in the title plugin. https://developer.mozilla.org/en-US/docs/Web/CSS/line-height --- docs/axes/labelling.md | 2 +- docs/configuration/title.md | 2 +- src/core/core.scale.js | 16 +++++++++--- src/helpers/helpers.options.js | 35 ++++++++++++++++++++++++++ src/helpers/index.js | 1 + src/plugins/plugin.title.js | 10 +++++--- test/specs/core.helpers.tests.js | 6 +++-- test/specs/helpers.options.tests.js | 27 ++++++++++++++++++++ test/specs/plugin.title.tests.js | 15 +++++------ test/specs/scale.category.tests.js | 3 ++- test/specs/scale.linear.tests.js | 14 ++++++----- test/specs/scale.logarithmic.tests.js | 3 ++- test/specs/scale.radialLinear.tests.js | 3 ++- test/specs/scale.time.tests.js | 3 ++- 14 files changed, 111 insertions(+), 29 deletions(-) create mode 100644 src/helpers/helpers.options.js create mode 100644 test/specs/helpers.options.tests.js diff --git a/docs/axes/labelling.md b/docs/axes/labelling.md index 5e4a6a76b87..22fc2600463 100644 --- a/docs/axes/labelling.md +++ b/docs/axes/labelling.md @@ -10,7 +10,7 @@ The scale label configuration is nested under the scale configuration in the `sc | -----| ---- | --------| ----------- | `display` | `Boolean` | `false` | If true, display the axis title. | `labelString` | `String` | `''` | The text for the title. (i.e. "# of People" or "Response Choices"). -| `lineHeight` | `Number` | `` | Height of an individual line of text. If not defined, the font size is used. +| `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)) | `fontColor` | Color | `'#666'` | Font color for scale title. | `fontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family for the scale title, follows CSS font-family options. | `fontSize` | `Number` | `12` | Font size for scale title. diff --git a/docs/configuration/title.md b/docs/configuration/title.md index 3952f2ca64f..b2c04cd8fbe 100644 --- a/docs/configuration/title.md +++ b/docs/configuration/title.md @@ -14,7 +14,7 @@ The title configuration is passed into the `options.title` namespace. The global | `fontColor` | Color | `'#666'` | Font color | `fontStyle` | `String` | `'bold'` | Font style | `padding` | `Number` | `10` | Number of pixels to add above and below the title text. -| `lineHeight` | `Number` | `undefined` | Height of line of text. If not specified, the `fontSize` is used. +| `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)) | `text` | `String/String[]` | `''` | Title text to display. If specified as an array, text is rendered on multiple lines. ### Position diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 9626c3f0e6c..942e4fff6b9 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -28,11 +28,13 @@ defaults._set('scale', { // scale label scaleLabel: { + // display property + display: false, + // actual label labelString: '', - // display property - display: false, + lineHeight: 1.2 }, // label settings @@ -77,6 +79,12 @@ module.exports = function(Chart) { }; } + function parseLineHeight(options) { + return helpers.options.toLineHeight( + helpers.valueOrDefault(options.lineHeight, 1.2), + helpers.valueOrDefault(options.fontSize, defaults.global.defaultFontSize)); + } + Chart.Scale = Chart.Element.extend({ /** * Get the padding needed for the scale @@ -310,8 +318,8 @@ module.exports = function(Chart) { var isHorizontal = me.isHorizontal(); var tickFont = parseFontOptions(tickOpts); - var scaleLabelLineHeight = helpers.valueOrDefault(scaleLabelOpts.lineHeight, parseFontOptions(scaleLabelOpts).size * 1.5); var tickMarkLength = opts.gridLines.tickMarkLength; + var scaleLabelLineHeight = parseLineHeight(scaleLabelOpts); // Width if (isHorizontal) { @@ -738,7 +746,7 @@ module.exports = function(Chart) { var scaleLabelX; var scaleLabelY; var rotation = 0; - var halfLineHeight = helpers.valueOrDefault(scaleLabel.lineHeight, scaleLabelFont.size) / 2; + var halfLineHeight = parseLineHeight(scaleLabel) / 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 new file mode 100644 index 00000000000..1aab9bb9660 --- /dev/null +++ b/src/helpers/helpers.options.js @@ -0,0 +1,35 @@ +'use strict'; + +/** + * @namespace Chart.helpers.options + */ +module.exports = { + /** + * Converts the given line height `value` in pixels for a specific font `size`. + * @param {Number|String} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em'). + * @param {Number} size - The font size (in pixels) used to resolve relative `value`. + * @returns {Number} The effective line height in pixels (size * 1.2 if value is invalid). + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height + * @since 2.7.0 + */ + toLineHeight: function(value, size) { + var matches = (''+value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/); + if (!matches || matches[1] === 'normal') { + return size * 1.2; + } + + value = parseFloat(matches[2]); + + switch (matches[3]) { + case 'px': + return value; + case '%': + value /= 100; + break; + default: + break; + } + + return size * value; + } +}; diff --git a/src/helpers/index.js b/src/helpers/index.js index a21c99aca13..632b772076a 100644 --- a/src/helpers/index.js +++ b/src/helpers/index.js @@ -3,4 +3,5 @@ module.exports = require('./helpers.core'); module.exports.easing = require('./helpers.easing'); module.exports.canvas = require('./helpers.canvas'); +module.exports.options = require('./helpers.options'); module.exports.time = require('./helpers.time'); diff --git a/src/plugins/plugin.title.js b/src/plugins/plugin.title.js index 30f8813d92a..259863b62ac 100644 --- a/src/plugins/plugin.title.js +++ b/src/plugins/plugin.title.js @@ -8,6 +8,7 @@ defaults._set('global', { display: false, fontStyle: 'bold', fullWidth: true, + lineHeight: 1.2, padding: 10, position: 'top', text: '', @@ -114,7 +115,7 @@ module.exports = function(Chart) { fontSize = valueOrDefault(opts.fontSize, defaults.global.defaultFontSize), minSize = me.minSize, lineCount = helpers.isArray(opts.text) ? opts.text.length : 1, - lineHeight = valueOrDefault(opts.lineHeight, fontSize), + lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize), textSize = display ? (lineCount * lineHeight) + (opts.padding * 2) : 0; if (me.isHorizontal()) { @@ -150,7 +151,8 @@ module.exports = function(Chart) { fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle), fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily), titleFont = helpers.fontString(fontSize, fontStyle, fontFamily), - lineHeight = valueOrDefault(opts.lineHeight, fontSize), + lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize), + offset = lineHeight/2 + opts.padding, rotation = 0, titleX, titleY, @@ -166,10 +168,10 @@ module.exports = function(Chart) { // Horizontal if (me.isHorizontal()) { titleX = left + ((right - left) / 2); // midpoint of the width - titleY = top + ((bottom - top) / 2); // midpoint of the height + titleY = top + offset; maxWidth = right - left; } else { - titleX = opts.position === 'left' ? left + (fontSize / 2) : right - (fontSize / 2); + titleX = opts.position === 'left' ? left + offset : right - offset; titleY = top + ((bottom - top) / 2); maxWidth = bottom - top; rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5); diff --git a/test/specs/core.helpers.tests.js b/test/specs/core.helpers.tests.js index d0182cc6c7c..e6f6264395a 100644 --- a/test/specs/core.helpers.tests.js +++ b/test/specs/core.helpers.tests.js @@ -128,8 +128,9 @@ describe('Core helper tests', function() { }, position: 'right', scaleLabel: { - labelString: '', display: false, + labelString: '', + lineHeight: 1.2 }, ticks: { beginAtZero: false, @@ -168,8 +169,9 @@ describe('Core helper tests', function() { }, position: 'left', scaleLabel: { - labelString: '', display: false, + labelString: '', + lineHeight: 1.2 }, ticks: { beginAtZero: false, diff --git a/test/specs/helpers.options.tests.js b/test/specs/helpers.options.tests.js new file mode 100644 index 00000000000..1b01159dce1 --- /dev/null +++ b/test/specs/helpers.options.tests.js @@ -0,0 +1,27 @@ +'use strict'; + +describe('Chart.helpers.options', function() { + var options = Chart.helpers.options; + + describe('toLineHeight', function() { + it ('should support keyword values', function() { + expect(options.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); + }); + it ('should support length values', function() { + expect(options.toLineHeight('42px', 16)).toBe(42); + expect(options.toLineHeight('1.4em', 16)).toBe(16 * 1.4); + }); + it ('should support percentage values', function() { + expect(options.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); + }); + }); +}); diff --git a/test/specs/plugin.title.tests.js b/test/specs/plugin.title.tests.js index 3c73b55d4bc..edeb32a8b47 100644 --- a/test/specs/plugin.title.tests.js +++ b/test/specs/plugin.title.tests.js @@ -13,6 +13,7 @@ describe('Title block tests', function() { fullWidth: true, weight: 2000, fontStyle: 'bold', + lineHeight: 1.2, padding: 10, text: '' }); @@ -43,7 +44,7 @@ describe('Title block tests', function() { expect(minSize).toEqual({ width: 400, - height: 32 + height: 34.4 }); }); @@ -72,7 +73,7 @@ describe('Title block tests', function() { minSize = title.update(200, 400); expect(minSize).toEqual({ - width: 32, + width: 34.4, height: 400 }); }); @@ -84,7 +85,7 @@ describe('Title block tests', function() { options.text = ['line1', 'line2']; options.position = 'left'; options.display = true; - options.lineHeight = 15; + options.lineHeight = 1.5; var title = new Chart.Title({ chart: chart, @@ -94,7 +95,7 @@ describe('Title block tests', function() { var minSize = title.update(200, 400); expect(minSize).toEqual({ - width: 50, + width: 56, height: 400 }); }); @@ -135,7 +136,7 @@ describe('Title block tests', function() { args: [] }, { name: 'translate', - args: [300, 66] + args: [300, 67.2] }, { name: 'rotate', args: [0] @@ -185,7 +186,7 @@ describe('Title block tests', function() { args: [] }, { name: 'translate', - args: [106, 250] + args: [117.2, 250] }, { name: 'rotate', args: [-0.5 * Math.PI] @@ -218,7 +219,7 @@ describe('Title block tests', function() { args: [] }, { name: 'translate', - args: [126, 250] + args: [117.2, 250] }, { name: 'rotate', args: [0.5 * Math.PI] diff --git a/test/specs/scale.category.tests.js b/test/specs/scale.category.tests.js index 31c0a4e6fee..c11f751962b 100644 --- a/test/specs/scale.category.tests.js +++ b/test/specs/scale.category.tests.js @@ -30,8 +30,9 @@ describe('Category scale tests', function() { }, position: 'bottom', scaleLabel: { + display: false, labelString: '', - display: false + lineHeight: 1.2 }, ticks: { beginAtZero: false, diff --git a/test/specs/scale.linear.tests.js b/test/specs/scale.linear.tests.js index 5b37a0c11f7..de653268d73 100644 --- a/test/specs/scale.linear.tests.js +++ b/test/specs/scale.linear.tests.js @@ -28,8 +28,9 @@ describe('Linear Scale', function() { }, position: 'left', scaleLabel: { - labelString: '', display: false, + labelString: '', + lineHeight: 1.2 }, ticks: { beginAtZero: false, @@ -772,15 +773,15 @@ describe('Linear Scale', function() { expect(xScale.paddingBottom).toBeCloseToPixel(0); expect(xScale.paddingLeft).toBeCloseToPixel(0); expect(xScale.paddingRight).toBeCloseToPixel(0); - expect(xScale.width).toBeCloseToPixel(450); - expect(xScale.height).toBeCloseToPixel(46); + expect(xScale.width).toBeCloseToPixel(454); + expect(xScale.height).toBeCloseToPixel(42); expect(yScale.paddingTop).toBeCloseToPixel(0); expect(yScale.paddingBottom).toBeCloseToPixel(0); expect(yScale.paddingLeft).toBeCloseToPixel(0); expect(yScale.paddingRight).toBeCloseToPixel(0); - expect(yScale.width).toBeCloseToPixel(48); - expect(yScale.height).toBeCloseToPixel(434); + expect(yScale.width).toBeCloseToPixel(44); + expect(yScale.height).toBeCloseToPixel(438); }); it('should fit correctly when display is turned off', function() { @@ -820,7 +821,8 @@ describe('Linear Scale', function() { drawBorder: false }, scaleLabel: { - display: false + display: false, + lineHeight: 1.2 }, ticks: { display: false, diff --git a/test/specs/scale.logarithmic.tests.js b/test/specs/scale.logarithmic.tests.js index 95287aa79a6..16ab3d7800d 100644 --- a/test/specs/scale.logarithmic.tests.js +++ b/test/specs/scale.logarithmic.tests.js @@ -27,8 +27,9 @@ describe('Logarithmic Scale tests', function() { }, position: 'left', scaleLabel: { - labelString: '', display: false, + labelString: '', + lineHeight: 1.2 }, ticks: { beginAtZero: false, diff --git a/test/specs/scale.radialLinear.tests.js b/test/specs/scale.radialLinear.tests.js index a8ac68ff13a..6164249d652 100644 --- a/test/specs/scale.radialLinear.tests.js +++ b/test/specs/scale.radialLinear.tests.js @@ -40,8 +40,9 @@ describe('Test the radial linear scale', function() { }, position: 'chartArea', scaleLabel: { - labelString: '', display: false, + labelString: '', + lineHeight: 1.2 }, ticks: { backdropColor: 'rgba(255,255,255,0.75)', diff --git a/test/specs/scale.time.tests.js b/test/specs/scale.time.tests.js index ff02bc8df13..c5ca74fa926 100755 --- a/test/specs/scale.time.tests.js +++ b/test/specs/scale.time.tests.js @@ -81,8 +81,9 @@ describe('Time scale tests', function() { }, position: 'bottom', scaleLabel: { + display: false, labelString: '', - display: false + lineHeight: 1.2 }, ticks: { beginAtZero: false,