Skip to content

Commit 784c2dd

Browse files
nagixsimonbrunel
authored andcommitted
Add scale.pointLabels.lineHeight and scale.ticks.lineHeight options (chartjs#5914)
1 parent 0d8a44c commit 784c2dd

24 files changed

+328
-278
lines changed

docs/axes/radial/linear.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,4 @@ The following options are used to configure the point labels that are shown on t
109109
| `fontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family to use when rendering labels.
110110
| `fontSize` | `Number` | 10 | font size in pixels.
111111
| `fontStyle` | `String` | `'normal'` | Font style to use when rendering point labels.
112+
| `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)).

docs/axes/styling.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ The tick configuration is nested under the scale configuration in the `ticks` ke
3535
| `fontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family for the tick labels, follows CSS font-family options.
3636
| `fontSize` | `Number` | `12` | Font size for the tick labels.
3737
| `fontStyle` | `String` | `'normal'` | Font style for the tick labels, follows CSS font-style options (i.e. normal, italic, oblique, initial, inherit).
38+
| `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)).
3839
| `reverse` | `Boolean` | `false` | Reverses order of tick labels.
3940
| `minor` | `object` | `{}` | Minor ticks configuration. Omitted options are inherited from options above.
4041
| `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
5051
| `fontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family for the tick labels, follows CSS font-family options.
5152
| `fontSize` | `Number` | `12` | Font size for the tick labels.
5253
| `fontStyle` | `String` | `'normal'` | Font style for the tick labels, follows CSS font-style options (i.e. normal, italic, oblique, initial, inherit).
54+
| `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)).
5355

5456
## Major Tick Configuration
5557
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
6163
| `fontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family for the tick labels, follows CSS font-family options.
6264
| `fontSize` | `Number` | `12` | Font size for the tick labels.
6365
| `fontStyle` | `String` | `'normal'` | Font style for the tick labels, follows CSS font-style options (i.e. normal, italic, oblique, initial, inherit).
66+
| `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)).

src/core/core.defaults.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
var helpers = require('../helpers/index');
3+
var helpers = require('../helpers/helpers.core');
44

55
module.exports = {
66
/**

src/core/core.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ defaults._set('global', {
1919
defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
2020
defaultFontSize: 12,
2121
defaultFontStyle: 'normal',
22+
defaultLineHeight: 1.2,
2223
showLines: true,
2324

2425
// Element defaults defined in element extensions

src/core/core.scale.js

Lines changed: 34 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@ defaults._set('scale', {
3636
// actual label
3737
labelString: '',
3838

39-
// line height
40-
lineHeight: 1.2,
41-
4239
// top/bottom padding
4340
padding: {
4441
top: 4,
@@ -99,27 +96,6 @@ function computeTextSize(context, tick, font) {
9996
context.measureText(tick).width;
10097
}
10198

102-
function parseFontOptions(options) {
103-
var valueOrDefault = helpers.valueOrDefault;
104-
var globalDefaults = defaults.global;
105-
var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize);
106-
var style = valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle);
107-
var family = valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily);
108-
109-
return {
110-
size: size,
111-
style: style,
112-
family: family,
113-
font: helpers.fontString(size, style, family)
114-
};
115-
}
116-
117-
function parseLineHeight(options) {
118-
return helpers.options.toLineHeight(
119-
helpers.valueOrDefault(options.lineHeight, 1.2),
120-
helpers.valueOrDefault(options.fontSize, defaults.global.defaultFontSize));
121-
}
122-
12399
module.exports = Element.extend({
124100
/**
125101
* Get the padding needed for the scale
@@ -341,13 +317,13 @@ module.exports = Element.extend({
341317

342318
// Get the width of each grid by calculating the difference
343319
// between x offsets between 0 and 1.
344-
var tickFont = parseFontOptions(tickOpts);
345-
context.font = tickFont.font;
320+
var tickFont = helpers.options._parseFont(tickOpts);
321+
context.font = tickFont.string;
346322

347323
var labelRotation = tickOpts.minRotation || 0;
348324

349325
if (labels.length && me.options.display && me.isHorizontal()) {
350-
var originalLabelWidth = helpers.longestText(context, tickFont.font, labels, me.longestTextCache);
326+
var originalLabelWidth = helpers.longestText(context, tickFont.string, labels, me.longestTextCache);
351327
var labelWidth = originalLabelWidth;
352328
var cosRotation, sinRotation;
353329

@@ -400,7 +376,8 @@ module.exports = Element.extend({
400376
var position = opts.position;
401377
var isHorizontal = me.isHorizontal();
402378

403-
var tickFont = parseFontOptions(tickOpts);
379+
var parseFont = helpers.options._parseFont;
380+
var tickFont = parseFont(tickOpts);
404381
var tickMarkLength = opts.gridLines.tickMarkLength;
405382

406383
// Width
@@ -420,9 +397,9 @@ module.exports = Element.extend({
420397

421398
// Are we showing a title for the scale?
422399
if (scaleLabelOpts.display && display) {
423-
var scaleLabelLineHeight = parseLineHeight(scaleLabelOpts);
400+
var scaleLabelFont = parseFont(scaleLabelOpts);
424401
var scaleLabelPadding = helpers.options.toPadding(scaleLabelOpts.padding);
425-
var deltaHeight = scaleLabelLineHeight + scaleLabelPadding.height;
402+
var deltaHeight = scaleLabelFont.lineHeight + scaleLabelPadding.height;
426403

427404
if (isHorizontal) {
428405
minSize.height += deltaHeight;
@@ -433,7 +410,7 @@ module.exports = Element.extend({
433410

434411
// Don't bother fitting the ticks if we are not showing them
435412
if (tickOpts.display && display) {
436-
var largestTextWidth = helpers.longestText(me.ctx, tickFont.font, labels, me.longestTextCache);
413+
var largestTextWidth = helpers.longestText(me.ctx, tickFont.string, labels, me.longestTextCache);
437414
var tallestLabelHeightInLines = helpers.numberOfLabelLines(labels);
438415
var lineSpace = tickFont.size * 0.5;
439416
var tickPadding = me.options.ticks.padding;
@@ -448,15 +425,14 @@ module.exports = Element.extend({
448425

449426
// TODO - improve this calculation
450427
var labelHeight = (sinRotation * largestTextWidth)
451-
+ (tickFont.size * tallestLabelHeightInLines)
452-
+ (lineSpace * (tallestLabelHeightInLines - 1))
428+
+ (tickFont.lineHeight * tallestLabelHeightInLines)
453429
+ lineSpace; // padding
454430

455431
minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding);
456432

457-
me.ctx.font = tickFont.font;
458-
var firstLabelWidth = computeTextSize(me.ctx, labels[0], tickFont.font);
459-
var lastLabelWidth = computeTextSize(me.ctx, labels[labels.length - 1], tickFont.font);
433+
me.ctx.font = tickFont.string;
434+
var firstLabelWidth = computeTextSize(me.ctx, labels[0], tickFont.string);
435+
var lastLabelWidth = computeTextSize(me.ctx, labels[labels.length - 1], tickFont.string);
460436
var offsetLeft = me.getPixelForTick(0) - me.left;
461437
var offsetRight = me.right - me.getPixelForTick(labels.length - 1);
462438
var paddingLeft, paddingRight;
@@ -717,6 +693,7 @@ module.exports = Element.extend({
717693
var chart = me.chart;
718694
var context = me.ctx;
719695
var globalDefaults = defaults.global;
696+
var defaultFontColor = globalDefaults.defaultFontColor;
720697
var optionTicks = options.ticks.minor;
721698
var optionMajorTicks = options.ticks.major || optionTicks;
722699
var gridLines = options.gridLines;
@@ -727,18 +704,20 @@ module.exports = Element.extend({
727704
var isMirrored = optionTicks.mirror;
728705
var isHorizontal = me.isHorizontal();
729706

707+
var parseFont = helpers.options._parseFont;
730708
var ticks = optionTicks.autoSkip ? me._autoSkip(me.getTicks()) : me.getTicks();
731-
var tickFontColor = helpers.valueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor);
732-
var tickFont = parseFontOptions(optionTicks);
733-
var majorTickFontColor = helpers.valueOrDefault(optionMajorTicks.fontColor, globalDefaults.defaultFontColor);
734-
var majorTickFont = parseFontOptions(optionMajorTicks);
709+
var tickFontColor = helpers.valueOrDefault(optionTicks.fontColor, defaultFontColor);
710+
var tickFont = parseFont(optionTicks);
711+
var lineHeight = tickFont.lineHeight;
712+
var majorTickFontColor = helpers.valueOrDefault(optionMajorTicks.fontColor, defaultFontColor);
713+
var majorTickFont = parseFont(optionMajorTicks);
735714
var tickPadding = optionTicks.padding;
736715
var labelOffset = optionTicks.labelOffset;
737716

738717
var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0;
739718

740-
var scaleLabelFontColor = helpers.valueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor);
741-
var scaleLabelFont = parseFontOptions(scaleLabel);
719+
var scaleLabelFontColor = helpers.valueOrDefault(scaleLabel.fontColor, defaultFontColor);
720+
var scaleLabelFont = parseFont(scaleLabel);
742721
var scaleLabelPadding = helpers.options.toPadding(scaleLabel.padding);
743722
var labelRotationRadians = helpers.toRadians(me.labelRotation);
744723

@@ -790,8 +769,8 @@ module.exports = Element.extend({
790769
}
791770

792771
// Common properties
793-
var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY, textAlign;
794-
var textBaseline = 'middle';
772+
var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY, textOffset, textAlign;
773+
var labelCount = helpers.isArray(label) ? label.length : 1;
795774
var lineValue = getPixelForGridLine(me, index, gridLines.offsetGridLines);
796775

797776
if (isHorizontal) {
@@ -809,13 +788,13 @@ module.exports = Element.extend({
809788
if (position === 'top') {
810789
y1 = alignPixel(chart, chartArea.top, axisWidth) + axisWidth / 2;
811790
y2 = chartArea.bottom;
812-
textBaseline = !isRotated ? 'bottom' : 'middle';
791+
textOffset = ((!isRotated ? 0.5 : 1) - labelCount) * lineHeight;
813792
textAlign = !isRotated ? 'center' : 'left';
814793
labelY = me.bottom - labelYOffset;
815794
} else {
816795
y1 = chartArea.top;
817796
y2 = alignPixel(chart, chartArea.bottom, axisWidth) - axisWidth / 2;
818-
textBaseline = !isRotated ? 'top' : 'middle';
797+
textOffset = (!isRotated ? 0.5 : 0) * lineHeight;
819798
textAlign = !isRotated ? 'center' : 'right';
820799
labelY = me.top + labelYOffset;
821800
}
@@ -830,6 +809,7 @@ module.exports = Element.extend({
830809
tx2 = tickEnd;
831810
ty1 = ty2 = y1 = y2 = alignPixel(chart, lineValue, lineWidth);
832811
labelY = me.getPixelForTick(index) + labelOffset;
812+
textOffset = (1 - labelCount) * lineHeight / 2;
833813

834814
if (position === 'left') {
835815
x1 = alignPixel(chart, chartArea.left, axisWidth) + axisWidth / 2;
@@ -862,7 +842,7 @@ module.exports = Element.extend({
862842
rotation: -1 * labelRotationRadians,
863843
label: label,
864844
major: tick.major,
865-
textBaseline: textBaseline,
845+
textOffset: textOffset,
866846
textAlign: textAlign
867847
});
868848
});
@@ -902,25 +882,21 @@ module.exports = Element.extend({
902882
context.save();
903883
context.translate(itemToDraw.labelX, itemToDraw.labelY);
904884
context.rotate(itemToDraw.rotation);
905-
context.font = itemToDraw.major ? majorTickFont.font : tickFont.font;
885+
context.font = itemToDraw.major ? majorTickFont.string : tickFont.string;
906886
context.fillStyle = itemToDraw.major ? majorTickFontColor : tickFontColor;
907-
context.textBaseline = itemToDraw.textBaseline;
887+
context.textBaseline = 'middle';
908888
context.textAlign = itemToDraw.textAlign;
909889

910890
var label = itemToDraw.label;
891+
var y = itemToDraw.textOffset;
911892
if (helpers.isArray(label)) {
912-
var lineCount = label.length;
913-
var lineHeight = tickFont.size * 1.5;
914-
var y = isHorizontal ? 0 : -lineHeight * (lineCount - 1) / 2;
915-
916-
for (var i = 0; i < lineCount; ++i) {
893+
for (var i = 0; i < label.length; ++i) {
917894
// We just make sure the multiline element is a string here..
918895
context.fillText('' + label[i], 0, y);
919-
// apply same lineSpacing as calculated @ L#320
920896
y += lineHeight;
921897
}
922898
} else {
923-
context.fillText(label, 0, 0);
899+
context.fillText(label, 0, y);
924900
}
925901
context.restore();
926902
}
@@ -931,7 +907,7 @@ module.exports = Element.extend({
931907
var scaleLabelX;
932908
var scaleLabelY;
933909
var rotation = 0;
934-
var halfLineHeight = parseLineHeight(scaleLabel) / 2;
910+
var halfLineHeight = scaleLabelFont.lineHeight / 2;
935911

936912
if (isHorizontal) {
937913
scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width
@@ -953,7 +929,7 @@ module.exports = Element.extend({
953929
context.textAlign = 'center';
954930
context.textBaseline = 'middle';
955931
context.fillStyle = scaleLabelFontColor; // render in correct colour
956-
context.font = scaleLabelFont.font;
932+
context.font = scaleLabelFont.string;
957933
context.fillText(scaleLabel.labelString, 0, 0);
958934
context.restore();
959935
}

src/elements/element.line.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ var defaults = require('../core/core.defaults');
44
var Element = require('../core/core.element');
55
var helpers = require('../helpers/index');
66

7-
var globalDefaults = defaults.global;
7+
var defaultColor = defaults.global.defaultColor;
88

99
defaults._set('global', {
1010
elements: {
1111
line: {
1212
tension: 0.4,
13-
backgroundColor: globalDefaults.defaultColor,
13+
backgroundColor: defaultColor,
1414
borderWidth: 3,
15-
borderColor: globalDefaults.defaultColor,
15+
borderColor: defaultColor,
1616
borderCapStyle: 'butt',
1717
borderDash: [],
1818
borderDashOffset: 0.0,
@@ -30,6 +30,7 @@ module.exports = Element.extend({
3030
var ctx = me._chart.ctx;
3131
var spanGaps = vm.spanGaps;
3232
var points = me._children.slice(); // clone array
33+
var globalDefaults = defaults.global;
3334
var globalOptionLineElements = globalDefaults.elements.line;
3435
var lastDrawnIndex = -1;
3536
var index, current, previous, currentVM;

src/elements/element.point.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ module.exports = Element.extend({
7373
var x = vm.x;
7474
var y = vm.y;
7575
var epsilon = 0.0000001; // 0.0000001 is margin in pixels for Accumulated error.
76+
var globalDefaults = defaults.global;
77+
var defaultColor = globalDefaults.defaultColor; // eslint-disable-line no-shadow
7678

7779
if (vm.skip) {
7880
return;
@@ -81,7 +83,7 @@ module.exports = Element.extend({
8183
// Clipping for Points.
8284
if (chartArea === undefined || (model.x > chartArea.left - epsilon && chartArea.right + epsilon > model.x && model.y > chartArea.top - epsilon && chartArea.bottom + epsilon > model.y)) {
8385
ctx.strokeStyle = vm.borderColor || defaultColor;
84-
ctx.lineWidth = helpers.valueOrDefault(vm.borderWidth, defaults.global.elements.point.borderWidth);
86+
ctx.lineWidth = helpers.valueOrDefault(vm.borderWidth, globalDefaults.elements.point.borderWidth);
8587
ctx.fillStyle = vm.backgroundColor || defaultColor;
8688
helpers.canvas.drawPoint(ctx, pointStyle, radius, x, y, rotation);
8789
}

src/elements/element.rectangle.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
var defaults = require('../core/core.defaults');
44
var Element = require('../core/core.element');
55

6+
var defaultColor = defaults.global.defaultColor;
7+
68
defaults._set('global', {
79
elements: {
810
rectangle: {
9-
backgroundColor: defaults.global.defaultColor,
10-
borderColor: defaults.global.defaultColor,
11+
backgroundColor: defaultColor,
12+
borderColor: defaultColor,
1113
borderSkipped: 'bottom',
1214
borderWidth: 0
1315
}

src/helpers/helpers.options.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,25 @@
11
'use strict';
22

3+
var defaults = require('../core/core.defaults');
34
var helpers = require('./helpers.core');
45

6+
/**
7+
* Converts the given font object into a CSS font string.
8+
* @param {Object} font - A font object.
9+
* @return {Stringg} The CSS font string. See https://developer.mozilla.org/en-US/docs/Web/CSS/font
10+
* @private
11+
*/
12+
function toFontString(font) {
13+
if (!font || helpers.isNullOrUndef(font.size) || helpers.isNullOrUndef(font.family)) {
14+
return null;
15+
}
16+
17+
return (font.style ? font.style + ' ' : '')
18+
+ (font.weight ? font.weight + ' ' : '')
19+
+ font.size + 'px '
20+
+ font.family;
21+
}
22+
523
/**
624
* @alias Chart.helpers.options
725
* @namespace
@@ -65,6 +83,30 @@ module.exports = {
6583
};
6684
},
6785

86+
/**
87+
* Parses font options and returns the font object.
88+
* @param {Object} options - A object that contains font opttons to be parsed.
89+
* @return {Object} The font object.
90+
* @todo Support font.* options and renamed to toFont().
91+
* @private
92+
*/
93+
_parseFont: function(options) {
94+
var valueOrDefault = helpers.valueOrDefault;
95+
var globalDefaults = defaults.global;
96+
var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize);
97+
var font = {
98+
family: valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily),
99+
lineHeight: helpers.options.toLineHeight(valueOrDefault(options.lineHeight, globalDefaults.defaultLineHeight), size),
100+
size: size,
101+
style: valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle),
102+
weight: null,
103+
string: ''
104+
};
105+
106+
font.string = toFontString(font);
107+
return font;
108+
},
109+
68110
/**
69111
* Evaluates the given `inputs` sequentially and returns the first defined value.
70112
* @param {Array[]} inputs - An array of values, falling back to the last value.

0 commit comments

Comments
 (0)