Skip to content

Commit 07fae61

Browse files
dkichlersimonbrunel
authored andcommitted
Implement legend.align: 'start', 'center', 'end' (#6141)
New `options.legend.align`config option for controlling alignment of legend blocks in horizontal/vertical legends.
1 parent b9290a2 commit 07fae61

File tree

39 files changed

+491
-367
lines changed

39 files changed

+491
-367
lines changed

docs/configuration/legend.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ The legend configuration is passed into the `options.legend` namespace. The glob
99
| ---- | ---- | ------- | -----------
1010
| `display` | `boolean` | `true` | Is the legend shown?
1111
| `position` | `string` | `'top'` | Position of the legend. [more...](#position)
12+
| `align` | `string` | `'center'` | Alignment of the legend. [more...](#align)
1213
| `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.
1314
| `onClick` | `function` | | A callback that is called when a click event is registered on a label item.
1415
| `onHover` | `function` | | A callback that is called when a 'mousemove' event is registered on top of a label item.
@@ -23,6 +24,14 @@ Position of the legend. Options are:
2324
* `'bottom'`
2425
* `'right'`
2526

27+
## Align
28+
Alignment of the legend. Options are:
29+
* `'start'`
30+
* `'center'`
31+
* `'end'`
32+
33+
Defaults to `'center'` for unrecognized values.
34+
2635
## Legend Label Configuration
2736

2837
The legend label configuration is nested below the legend configuration using the `labels` key.

src/plugins/plugin.legend.js

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ defaults._set('global', {
1212
legend: {
1313
display: true,
1414
position: 'top',
15+
align: 'center',
1516
fullWidth: true,
1617
reverse: false,
1718
weight: 1000,
@@ -102,18 +103,19 @@ function getBoxWidth(labelOpts, fontSize) {
102103
var Legend = Element.extend({
103104

104105
initialize: function(config) {
105-
helpers.extend(this, config);
106+
var me = this;
107+
helpers.extend(me, config);
106108

107109
// Contains hit boxes for each dataset (in dataset order)
108-
this.legendHitBoxes = [];
110+
me.legendHitBoxes = [];
109111

110112
/**
111113
* @private
112114
*/
113-
this._hoveredItem = null;
115+
me._hoveredItem = null;
114116

115117
// Are we in doughnut mode which has a different data type
116-
this.doughnutMode = false;
118+
me.doughnutMode = false;
117119
},
118120

119121
// These methods are ordered by lifecycle. Utilities then follow.
@@ -253,9 +255,9 @@ var Legend = Element.extend({
253255
var boxWidth = getBoxWidth(labelOpts, fontSize);
254256
var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
255257

256-
if (i === 0 || lineWidths[lineWidths.length - 1] + width + labelOpts.padding > minSize.width) {
258+
if (i === 0 || lineWidths[lineWidths.length - 1] + width + 2 * labelOpts.padding > minSize.width) {
257259
totalHeight += fontSize + labelOpts.padding;
258-
lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = labelOpts.padding;
260+
lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0;
259261
}
260262

261263
// Store the hitbox width and height here. Final position will be updated in `draw`
@@ -274,27 +276,27 @@ var Legend = Element.extend({
274276
} else {
275277
var vPadding = labelOpts.padding;
276278
var columnWidths = me.columnWidths = [];
279+
var columnHeights = me.columnHeights = [];
277280
var totalWidth = labelOpts.padding;
278281
var currentColWidth = 0;
279282
var currentColHeight = 0;
280-
var itemHeight = fontSize + vPadding;
281283

282284
helpers.each(me.legendItems, function(legendItem, i) {
283285
var boxWidth = getBoxWidth(labelOpts, fontSize);
284286
var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
285287

286288
// If too tall, go to new column
287-
if (i > 0 && currentColHeight + itemHeight > minSize.height - vPadding) {
289+
if (i > 0 && currentColHeight + fontSize + 2 * vPadding > minSize.height) {
288290
totalWidth += currentColWidth + labelOpts.padding;
289291
columnWidths.push(currentColWidth); // previous column width
290-
292+
columnHeights.push(currentColHeight);
291293
currentColWidth = 0;
292294
currentColHeight = 0;
293295
}
294296

295297
// Get max width
296298
currentColWidth = Math.max(currentColWidth, itemWidth);
297-
currentColHeight += itemHeight;
299+
currentColHeight += fontSize + vPadding;
298300

299301
// Store the hitbox width and height here. Final position will be updated in `draw`
300302
hitboxes[i] = {
@@ -307,6 +309,7 @@ var Legend = Element.extend({
307309

308310
totalWidth += currentColWidth;
309311
columnWidths.push(currentColWidth);
312+
columnHeights.push(currentColHeight);
310313
minSize.width += totalWidth;
311314
}
312315
}
@@ -329,6 +332,8 @@ var Legend = Element.extend({
329332
var globalDefaults = defaults.global;
330333
var defaultColor = globalDefaults.defaultColor;
331334
var lineDefault = globalDefaults.elements.line;
335+
var legendHeight = me.height;
336+
var columnHeights = me.columnHeights;
332337
var legendWidth = me.width;
333338
var lineWidths = me.lineWidths;
334339

@@ -408,18 +413,29 @@ var Legend = Element.extend({
408413
}
409414
};
410415

416+
var alignmentOffset = function(dimension, blockSize) {
417+
switch (opts.align) {
418+
case 'start':
419+
return labelOpts.padding;
420+
case 'end':
421+
return dimension - blockSize;
422+
default: // center
423+
return (dimension - blockSize + labelOpts.padding) / 2;
424+
}
425+
};
426+
411427
// Horizontal
412428
var isHorizontal = me.isHorizontal();
413429
if (isHorizontal) {
414430
cursor = {
415-
x: me.left + ((legendWidth - lineWidths[0]) / 2) + labelOpts.padding,
431+
x: me.left + alignmentOffset(legendWidth, lineWidths[0]),
416432
y: me.top + labelOpts.padding,
417433
line: 0
418434
};
419435
} else {
420436
cursor = {
421437
x: me.left + labelOpts.padding,
422-
y: me.top + labelOpts.padding,
438+
y: me.top + alignmentOffset(legendHeight, columnHeights[0]),
423439
line: 0
424440
};
425441
}
@@ -438,12 +454,12 @@ var Legend = Element.extend({
438454
if (i > 0 && x + width + labelOpts.padding > me.left + me.minSize.width) {
439455
y = cursor.y += itemHeight;
440456
cursor.line++;
441-
x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2) + labelOpts.padding;
457+
x = cursor.x = me.left + alignmentOffset(legendWidth, lineWidths[cursor.line]);
442458
}
443459
} else if (i > 0 && y + itemHeight > me.top + me.minSize.height) {
444460
x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding;
445-
y = cursor.y = me.top + labelOpts.padding;
446461
cursor.line++;
462+
y = cursor.y = me.top + alignmentOffset(legendHeight, columnHeights[cursor.line]);
447463
}
448464

449465
drawLegendBox(x, y, legendItem);
@@ -459,7 +475,6 @@ var Legend = Element.extend({
459475
} else {
460476
cursor.y += itemHeight;
461477
}
462-
463478
});
464479
}
465480
},
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"config": {
3+
"type": "doughnut",
4+
"data": {
5+
"labels": ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
6+
"datasets": [{
7+
"data": [10, 20, 30, 40, 50, 60, 70, 10, 20, 30, 40, 50, 60, 70, 10, 20, 30, 20, 10],
8+
"backgroundColor": "#00ff00",
9+
"borderWidth": 0
10+
}]
11+
},
12+
"options": {
13+
"legend": {
14+
"position": "bottom",
15+
"align": "center"
16+
}
17+
}
18+
},
19+
"options": {
20+
"canvas": {
21+
"height": 256,
22+
"width": 512
23+
}
24+
}
25+
}
12.3 KB
Loading
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"config": {
3+
"type": "doughnut",
4+
"data": {
5+
"labels": [""],
6+
"datasets": [{
7+
"data": [10],
8+
"backgroundColor": "#00ff00",
9+
"borderWidth": 0
10+
}]
11+
},
12+
"options": {
13+
"legend": {
14+
"position": "bottom",
15+
"align": "center"
16+
}
17+
}
18+
},
19+
"options": {
20+
"canvas": {
21+
"height": 256,
22+
"width": 512
23+
}
24+
}
25+
}
9.15 KB
Loading
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"config": {
3+
"type": "doughnut",
4+
"data": {
5+
"labels": ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
6+
"datasets": [{
7+
"data": [10, 20, 30, 40, 50, 60, 70, 10, 20, 30, 40, 50, 60, 70, 10],
8+
"backgroundColor": "#00ff00",
9+
"borderWidth": 0
10+
}]
11+
},
12+
"options": {
13+
"legend": {
14+
"position": "bottom",
15+
"align": "end"
16+
}
17+
}
18+
},
19+
"options": {
20+
"canvas": {
21+
"height": 256,
22+
"width": 512
23+
}
24+
}
25+
}
11.4 KB
Loading
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"config": {
3+
"type": "doughnut",
4+
"data": {
5+
"labels": ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
6+
"datasets": [{
7+
"data": [10, 20, 30, 40, 50, 60, 70, 10, 20, 30, 40, 50, 60, 70, 10],
8+
"backgroundColor": "#00ff00",
9+
"borderWidth": 0
10+
}]
11+
},
12+
"options": {
13+
"legend": {
14+
"position": "bottom",
15+
"align": "start"
16+
}
17+
}
18+
},
19+
"options": {
20+
"canvas": {
21+
"height": 256,
22+
"width": 512
23+
}
24+
}
25+
}
11.3 KB
Loading

0 commit comments

Comments
 (0)