Skip to content

Commit 3bde39f

Browse files
committed
Add legend.itemheight to set the height of the legend fill swatch
1 parent 0af79cc commit 3bde39f

6 files changed

Lines changed: 169 additions & 3 deletions

File tree

src/components/legend/attributes.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,17 @@ module.exports = {
141141
editType: 'legend',
142142
description: 'Sets the width (in px) of the legend item symbols (the part other than the title.text).',
143143
},
144+
itemheight: {
145+
valType: 'number',
146+
min: 6,
147+
dflt: 6,
148+
editType: 'legend',
149+
description: [
150+
'Sets the height (in px) of the legend item fill swatch.',
151+
'Increasing it reveals more of a trace *fill* or *fillpattern* in the legend,',
152+
'and grows the legend item to fit.',
153+
].join(' '),
154+
},
144155
itemclick: {
145156
valType: 'enumerated',
146157
values: ['toggle', 'toggleothers', false],

src/components/legend/defaults.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ function groupDefaults(legendId, layoutIn, layoutOut, fullData, legendCount) {
219219
coerce('indentation');
220220
coerce('itemsizing');
221221
coerce('itemwidth');
222+
coerce('itemheight');
222223

223224
coerce('itemclick');
224225
coerce('itemdoubleclick');

src/components/legend/draw.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -826,7 +826,7 @@ function computeTextDimensions(g, gd, legendObj, aTitle) {
826826
legendObj._titleHeight = height;
827827
} else { // legend item
828828
legendItem.lineHeight = lineHeight;
829-
legendItem.height = Math.max(height, 16) + 3;
829+
legendItem.height = Math.max(height, 16, legendObj.itemheight + 10) + 3;
830830
legendItem.width = width;
831831
}
832832
}

src/components/legend/style.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ module.exports = function style(s, gd, legend) {
2626
if (!legend) legend = fullLayout.legend;
2727
var constantItemSizing = legend.itemsizing === 'constant';
2828
var itemWidth = legend.itemwidth;
29+
var itemHeight = legend.itemheight;
2930
var centerPos = (itemWidth + constants.itemGap * 2) / 2;
3031
var centerTransform = strTranslate(centerPos, 0);
3132

@@ -139,7 +140,7 @@ module.exports = function style(s, gd, legend) {
139140
.data(showFill || showGradientFill ? [d] : []);
140141
fill.enter().append('path').classed('js-fill', true);
141142
fill.exit().remove();
142-
fill.attr('d', pathStart + 'h' + itemWidth + 'v6h-' + itemWidth + 'z').call(fillStyle);
143+
fill.attr('d', pathStart + 'h' + itemWidth + 'v' + itemHeight + 'h-' + itemWidth + 'z').call(fillStyle);
143144

144145
if (showLine || showGradientLine) {
145146
var lw = boundLineWidth(undefined, trace.line, MAX_LINE_WIDTH, CST_LINE_WIDTH);

test/jasmine/tests/legend_test.js

Lines changed: 147 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,21 @@ describe('legend defaults', function () {
8383
expect(layoutOut.showlegend).toBe(false);
8484
});
8585

86+
it('defaults itemheight to 6 and clamps values below the minimum', function () {
87+
fullData = allShown([{ type: 'scatter' }, { type: 'scatter' }]);
88+
89+
supplyLayoutDefaults({}, layoutOut, fullData);
90+
expect(layoutOut.legend.itemheight).toBe(6);
91+
92+
layoutOut = { font: Plots.layoutAttributes.font, bg_color: Plots.layoutAttributes.bg_color };
93+
supplyLayoutDefaults({ showlegend: true, legend: { itemheight: 1 } }, layoutOut, fullData);
94+
expect(layoutOut.legend.itemheight).toBe(6);
95+
96+
layoutOut = { font: Plots.layoutAttributes.font, bg_color: Plots.layoutAttributes.bg_color };
97+
supplyLayoutDefaults({ showlegend: true, legend: { itemheight: 24 } }, layoutOut, fullData);
98+
expect(layoutOut.legend.itemheight).toBe(24);
99+
});
100+
86101
it('shows with one visible pie', function () {
87102
fullData = allShown([{ type: 'pie' }]);
88103

@@ -3498,4 +3513,135 @@ describe('legend title click', function() {
34983513
}
34993514
}).then(done, done.fail);
35003515
});
3501-
});
3516+
});
3517+
describe('legend itemheight:', function() {
3518+
'use strict';
3519+
3520+
var gd;
3521+
3522+
beforeEach(function() {
3523+
gd = createGraphDiv();
3524+
});
3525+
3526+
afterEach(destroyGraphDiv);
3527+
3528+
function fillPathD() {
3529+
return d3Select(gd).select('g.legendfill').select('path').attr('d');
3530+
}
3531+
3532+
function linePathD() {
3533+
return d3Select(gd).select('g.legendlines').select('path').attr('d');
3534+
}
3535+
3536+
// The toggle rect is sized to the computed row height, see setRect in draw.js
3537+
function rowHeights() {
3538+
var heights = [];
3539+
d3Select(gd).selectAll('rect.legendtoggle').each(function() {
3540+
heights.push(+this.getAttribute('height'));
3541+
});
3542+
return heights;
3543+
}
3544+
3545+
var filled = [
3546+
{ x: [1, 2], y: [1, 2], fill: 'tozeroy', name: 'a' },
3547+
{ x: [1, 2], y: [2, 3], fill: 'tozeroy', name: 'b' }
3548+
];
3549+
3550+
it('reproduces the historical 6px swatch at the default', function(done) {
3551+
Plotly.newPlot(gd, filled, { showlegend: true })
3552+
.then(function() {
3553+
expect(gd._fullLayout.legend.itemheight).toBe(6);
3554+
expect(fillPathD()).toBe('M5,0h30v6h-30z');
3555+
})
3556+
.then(done, done.fail);
3557+
});
3558+
3559+
it('grows the fill swatch to the requested height', function(done) {
3560+
Plotly.newPlot(gd, filled, { showlegend: true, legend: { itemheight: 24 } })
3561+
.then(function() {
3562+
expect(fillPathD()).toBe('M5,0h30v24h-30z');
3563+
})
3564+
.then(done, done.fail);
3565+
});
3566+
3567+
it('grows the swatch downwards, leaving the line on top of the fill', function(done) {
3568+
var dfltLine;
3569+
3570+
Plotly.newPlot(gd, filled, { showlegend: true })
3571+
.then(function() {
3572+
dfltLine = linePathD();
3573+
return Plotly.relayout(gd, 'legend.itemheight', 30);
3574+
})
3575+
.then(function() {
3576+
// the line marks the top edge of the fill, exactly as in the plot itself
3577+
expect(linePathD()).toBe(dfltLine);
3578+
expect(fillPathD()).toBe('M5,0h30v30h-30z');
3579+
})
3580+
.then(done, done.fail);
3581+
});
3582+
3583+
it('does not move the swatch of a trace without fill', function(done) {
3584+
var unfilled = [
3585+
{ x: [1, 2], y: [1, 2], name: 'a' },
3586+
{ x: [1, 2], y: [2, 3], name: 'b' }
3587+
];
3588+
var dfltLine;
3589+
3590+
Plotly.newPlot(gd, unfilled, { showlegend: true })
3591+
.then(function() {
3592+
dfltLine = linePathD();
3593+
return Plotly.relayout(gd, 'legend.itemheight', 40);
3594+
})
3595+
.then(function() {
3596+
expect(linePathD()).toBe(dfltLine);
3597+
})
3598+
.then(done, done.fail);
3599+
});
3600+
3601+
it('grows each legend row so taller swatches do not overlap', function(done) {
3602+
var dflt;
3603+
3604+
Plotly.newPlot(gd, filled, { showlegend: true })
3605+
.then(function() {
3606+
dflt = rowHeights();
3607+
expect(dflt.length).toBe(2);
3608+
return Plotly.relayout(gd, 'legend.itemheight', 40);
3609+
})
3610+
.then(function() {
3611+
var grown = rowHeights();
3612+
expect(grown.length).toBe(dflt.length);
3613+
grown.forEach(function(h, i) {
3614+
expect(h).toBeGreaterThan(dflt[i]);
3615+
// Math.max(textHeight, 16, itemheight + 10) + 3
3616+
expect(h).toBe(53);
3617+
});
3618+
})
3619+
.then(done, done.fail);
3620+
});
3621+
3622+
it('leaves row heights untouched at the default', function(done) {
3623+
var dflt;
3624+
3625+
Plotly.newPlot(gd, filled, { showlegend: true })
3626+
.then(function() {
3627+
dflt = rowHeights();
3628+
return Plotly.relayout(gd, 'legend.itemheight', 6);
3629+
})
3630+
.then(function() {
3631+
expect(rowHeights()).toEqual(dflt);
3632+
})
3633+
.then(done, done.fail);
3634+
});
3635+
3636+
it('applies to unified hover labels without error', function(done) {
3637+
Plotly.newPlot(gd, filled, {
3638+
showlegend: true,
3639+
hovermode: 'x unified',
3640+
legend: { itemheight: 18 }
3641+
})
3642+
.then(function() {
3643+
expect(gd._fullLayout.legend.itemheight).toBe(18);
3644+
})
3645+
.then(done, done.fail);
3646+
});
3647+
});

test/plot-schema.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3464,6 +3464,13 @@
34643464
false
34653465
]
34663466
},
3467+
"itemheight": {
3468+
"description": "Sets the height (in px) of the legend item fill swatch. Increasing it reveals more of a trace *fill* or *fillpattern* in the legend, and grows the legend item to fit.",
3469+
"dflt": 6,
3470+
"editType": "legend",
3471+
"min": 6,
3472+
"valType": "number"
3473+
},
34673474
"itemsizing": {
34683475
"description": "Determines if the legend items symbols scale with their corresponding *trace* attributes or remain *constant* independent of the symbol size on the graph.",
34693476
"dflt": "trace",

0 commit comments

Comments
 (0)