Skip to content

Commit 4784ba2

Browse files
committed
Add legend label style option
- Add style option to legend labels config suppoting box, line and point style - Deprecate usePointStyle option - BoxWidth is now calculated for each legend item as it can have different width - Fix the existing tests and add more tests - Update document
1 parent 88308c6 commit 4784ba2

File tree

7 files changed

+282
-38
lines changed

7 files changed

+282
-38
lines changed

docs/configuration/legend.md

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ The legend configuration is passed into the `options.legend` namespace. The glob
77

88
| Name | Type | Default | Description
99
| -----| ---- | --------| -----------
10-
| `display` | `Boolean` | `true` | is the legend shown
10+
| `display` | `Boolean` | `true` | Whether the legend is shown
1111
| `position` | `String` | `'top'` | Position of the legend. [more...](#position)
1212
| `fullWidth` | `Boolean` | `true` | Marks that this box should take the full width of the canvas (pushing down other boxes). This is unlikely to need to be changed in day-to-day use.
1313
| `onClick` | `Function` | | A callback that is called when a click event is registered on a label item
@@ -28,15 +28,28 @@ The legend label configuration is nested below the legend configuration using th
2828

2929
| Name | Type | Default | Description
3030
| -----| ---- | --------| -----------
31-
| `boxWidth` | `Number` | `40` | width of coloured box
32-
| `fontSize` | `Number` | `12` | font size of text
33-
| `fontStyle` | `String` | `'normal'` | font style of text
31+
| `boxWidth` | `Number` | `40` | Width of coloured box
32+
| `fontSize` | `Number` | `12` | Font size of text
33+
| `fontStyle` | `String` | `'normal'` | Font style of text
3434
| `fontColor` | `Color` | `'#666'` | Color of text
3535
| `fontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family of legend text.
3636
| `padding` | `Number` | `10` | Padding between rows of colored boxes.
3737
| `generateLabels` | `Function` | | Generates legend items for each thing in the legend. Default implementation returns the text + styling for the color box. See [Legend Item](#legend-item-interface) for details.
3838
| `filter` | `Function` | `null` | Filters legend items out of the legend. Receives 2 parameters, a [Legend Item](#legend-item-interface) and the chart data.
39-
| `usePointStyle` | `Boolean` | `false` | Label style will match corresponding point style (size is based on fontSize, boxWidth is not used in this case).
39+
| `style` | `String` | | Style of the label item. [more...](#label-style)
40+
41+
### Label Style
42+
43+
Possible label style values are:
44+
* `'box'`
45+
* `'line'`
46+
* `'point'`
47+
48+
`'box'` will draw a box using the background color, border width and color of the corresponding element.
49+
`'line'` will draw a line using the corresponding line style.
50+
`'point'` will make the label style match the corresponding point style (size is based on `fontSize`, `boxWidth` is not used in this case).
51+
52+
If not set, the `'line'` style is used for line elements, and the `'box'` style for other elements.
4053

4154
## Legend Item Interface
4255

@@ -53,26 +66,29 @@ Items passed to the legend `onClick` function are the ones returned from `labels
5366
// If true, this item represents a hidden dataset. Label will be rendered with a strike-through effect
5467
hidden: Boolean,
5568

56-
// For box border. See https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineCap
69+
// For line border. See https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineCap
5770
lineCap: String,
5871

59-
// For box border. See https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash
72+
// For line border. See https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash
6073
lineDash: Array[Number],
6174

62-
// For box border. See https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset
75+
// For line border. See https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset
6376
lineDashOffset: Number,
6477

65-
// For box border. See https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin
78+
// For line border. See https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin
6679
lineJoin: String,
6780

68-
// Width of box border
81+
// Width of box border or line
6982
lineWidth: Number,
7083

71-
// Stroke style of the legend box
72-
strokeStyle: Color
84+
// Stroke style of the legend box or line
85+
strokeStyle: Color,
7386

74-
// Point style of the legend box (only used if usePointStyle is true)
75-
pointStyle: String
87+
// Point style of the legend box (only used if style is 'point')
88+
pointStyle: String,
89+
90+
// Style of the legend box
91+
style: String
7692
}
7793
```
7894

@@ -91,7 +107,7 @@ var chart = new Chart(ctx, {
91107
fontColor: 'rgb(255, 99, 132)'
92108
}
93109
}
94-
}
110+
}
95111
});
96112
```
97113

@@ -107,7 +123,7 @@ function(e, legendItem) {
107123
var meta = ci.getDatasetMeta(index);
108124

109125
// See controller.isDatasetVisible comment
110-
meta.hidden = meta.hidden === null? !ci.data.datasets[index].hidden : null;
126+
meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null;
111127

112128
// We hid a dataset ... rerender the chart
113129
ci.update();
@@ -118,7 +134,7 @@ Lets say we wanted instead to link the display of the first two datasets. We cou
118134

119135
```javascript
120136
var defaultLegendClickHandler = Chart.defaults.global.legend.onClick;
121-
var newLegendClickHandler = function (e, legendItem) {
137+
var newLegendClickHandler = function(e, legendItem) {
122138
var index = legendItem.datasetIndex;
123139

124140
if (index > 1) {
@@ -128,11 +144,11 @@ var newLegendClickHandler = function (e, legendItem) {
128144
let ci = this.chart;
129145
[ci.getDatasetMeta(0),
130146
ci.getDatasetMeta(1)].forEach(function(meta) {
131-
meta.hidden = meta.hidden === null? !ci.data.datasets[index].hidden : null;
147+
meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null;
132148
});
133149
ci.update();
134150
}
135-
};
151+
});
136152

137153
var chart = new Chart(ctx, {
138154
type: 'line',

src/controllers/controller.doughnut.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,17 @@ defaults._set('doughnut', {
5050
var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
5151
var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
5252
var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
53+
var labelOpts = chart.options.legend.labels;
54+
var style = labelOpts.style;
5355

5456
return {
5557
text: label,
5658
fillStyle: fill,
5759
strokeStyle: stroke,
5860
lineWidth: bw,
5961
hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
62+
// `usePointStyle` is deprecated. To be removed at version 3
63+
style: !style && labelOpts.usePointStyle ? 'point' : style,
6064

6165
// Extra data used for toggling the correct item
6266
index: i

src/controllers/controller.polarArea.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,17 @@ defaults._set('polarArea', {
6464
var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
6565
var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
6666
var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
67+
var labelOpts = chart.options.legend.labels;
68+
var style = labelOpts.style;
6769

6870
return {
6971
text: label,
7072
fillStyle: fill,
7173
strokeStyle: stroke,
7274
lineWidth: bw,
7375
hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
76+
// `usePointStyle` is deprecated. To be removed at version 3
77+
style: !style && labelOpts.usePointStyle ? 'point' : style,
7478

7579
// Extra data used for toggling the correct item
7680
index: i

src/plugins/plugin.legend.js

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,19 @@ defaults._set('global', {
4747
generateLabels: function(chart) {
4848
var data = chart.data;
4949
return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) {
50+
var labelOpts = chart.options.legend.labels;
51+
var style = labelOpts.style;
52+
var type = chart.getDatasetMeta(i).type;
53+
54+
if (!style) {
55+
// `usePointStyle` is deprecated. To be removed at version 3
56+
if (labelOpts.usePointStyle) {
57+
style = 'point';
58+
} else if (type === 'line' || type === 'radar') {
59+
style = 'line';
60+
}
61+
}
62+
5063
return {
5164
text: dataset.label,
5265
fillStyle: (!helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]),
@@ -58,6 +71,7 @@ defaults._set('global', {
5871
lineWidth: dataset.borderWidth,
5972
strokeStyle: dataset.borderColor,
6073
pointStyle: dataset.pointStyle,
74+
style: style,
6175

6276
// Below is extra data used for toggling the datasets
6377
datasetIndex: i
@@ -83,13 +97,14 @@ defaults._set('global', {
8397
});
8498

8599
/**
86-
* Helper function to get the box width based on the usePointStyle option
87-
* @param labelopts {Object} the label options on the legend
100+
* Helper function to get the box width based on the style option
101+
* @param legendItem {Object} the legend item
102+
* @param labelOpts {Object} the label options on the legend
88103
* @param fontSize {Number} the label font size
89104
* @return {Number} width of the color box area
90105
*/
91-
function getBoxWidth(labelOpts, fontSize) {
92-
return labelOpts.usePointStyle ?
106+
function getBoxWidth(legendItem, labelOpts, fontSize) {
107+
return legendItem.style === 'point' ?
93108
fontSize * Math.SQRT2 :
94109
labelOpts.boxWidth;
95110
}
@@ -247,7 +262,7 @@ var Legend = Element.extend({
247262
ctx.textBaseline = 'top';
248263

249264
helpers.each(me.legendItems, function(legendItem, i) {
250-
var boxWidth = getBoxWidth(labelOpts, fontSize);
265+
var boxWidth = getBoxWidth(legendItem, labelOpts, fontSize);
251266
var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
252267

253268
if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) {
@@ -277,7 +292,7 @@ var Legend = Element.extend({
277292
var itemHeight = fontSize + vPadding;
278293

279294
helpers.each(me.legendItems, function(legendItem, i) {
280-
var boxWidth = getBoxWidth(labelOpts, fontSize);
295+
var boxWidth = getBoxWidth(legendItem, labelOpts, fontSize);
281296
var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
282297

283298
// If too tall, go to new column
@@ -346,11 +361,10 @@ var Legend = Element.extend({
346361
ctx.fillStyle = fontColor; // render in correct colour
347362
ctx.font = labelFont;
348363

349-
var boxWidth = getBoxWidth(labelOpts, fontSize);
350364
var hitboxes = me.legendHitBoxes;
351365

352366
// current position
353-
var drawLegendBox = function(x, y, legendItem) {
367+
var drawLegendBox = function(x, y, legendItem, boxWidth) {
354368
if (isNaN(boxWidth) || boxWidth <= 0) {
355369
return;
356370
}
@@ -371,7 +385,7 @@ var Legend = Element.extend({
371385
ctx.setLineDash(valueOrDefault(legendItem.lineDash, lineDefault.borderDash));
372386
}
373387

374-
if (opts.labels && opts.labels.usePointStyle) {
388+
if (legendItem.style === 'point') {
375389
// Recalculate x and y for drawPoint() because its expecting
376390
// x and y to be center of figure (instead of top left)
377391
var radius = fontSize * Math.SQRT2 / 2;
@@ -381,6 +395,14 @@ var Legend = Element.extend({
381395

382396
// Draw pointStyle as legend symbol
383397
helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY);
398+
} else if (legendItem.style === 'line') {
399+
// Draw line as legend symbol
400+
if (!isLineWidthZero) {
401+
ctx.beginPath();
402+
ctx.moveTo(x, y + fontSize / 2);
403+
ctx.lineTo(x + boxWidth, y + fontSize / 2);
404+
ctx.stroke();
405+
}
384406
} else {
385407
// Draw box as legend symbol
386408
if (!isLineWidthZero) {
@@ -391,7 +413,7 @@ var Legend = Element.extend({
391413

392414
ctx.restore();
393415
};
394-
var fillText = function(x, y, legendItem, textWidth) {
416+
var fillText = function(x, y, legendItem, boxWidth, textWidth) {
395417
var halfFontSize = fontSize / 2;
396418
var xLeft = boxWidth + halfFontSize + x;
397419
var yMiddle = y + halfFontSize;
@@ -426,6 +448,7 @@ var Legend = Element.extend({
426448

427449
var itemHeight = fontSize + labelOpts.padding;
428450
helpers.each(me.legendItems, function(legendItem, i) {
451+
var boxWidth = getBoxWidth(legendItem, labelOpts, fontSize);
429452
var textWidth = ctx.measureText(legendItem.text).width;
430453
var width = boxWidth + (fontSize / 2) + textWidth;
431454
var x = cursor.x;
@@ -443,13 +466,13 @@ var Legend = Element.extend({
443466
cursor.line++;
444467
}
445468

446-
drawLegendBox(x, y, legendItem);
469+
drawLegendBox(x, y, legendItem, boxWidth);
447470

448471
hitboxes[i].left = x;
449472
hitboxes[i].top = y;
450473

451474
// Fill the actual label
452-
fillText(x, y, legendItem, textWidth);
475+
fillText(x, y, legendItem, boxWidth, textWidth);
453476

454477
if (isHorizontal) {
455478
cursor.x += width + (labelOpts.padding);

test/specs/global.defaults.tests.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,21 +128,24 @@ describe('Default Configs', function() {
128128
hidden: false,
129129
index: 0,
130130
strokeStyle: '#000',
131-
lineWidth: 2
131+
lineWidth: 2,
132+
style: undefined
132133
}, {
133134
text: 'label2',
134135
fillStyle: 'green',
135136
hidden: false,
136137
index: 1,
137138
strokeStyle: '#000',
138-
lineWidth: 2
139+
lineWidth: 2,
140+
style: undefined
139141
}, {
140142
text: 'label3',
141143
fillStyle: 'blue',
142144
hidden: true,
143145
index: 2,
144146
strokeStyle: '#000',
145-
lineWidth: 2
147+
lineWidth: 2,
148+
style: undefined
146149
}];
147150
expect(chart.legend.legendItems).toEqual(expected);
148151
});
@@ -244,21 +247,24 @@ describe('Default Configs', function() {
244247
hidden: false,
245248
index: 0,
246249
strokeStyle: '#000',
247-
lineWidth: 2
250+
lineWidth: 2,
251+
style: undefined
248252
}, {
249253
text: 'label2',
250254
fillStyle: 'green',
251255
hidden: false,
252256
index: 1,
253257
strokeStyle: '#000',
254-
lineWidth: 2
258+
lineWidth: 2,
259+
style: undefined
255260
}, {
256261
text: 'label3',
257262
fillStyle: 'blue',
258263
hidden: true,
259264
index: 2,
260265
strokeStyle: '#000',
261-
lineWidth: 2
266+
lineWidth: 2,
267+
style: undefined
262268
}];
263269
expect(chart.legend.legendItems).toEqual(expected);
264270
});

test/specs/global.deprecations.tests.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,32 @@ describe('Deprecations', function() {
66
expect(Chart.layoutService).toBe(Chart.layouts);
77
});
88
});
9+
10+
describe('Legend Labels: usePointStyle option', function() {
11+
it('should use the style property', function() {
12+
var chart = window.acquireChart({
13+
type: 'line',
14+
data: {
15+
datasets: [{
16+
label: '',
17+
data: []
18+
}],
19+
labels: []
20+
},
21+
options: {
22+
legend: {
23+
labels: {
24+
usePointStyle: true
25+
}
26+
}
27+
}
28+
});
29+
30+
expect(chart.legend.legendItems[0].style).toEqual('point');
31+
expect(chart.legend.legendHitBoxes[0].height).toBeCloseToPixel(12);
32+
expect(chart.legend.legendHitBoxes[0].width).toBeCloseToPixel(23);
33+
});
34+
});
935
});
1036

1137
describe('Version 2.7.0', function() {

0 commit comments

Comments
 (0)