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
This commit is contained in:
Simon Brunel 2017-07-20 19:20:54 +02:00 committed by GitHub
parent 5ad1b4ade7
commit 090196c07c
14 changed files with 111 additions and 29 deletions

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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;
}
};

View File

@ -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');

View File

@ -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);

View File

@ -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,

View File

@ -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);
});
});
});

View File

@ -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]

View File

@ -30,8 +30,9 @@ describe('Category scale tests', function() {
},
position: 'bottom',
scaleLabel: {
display: false,
labelString: '',
display: false
lineHeight: 1.2
},
ticks: {
beginAtZero: false,

View File

@ -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,

View File

@ -27,8 +27,9 @@ describe('Logarithmic Scale tests', function() {
},
position: 'left',
scaleLabel: {
labelString: '',
display: false,
labelString: '',
lineHeight: 1.2
},
ticks: {
beginAtZero: false,

View File

@ -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)',

View File

@ -81,8 +81,9 @@ describe('Time scale tests', function() {
},
position: 'bottom',
scaleLabel: {
display: false,
labelString: '',
display: false
lineHeight: 1.2
},
ticks: {
beginAtZero: false,