Skip to content

feat: Allow configuration of horizontal legend max height #7359

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Mar 6, 2025
1 change: 1 addition & 0 deletions draftlogs/7359_add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Allow configuration of horizontal legend max height [[#7359](https://github.com/plotly/plotly.js/pull/7359)]
11 changes: 11 additions & 0 deletions src/components/legend/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ module.exports = {
editType: 'legend',
description: 'Sets the color of the border enclosing the legend.'
},
hmaxheight: {
valType: 'number',
min: 0,
dflt: 0.5,
editType: 'legend',
description: [
'If greater than one, it sets the max height (in px) of the horizontaly aligned legend.',
'Otherwise, it sets the max height ratio (layout * ratio) of the visible legend when horizontaly aligned.',
'Default value is 0.5; the legend will take up to 50% of the layout height before displaying a scrollbar.'
].join(' ')
},
borderwidth: {
valType: 'number',
min: 0,
Expand Down
1 change: 1 addition & 0 deletions src/components/legend/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ function groupDefaults(legendId, layoutIn, layoutOut, fullData) {

coerce('xanchor', defaultXAnchor);
coerce('yanchor', defaultYAnchor);
coerce('hmaxheight');
coerce('valign');
Lib.noneOrAll(containerIn, containerOut, ['x', 'y']);

Expand Down
13 changes: 8 additions & 5 deletions src/components/legend/draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -769,12 +769,15 @@ function computeLegendDimensions(gd, groups, traces, legendObj) {
var traceGroupGap = legendObj.tracegroupgap;
var legendGroupWidths = {};

// - if below/above plot area, give it the maximum potential margin-push value
// - if below/above plot area, give it the user defined maximum potential margin-push value
// - otherwise, extend the height of the plot area
legendObj._maxHeight = Math.max(
(isBelowPlotArea || isAbovePlotArea) ? fullLayout.height / 2 : gs.h,
30
);
if (isBelowPlotArea || isAbovePlotArea) {
legendObj._maxHeight = legendObj.hmaxheight > 1
? legendObj.hmaxheight
: fullLayout.height * legendObj.hmaxheight;
} else {
legendObj._maxHeight = Math.max(gs.h, 30);
}

var toggleRectWidth = 0;
legendObj._width = 0;
Expand Down
7 changes: 7 additions & 0 deletions test/plot-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3396,6 +3396,13 @@
"valType": "integer"
}
},
"hmaxheight": {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if there could be a more descriptive title for this config without making it crazy long? Is there a similar config that we could look at for inspiration?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just call it maxheight and only coerce it (and document that it only applies) when the orientation is 'h'? Or actually, how hard would it be to make it also apply to vertical legends? That would be useful for example if you also have a colorbar, or if you have multiple legends, and you don't want them to overlap.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could certainly apply to both horizontal and vertical legends. If we go that direction, should the percentage option use the full layout height or the plot height (gs.h)? Presently, horizontal legends use the full layout height, but vertical legends use the plot height as the max.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting - in principle for both horizontal and vertical legends it should match yref, ie plot height if yref='paper' (the default). That's what you'd want for a paper-referenced legend because they and other items that they want to avoid (such as colorbars) are positioned based on the plot height. But if yref='container', its position is based on the full height so the maxheight should be as well.

I guess it gets a bit confusing for paper-referenced horizontal legends, since (exactly when the max height matters) horizontal legends push out the bottom margin, or the top margin if positioned on top, and that means the size of the legend helps determine the plot height. It may be difficult to do that calculation, since the legend may not be the only thing altering the margins - axis labels, titles, colorbars, and rangesliders can all do that too. So perhaps we need to say that maxheight as a fraction is relative to the full height except for vertical legends when yref='paper'?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll put something together and see what it looks like, though it sounds a bit confusing for an end user. Are there other attributes with similar caveats?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another issue is that vertical and horizontal legends currently have different default max heights: 1 for vertical and 0.5 for horizontal. That would seem to preclude having one property.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Different defaults for one attribute happens a lot, you just don’t put a default in the attribute definition, you explain the conditions in the description, and you implement that logic in the supplyDefaults function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. So something like the following for this line?

coerce('maxheight', isHorizontal ? 0.5 : 1);

"description": "If greater than one, it sets the max height (in px) of the horizontaly aligned legend. Otherwise, it sets the max height ratio (layout * ratio) of the visible legend when horizontaly aligned. Default value is 0.5; the legend will take up to 50% of the layout height before displaying a scrollbar.",
"dflt": 0.5,
"editType": "legend",
"min": 0,
"valType": "number"
},
"indentation": {
"description": "Sets the indentation (in px) of the legend entries.",
"dflt": 0,
Expand Down