Skip to content

Commit dc15177

Browse files
authored
Merge pull request #5752 from plotly/legendgroup-titles
Legend group titles
2 parents 608337e + 4289532 commit dc15177

13 files changed

+236
-19
lines changed

src/components/legend/draw.js

+30-10
Original file line numberDiff line numberDiff line change
@@ -393,20 +393,26 @@ function drawTexts(g, gd, legendObj) {
393393
var isEditable = !legendObj._inHover && gd._context.edits.legendText && !isPieLike;
394394
var maxNameLength = legendObj._maxNameLength;
395395

396-
var name;
397-
if(!legendObj.entries) {
398-
name = isPieLike ? legendItem.label : trace.name;
399-
if(trace._meta) {
400-
name = Lib.templateString(name, trace._meta);
401-
}
396+
var name, font;
397+
if(legendItem.groupTitle) {
398+
name = legendItem.groupTitle.text;
399+
font = legendItem.groupTitle.font;
402400
} else {
403-
name = legendItem.text;
401+
font = legendObj.font;
402+
if(!legendObj.entries) {
403+
name = isPieLike ? legendItem.label : trace.name;
404+
if(trace._meta) {
405+
name = Lib.templateString(name, trace._meta);
406+
}
407+
} else {
408+
name = legendItem.text;
409+
}
404410
}
405411

406412
var textEl = Lib.ensureSingle(g, 'text', 'legendtext');
407413

408414
textEl.attr('text-anchor', 'start')
409-
.call(Drawing.font, legendObj.font)
415+
.call(Drawing.font, font)
410416
.text(isEditable ? ensureLength(name, maxNameLength) : name);
411417

412418
var textGap = legendObj.itemwidth + constants.itemGap * 2;
@@ -512,7 +518,15 @@ function computeTextDimensions(g, gd, legendObj, aTitle) {
512518
var mathjaxNode = mathjaxGroup.node();
513519
if(!legendObj) legendObj = gd._fullLayout.legend;
514520
var bw = legendObj.borderwidth;
515-
var lineHeight = (aTitle === MAIN_TITLE ? legendObj.title : legendObj).font.size * LINE_SPACING;
521+
var font;
522+
if(aTitle === MAIN_TITLE) {
523+
font = legendObj.title.font;
524+
} else if(legendItem.groupTitle) {
525+
font = legendItem.groupTitle.font;
526+
} else {
527+
font = legendObj.font;
528+
}
529+
var lineHeight = font.size * LINE_SPACING;
516530
var height, width;
517531

518532
if(mathjaxNode) {
@@ -549,8 +563,14 @@ function computeTextDimensions(g, gd, legendObj, aTitle) {
549563
bw + lineHeight
550564
);
551565
} else { // legend item
566+
var x = constants.itemGap * 2 + legendObj.itemwidth;
567+
if(legendItem.groupTitle) {
568+
x = constants.itemGap;
569+
width -= legendObj.itemwidth;
570+
}
571+
552572
svgTextUtils.positionText(textEl,
553-
legendObj.itemwidth + constants.itemGap * 2,
573+
x,
554574
-lineHeight * ((textLines - 1) / 2 - 0.3)
555575
);
556576
}

src/components/legend/get_legend_data.js

+25
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,33 @@ module.exports = function getLegendData(calcdata, opts) {
117117
// sort considering trace.legendrank and legend.traceorder
118118
legendData[i].forEach(function(a, k) { a._preSort = k; });
119119
legendData[i].sort(orderFn2);
120+
121+
var firstItem = legendData[i][0];
122+
123+
var groupTitle = null;
124+
// get group title text
125+
for(j = 0; j < legendData[i].length; j++) {
126+
var gt = legendData[i][j].trace.legendgrouptitle;
127+
if(gt && gt.text) {
128+
groupTitle = gt;
129+
break;
130+
}
131+
}
132+
133+
// reverse order
120134
if(reversed) legendData[i].reverse();
121135

136+
if(groupTitle) {
137+
// set group title text
138+
legendData[i].unshift({
139+
i: -1,
140+
groupTitle: groupTitle,
141+
trace: {
142+
showlegend: firstItem.trace.showlegend
143+
}
144+
});
145+
}
146+
122147
// rearrange lgroupToTraces into a d3-friendly array of arrays
123148
for(j = 0; j < legendData[i].length; j++) {
124149
legendData[i][j] = [

src/components/legend/handle_click.js

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ module.exports = function handleClick(g, gd, numClicks) {
3232
[];
3333

3434
var legendItem = g.data()[0][0];
35+
if(legendItem.groupTitle) return; // no click on group legends for now
36+
3537
var fullData = gd._fullData;
3638
var fullTrace = legendItem.trace;
3739
var legendgroup = fullTrace.legendgroup;

src/plots/attributes.js

+18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict';
22

3+
var fontAttrs = require('./font_attributes');
34
var fxAttrs = require('../components/fx/attributes');
45

56
module.exports = {
@@ -41,6 +42,23 @@ module.exports = {
4142
'when toggling legend items.'
4243
].join(' ')
4344
},
45+
legendgrouptitle: {
46+
text: {
47+
valType: 'string',
48+
dflt: '',
49+
editType: 'style',
50+
description: [
51+
'Sets the title of the legend group.'
52+
].join(' ')
53+
},
54+
font: fontAttrs({
55+
editType: 'style',
56+
description: [
57+
'Sets this legend group\'s title font.'
58+
].join(' '),
59+
}),
60+
editType: 'style',
61+
},
4462
legendrank: {
4563
valType: 'number',
4664
dflt: 1000,

src/plots/plots.js

+7
Original file line numberDiff line numberDiff line change
@@ -1309,6 +1309,13 @@ plots.supplyTraceDefaults = function(traceIn, traceOut, colorIndex, layout, trac
13091309
);
13101310

13111311
coerce('legendgroup');
1312+
var titleText = coerce('legendgrouptitle.text');
1313+
if(titleText) {
1314+
Lib.coerceFont(coerce, 'legendgrouptitle.font', Lib.extendFlat({}, layout.font, {
1315+
size: Math.round(layout.font.size * 1.1) // default to larger font size
1316+
}));
1317+
}
1318+
13121319
coerce('legendrank');
13131320

13141321
traceOut._dfltShowLegend = true;
18.9 KB
Loading
16.4 KB
Loading

test/image/baselines/legendrank.png

1.73 KB
Loading

test/image/baselines/legendrank2.png

1.99 KB
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
{
2+
"data": [
3+
{
4+
"type": "pie",
5+
"domain": {
6+
"y": [0.8, 1]
7+
},
8+
"labels": [0],
9+
"legendgroup": "G1"
10+
},
11+
{
12+
"type": "pie",
13+
"domain": {
14+
"y": [0.6, 0.8]
15+
},
16+
"labels": [1],
17+
"legendgroup": "G1"
18+
},
19+
{
20+
"type": "pie",
21+
"domain": {
22+
"y": [0.4, 0.6]
23+
},
24+
"labels": [2],
25+
"legendgroup": "G1",
26+
"legendgrouptitle": {
27+
"text": "First group"
28+
}
29+
},
30+
{
31+
"type": "pie",
32+
"domain": {
33+
"y": [0.2, 0.4]
34+
},
35+
"labels": [3],
36+
"legendgroup": "G2",
37+
"legendgrouptitle": {
38+
"text": "Second group"
39+
}
40+
},
41+
{
42+
"domain": {
43+
"y": [0, 0.2]
44+
},
45+
"type": "pie",
46+
"labels": [4, 5],
47+
"legendgroup": "G3",
48+
"legendgrouptitle": {
49+
"text": "Third group"
50+
}
51+
}
52+
],
53+
"layout": {
54+
"title": {
55+
"text": "legend group titles"
56+
},
57+
"margin": {
58+
"t": 50,
59+
"b": 25,
60+
"l": 25,
61+
"r": 25
62+
},
63+
"width": 300,
64+
"height": 300,
65+
"legend": {
66+
"bgcolor": "lightblue",
67+
"title": {
68+
"text": ""
69+
}
70+
},
71+
"hovermode": "x unified"
72+
}
73+
}
+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
{
2+
"data": [
3+
{
4+
"y": [0],
5+
"legendgroup": "G1"
6+
},
7+
{
8+
"y": [1],
9+
"legendgroup": "G1"
10+
},
11+
{
12+
"y": [2],
13+
"legendgroup": "G1",
14+
"legendgrouptitle": {
15+
"text": "First group"
16+
}
17+
},
18+
{
19+
"y": [3],
20+
"legendgroup": "G2",
21+
"legendgrouptitle": {
22+
"text": "Second<br>group",
23+
"font": {
24+
"family": "Raleway",
25+
"size": 14
26+
}
27+
}
28+
},
29+
{
30+
"y": [4],
31+
"legendgroup": "G3",
32+
"legendgrouptitle": {
33+
"text": "Third group"
34+
}
35+
},
36+
{
37+
"y": [5],
38+
"legendgroup": "G3"
39+
}
40+
],
41+
"layout": {
42+
"title": {
43+
"text": "legend group titles"
44+
},
45+
"margin": {
46+
"t": 50,
47+
"b": 25,
48+
"l": 25,
49+
"r": 25
50+
},
51+
"width": 300,
52+
"height": 300,
53+
"yaxis": {
54+
"autorange": "reversed"
55+
},
56+
"legend": {
57+
"bgcolor": "lightblue",
58+
"title": {
59+
"text": ""
60+
}
61+
},
62+
"hovermode": "x unified"
63+
}
64+
}

test/image/mocks/legendrank.json

+7-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
{
55
"legendrank": 2,
66
"legendgroup": "pie",
7+
"legendgrouptitle": { "text": "Next pie" },
78
"type": "pie",
89
"labels": ["a","b","c","c","c","a"],
910
"textinfo": "none",
@@ -15,6 +16,7 @@
1516
{
1617
"legendrank": 1,
1718
"legendgroup": "pie",
19+
"legendgrouptitle": { "text": "Pie" },
1820
"type": "pie",
1921
"labels": ["z","x","x","x","y", "y"],
2022
"sort": false,
@@ -25,9 +27,9 @@
2527
}
2628
},
2729
{"type": "scatter", "name": "2", "y": [2], "yaxis": "y", "legendgroup": "one", "legendrank": 2},
28-
{"type": "scatter", "name": "1", "y": [1], "yaxis": "y", "legendgroup": "one", "legendrank": 1},
29-
{"type": "bar", "name": "2", "y": [2], "yaxis": "y2", "legendgroup": "two", "legendrank": 2},
30-
{"type": "scatter", "name": "3", "y": [3], "yaxis": "y", "legendgroup": "one", "legendrank": 3},
30+
{"type": "scatter", "name": "1", "y": [1], "yaxis": "y", "legendgroup": "one", "legendrank": 1, "legendgrouptitle": { "text": "Down" }},
31+
{"type": "bar", "name": "2", "y": [2], "yaxis": "y2", "legendgroup": "two", "legendrank": 2, "legendgrouptitle": { "text": "Up" }},
32+
{"type": "scatter", "name": "3", "y": [3], "yaxis": "y", "legendgroup": "one", "legendrank": 3, "legendgrouptitle": { "text": "IGNORE ME!" }},
3133
{"type": "bar", "name": "3", "y": [3], "yaxis": "y2", "legendgroup": "two", "legendrank": 1}
3234
],
3335
"layout": {
@@ -36,7 +38,8 @@
3638
},
3739
"hovermode": "x unified",
3840
"margin": {
39-
"t": 50
41+
"t": 50,
42+
"b": 25
4043
},
4144
"width": 300,
4245
"height": 400,

test/image/mocks/legendrank2.json

+10-5
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,36 @@
33
{
44
"name": "A",
55
"legendrank": 2,
6-
"y": [-2]
6+
"y": [-2],
7+
"legendgrouptitle": { "text": "Middle" }
78
},
89
{
910
"name": "D",
1011
"legendrank": 4,
1112
"y": [-4],
12-
"legendgroup": "bottom"
13+
"legendgroup": "bottom",
14+
"legendgrouptitle": { "text": "Bottom" }
1315
},
1416
{
1517
"name": "E",
1618
"legendrank": 4,
1719
"y": [-4],
18-
"legendgroup": "bottom"
20+
"legendgroup": "bottom",
21+
"legendgrouptitle": { "text": "Second" }
1922
},
2023
{
2124
"name": "B",
2225
"legendrank": 1,
2326
"y": [-1],
24-
"legendgroup": "top"
27+
"legendgroup": "top",
28+
"legendgrouptitle": { "text": "Top" }
2529
},
2630
{
2731
"name": "C",
2832
"legendrank": 3,
2933
"y": [-3],
30-
"legendgroup": "top"
34+
"legendgroup": "top",
35+
"legendgrouptitle": { "text": "Not first" }
3136
}
3237
],
3338
"layout": {

0 commit comments

Comments
 (0)