Skip to content

Refactor legend draw to make input/output and optional arguments clear #5579

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 10 commits into from
Apr 6, 2021
13 changes: 7 additions & 6 deletions src/components/fx/hover.js
Original file line number Diff line number Diff line change
@@ -1010,10 +1010,10 @@ function createHoverText(hoverData, opts, gd) {
};
var mockLayoutOut = {};
legendSupplyDefaults(mockLayoutIn, mockLayoutOut, gd._fullData);
var legendOpts = mockLayoutOut.legend;
var mockLegend = mockLayoutOut.legend;

// prepare items for the legend
legendOpts.entries = [];
mockLegend.entries = [];
for(var j = 0; j < hoverData.length; j++) {
var texts = getHoverLabelText(hoverData[j], true, hovermode, fullLayout, t0);
var text = texts[0];
@@ -1039,13 +1039,14 @@ function createHoverText(hoverData, opts, gd) {
}
pt._distinct = true;

legendOpts.entries.push([pt]);
mockLegend.entries.push([pt]);
}
legendOpts.entries.sort(function(a, b) { return a[0].trace.index - b[0].trace.index;});
legendOpts.layer = container;
mockLegend.entries.sort(function(a, b) { return a[0].trace.index - b[0].trace.index;});
mockLegend.layer = container;

// Draw unified hover label
legendDraw(gd, legendOpts);
mockLegend._inHover = true;
legendDraw(gd, mockLegend);

// Position the hover
var ly = Lib.mean(hoverData.map(function(c) {return (c.y0 + c.y1) / 2;}));
308 changes: 159 additions & 149 deletions src/components/legend/draw.js
Original file line number Diff line number Diff line change
@@ -22,44 +22,49 @@ var getLegendData = require('./get_legend_data');
var style = require('./style');
var helpers = require('./helpers');

var MAIN_TITLE = 1;

module.exports = function draw(gd, opts) {
if(!opts) opts = gd._fullLayout.legend || {};
return _draw(gd, opts);
};

function _draw(gd, legendObj) {
var fullLayout = gd._fullLayout;
var clipId = 'legend' + fullLayout._uid;
var layer;

// Check whether this is the main legend (ie. called without any opts)
if(!opts) {
opts = fullLayout.legend || {};
opts._main = true;
layer = fullLayout._infolayer;
} else {
layer = opts.layer;
var inHover = legendObj._inHover;
if(inHover) {
layer = legendObj.layer;
clipId += '-hover';
} else {
layer = fullLayout._infolayer;
}

if(!layer) return;

if(!gd._legendMouseDownTime) gd._legendMouseDownTime = 0;

var legendData;
if(opts._main) {
if(!inHover) {
if(!gd.calcdata) return;
legendData = fullLayout.showlegend && getLegendData(gd.calcdata, opts);
legendData = fullLayout.showlegend && getLegendData(gd.calcdata, legendObj);
} else {
if(!opts.entries) return;
legendData = getLegendData(opts.entries, opts);
if(!legendObj.entries) return;
legendData = getLegendData(legendObj.entries, legendObj);
}

var hiddenSlices = fullLayout.hiddenlabels || [];

if(opts._main && (!fullLayout.showlegend || !legendData.length)) {
if(!inHover && (!fullLayout.showlegend || !legendData.length)) {
layer.selectAll('.legend').remove();
fullLayout._topdefs.select('#' + clipId).remove();
return Plots.autoMargin(gd, 'legend');
}

var legend = Lib.ensureSingle(layer, 'g', 'legend', function(s) {
if(opts._main) s.attr('pointer-events', 'all');
if(!inHover) s.attr('pointer-events', 'all');
});

var clipPath = Lib.ensureSingleById(fullLayout._topdefs, 'clipPath', clipId, function(s) {
@@ -69,22 +74,22 @@ module.exports = function draw(gd, opts) {
var bg = Lib.ensureSingle(legend, 'rect', 'bg', function(s) {
s.attr('shape-rendering', 'crispEdges');
});
bg.call(Color.stroke, opts.bordercolor)
.call(Color.fill, opts.bgcolor)
.style('stroke-width', opts.borderwidth + 'px');
bg.call(Color.stroke, legendObj.bordercolor)
.call(Color.fill, legendObj.bgcolor)
.style('stroke-width', legendObj.borderwidth + 'px');

var scrollBox = Lib.ensureSingle(legend, 'g', 'scrollbox');

var title = opts.title;
opts._titleWidth = 0;
opts._titleHeight = 0;
var title = legendObj.title;
legendObj._titleWidth = 0;
legendObj._titleHeight = 0;
if(title.text) {
var titleEl = Lib.ensureSingle(scrollBox, 'text', 'legendtitletext');
titleEl.attr('text-anchor', 'start')
.call(Drawing.font, title.font)
.text(title.text);

textLayout(titleEl, scrollBox, gd, opts); // handle mathjax or multi-line text and compute title height
textLayout(titleEl, scrollBox, gd, legendObj, MAIN_TITLE); // handle mathjax or multi-line text and compute title height
} else {
scrollBox.selectAll('.legendtitletext').remove();
}
@@ -110,31 +115,31 @@ module.exports = function draw(gd, opts) {
return trace.visible === 'legendonly' ? 0.5 : 1;
}
})
.each(function() { d3.select(this).call(drawTexts, gd, opts); })
.call(style, gd, opts)
.each(function() { if(opts._main) d3.select(this).call(setupTraceToggle, gd); });
.each(function() { d3.select(this).call(drawTexts, gd, legendObj); })
.call(style, gd, legendObj)
.each(function() { if(!inHover) d3.select(this).call(setupTraceToggle, gd); });

Lib.syncOrAsync([
Plots.previousPromises,
function() { return computeLegendDimensions(gd, groups, traces, opts); },
function() { return computeLegendDimensions(gd, groups, traces, legendObj); },
function() {
// IF expandMargin return a Promise (which is truthy),
// we're under a doAutoMargin redraw, so we don't have to
// draw the remaining pieces below
if(opts._main && expandMargin(gd)) return;
if(!inHover && expandMargin(gd)) return;

var gs = fullLayout._size;
var bw = opts.borderwidth;
var bw = legendObj.borderwidth;

var lx = gs.l + gs.w * opts.x - FROM_TL[getXanchor(opts)] * opts._width;
var ly = gs.t + gs.h * (1 - opts.y) - FROM_TL[getYanchor(opts)] * opts._effHeight;
var lx = gs.l + gs.w * legendObj.x - FROM_TL[getXanchor(legendObj)] * legendObj._width;
var ly = gs.t + gs.h * (1 - legendObj.y) - FROM_TL[getYanchor(legendObj)] * legendObj._effHeight;

if(opts._main && fullLayout.margin.autoexpand) {
if(!inHover && fullLayout.margin.autoexpand) {
var lx0 = lx;
var ly0 = ly;

lx = Lib.constrain(lx, 0, fullLayout.width - opts._width);
ly = Lib.constrain(ly, 0, fullLayout.height - opts._effHeight);
lx = Lib.constrain(lx, 0, fullLayout.width - legendObj._width);
ly = Lib.constrain(ly, 0, fullLayout.height - legendObj._effHeight);

if(lx !== lx0) {
Lib.log('Constrain legend.x to make legend fit inside graph');
@@ -146,21 +151,21 @@ module.exports = function draw(gd, opts) {

// Set size and position of all the elements that make up a legend:
// legend, background and border, scroll box and scroll bar as well as title
if(opts._main) Drawing.setTranslate(legend, lx, ly);
if(!inHover) Drawing.setTranslate(legend, lx, ly);

// to be safe, remove previous listeners
scrollBar.on('.drag', null);
legend.on('wheel', null);

if(!opts._main || opts._height <= opts._maxHeight || gd._context.staticPlot) {
if(inHover || legendObj._height <= legendObj._maxHeight || gd._context.staticPlot) {
// if scrollbar should not be shown.
var height = opts._effHeight;
var height = legendObj._effHeight;

// if not the main legend, let it be its full size
if(!opts._main) height = opts._height;
// if unified hover, let it be its full size
if(inHover) height = legendObj._height;

bg.attr({
width: opts._width - bw,
width: legendObj._width - bw,
height: height - bw,
x: bw / 2,
y: bw / 2
@@ -169,7 +174,7 @@ module.exports = function draw(gd, opts) {
Drawing.setTranslate(scrollBox, 0, 0);

clipPath.select('rect').attr({
width: opts._width - 2 * bw,
width: legendObj._width - 2 * bw,
height: height - 2 * bw,
x: bw,
y: bw
@@ -178,36 +183,36 @@ module.exports = function draw(gd, opts) {
Drawing.setClipUrl(scrollBox, clipId, gd);

Drawing.setRect(scrollBar, 0, 0, 0, 0);
delete opts._scrollY;
delete legendObj._scrollY;
} else {
var scrollBarHeight = Math.max(constants.scrollBarMinHeight,
opts._effHeight * opts._effHeight / opts._height);
var scrollBarYMax = opts._effHeight -
legendObj._effHeight * legendObj._effHeight / legendObj._height);
var scrollBarYMax = legendObj._effHeight -
scrollBarHeight -
2 * constants.scrollBarMargin;
var scrollBoxYMax = opts._height - opts._effHeight;
var scrollBoxYMax = legendObj._height - legendObj._effHeight;
var scrollRatio = scrollBarYMax / scrollBoxYMax;

var scrollBoxY = Math.min(opts._scrollY || 0, scrollBoxYMax);
var scrollBoxY = Math.min(legendObj._scrollY || 0, scrollBoxYMax);

// increase the background and clip-path width
// by the scrollbar width and margin
bg.attr({
width: opts._width -
width: legendObj._width -
2 * bw +
constants.scrollBarWidth +
constants.scrollBarMargin,
height: opts._effHeight - bw,
height: legendObj._effHeight - bw,
x: bw / 2,
y: bw / 2
});

clipPath.select('rect').attr({
width: opts._width -
width: legendObj._width -
2 * bw +
constants.scrollBarWidth +
constants.scrollBarMargin,
height: opts._effHeight - 2 * bw,
height: legendObj._effHeight - 2 * bw,
x: bw,
y: bw + scrollBoxY
});
@@ -219,7 +224,7 @@ module.exports = function draw(gd, opts) {
// scroll legend by mousewheel or touchpad swipe up/down
legend.on('wheel', function() {
scrollBoxY = Lib.constrain(
opts._scrollY +
legendObj._scrollY +
((d3.event.deltaY / scrollBarYMax) * scrollBoxYMax),
0, scrollBoxYMax);
scrollHandler(scrollBoxY, scrollBarHeight, scrollRatio);
@@ -285,12 +290,12 @@ module.exports = function draw(gd, opts) {
}

function scrollHandler(scrollBoxY, scrollBarHeight, scrollRatio) {
opts._scrollY = gd._fullLayout.legend._scrollY = scrollBoxY;
legendObj._scrollY = gd._fullLayout.legend._scrollY = scrollBoxY;
Drawing.setTranslate(scrollBox, 0, -scrollBoxY);

Drawing.setRect(
scrollBar,
opts._width,
legendObj._width,
constants.scrollBarMargin + scrollBoxY * scrollRatio,
constants.scrollBarWidth,
scrollBarHeight
@@ -317,8 +322,8 @@ module.exports = function draw(gd, opts) {

Drawing.setTranslate(legend, newX, newY);

xf = dragElement.align(newX, 0, gs.l, gs.l + gs.w, opts.xanchor);
yf = dragElement.align(newY, 0, gs.t + gs.h, gs.t, opts.yanchor);
xf = dragElement.align(newX, 0, gs.l, gs.l + gs.w, legendObj.xanchor);
yf = dragElement.align(newY, 0, gs.t + gs.h, gs.t, legendObj.yanchor);
},
doneFn: function() {
if(xf !== undefined && yf !== undefined) {
@@ -340,7 +345,7 @@ module.exports = function draw(gd, opts) {
});
}
}], gd);
};
}

function clickOrDoubleClick(gd, legend, legendItem, numClicks, evt) {
var trace = legendItem.data()[0][0].trace;
@@ -381,16 +386,15 @@ function clickOrDoubleClick(gd, legend, legendItem, numClicks, evt) {
}
}

function drawTexts(g, gd, opts) {
function drawTexts(g, gd, legendObj) {
var legendItem = g.data()[0][0];
var trace = legendItem.trace;
var isPieLike = Registry.traceIs(trace, 'pie-like');
var traceIndex = trace.index;
var isEditable = opts._main && gd._context.edits.legendText && !isPieLike;
var maxNameLength = opts._maxNameLength;
var isEditable = !legendObj._inHover && gd._context.edits.legendText && !isPieLike;
var maxNameLength = legendObj._maxNameLength;

var name;
if(!opts.entries) {
if(!legendObj.entries) {
name = isPieLike ? legendItem.label : trace.name;
if(trace._meta) {
name = Lib.templateString(name, trace._meta);
@@ -402,18 +406,18 @@ function drawTexts(g, gd, opts) {
var textEl = Lib.ensureSingle(g, 'text', 'legendtext');

textEl.attr('text-anchor', 'start')
.call(Drawing.font, opts.font)
.call(Drawing.font, legendObj.font)
.text(isEditable ? ensureLength(name, maxNameLength) : name);

var textGap = opts.itemwidth + constants.itemGap * 2;
var textGap = legendObj.itemwidth + constants.itemGap * 2;
svgTextUtils.positionText(textEl, textGap, 0);

if(isEditable) {
textEl.call(svgTextUtils.makeEditable, {gd: gd, text: name})
.call(textLayout, g, gd, opts)
.call(textLayout, g, gd, legendObj)
.on('edit', function(newName) {
this.text(ensureLength(newName, maxNameLength))
.call(textLayout, g, gd, opts);
.call(textLayout, g, gd, legendObj);

var fullInput = legendItem.trace._fullInput || {};
var update = {};
@@ -431,10 +435,10 @@ function drawTexts(g, gd, opts) {
update.name = newName;
}

return Registry.call('_guiRestyle', gd, update, traceIndex);
return Registry.call('_guiRestyle', gd, update, trace.index);
});
} else {
textLayout(textEl, g, gd, opts);
textLayout(textEl, g, gd, legendObj);
}
}

@@ -490,25 +494,25 @@ function setupTraceToggle(g, gd) {
});
}

function textLayout(s, g, gd, opts) {
if(!opts._main) s.attr('data-notex', true); // do not process MathJax if not main
function textLayout(s, g, gd, legendObj, aTitle) {
if(legendObj._inHover) s.attr('data-notex', true); // do not process MathJax for unified hover
svgTextUtils.convertToTspans(s, gd, function() {
computeTextDimensions(g, gd, opts);
computeTextDimensions(g, gd, legendObj, aTitle);
});
}

function computeTextDimensions(g, gd, opts) {
function computeTextDimensions(g, gd, legendObj, aTitle) {
var legendItem = g.data()[0][0];
if(opts._main && legendItem && !legendItem.trace.showlegend) {
if(!legendObj._inHover && legendItem && !legendItem.trace.showlegend) {
g.remove();
return;
}

var mathjaxGroup = g.select('g[class*=math-group]');
var mathjaxNode = mathjaxGroup.node();
if(!opts) opts = gd._fullLayout.legend;
var bw = opts.borderwidth;
var lineHeight = (legendItem ? opts : opts.title).font.size * LINE_SPACING;
if(!legendObj) legendObj = gd._fullLayout.legend;
var bw = legendObj.borderwidth;
var lineHeight = (aTitle === MAIN_TITLE ? legendObj.title : legendObj).font.size * LINE_SPACING;
var height, width;

if(mathjaxNode) {
@@ -517,14 +521,14 @@ function computeTextDimensions(g, gd, opts) {
height = mathjaxBB.height;
width = mathjaxBB.width;

if(legendItem) {
if(aTitle === MAIN_TITLE) {
Drawing.setTranslate(mathjaxGroup, bw, bw + height * 0.75);
} else { // legend item
Drawing.setTranslate(mathjaxGroup, 0, height * 0.25);
} else { // case of title
Drawing.setTranslate(mathjaxGroup, bw, height * 0.75 + bw);
}
} else {
var textEl = g.select(legendItem ?
'.legendtext' : '.legendtitletext'
var textEl = g.select(aTitle === MAIN_TITLE ?
'.legendtitletext' : '.legendtext'
);
var textLines = svgTextUtils.lineCount(textEl);
var textNode = textEl.node();
@@ -534,36 +538,40 @@ function computeTextDimensions(g, gd, opts) {

// approximation to height offset to center the font
// to avoid getBoundingClientRect
var textY = lineHeight * ((textLines - 1) / 2 - 0.3);
if(legendItem) {
var textGap = opts.itemwidth + constants.itemGap * 2;
svgTextUtils.positionText(textEl, textGap, -textY);
} else { // case of title
svgTextUtils.positionText(textEl, constants.titlePad + bw, lineHeight + bw);
if(aTitle === MAIN_TITLE) {
svgTextUtils.positionText(textEl,
bw + constants.titlePad,
bw + lineHeight
);
} else { // legend item
svgTextUtils.positionText(textEl,
legendObj.itemwidth + constants.itemGap * 2,
-lineHeight * ((textLines - 1) / 2 - 0.3)
);
}
}

if(legendItem) {
if(aTitle === MAIN_TITLE) {
legendObj._titleWidth = width;
legendObj._titleHeight = height;
} else { // legend item
legendItem.lineHeight = lineHeight;
legendItem.height = Math.max(height, 16) + 3;
legendItem.width = width;
} else { // case of title
opts._titleWidth = width;
opts._titleHeight = height;
}
}

function getTitleSize(opts) {
function getTitleSize(legendObj) {
var w = 0;
var h = 0;

var side = opts.title.side;
var side = legendObj.title.side;
if(side) {
if(side.indexOf('left') !== -1) {
w = opts._titleWidth;
w = legendObj._titleWidth;
}
if(side.indexOf('top') !== -1) {
h = opts._titleHeight;
h = legendObj._titleHeight;
}
}

@@ -580,68 +588,70 @@ function getTitleSize(opts) {
* - _width: legend width
* - _maxWidth (for orientation:h only): maximum width before starting new row
*/
function computeLegendDimensions(gd, groups, traces, opts) {
function computeLegendDimensions(gd, groups, traces, legendObj) {
var fullLayout = gd._fullLayout;
if(!opts) opts = fullLayout.legend;
if(!legendObj) legendObj = fullLayout.legend;
var gs = fullLayout._size;

var isVertical = helpers.isVertical(opts);
var isGrouped = helpers.isGrouped(opts);
var isVertical = helpers.isVertical(legendObj);
var isGrouped = helpers.isGrouped(legendObj);

var bw = opts.borderwidth;
var bw = legendObj.borderwidth;
var bw2 = 2 * bw;
var itemGap = constants.itemGap;
var textGap = opts.itemwidth + itemGap * 2;
var textGap = legendObj.itemwidth + itemGap * 2;
var endPad = 2 * (bw + itemGap);

var yanchor = getYanchor(opts);
var isBelowPlotArea = opts.y < 0 || (opts.y === 0 && yanchor === 'top');
var isAbovePlotArea = opts.y > 1 || (opts.y === 1 && yanchor === 'bottom');
var yanchor = getYanchor(legendObj);
var isBelowPlotArea = legendObj.y < 0 || (legendObj.y === 0 && yanchor === 'top');
var isAbovePlotArea = legendObj.y > 1 || (legendObj.y === 1 && yanchor === 'bottom');

var traceGroupGap = legendObj.tracegroupgap;

// - if below/above plot area, give it the maximum potential margin-push value
// - otherwise, extend the height of the plot area
opts._maxHeight = Math.max(
legendObj._maxHeight = Math.max(
(isBelowPlotArea || isAbovePlotArea) ? fullLayout.height / 2 : gs.h,
30
);

var toggleRectWidth = 0;
opts._width = 0;
opts._height = 0;
var titleSize = getTitleSize(opts);
legendObj._width = 0;
legendObj._height = 0;
var titleSize = getTitleSize(legendObj);

if(isVertical) {
traces.each(function(d) {
var h = d[0].height;
Drawing.setTranslate(this,
bw + titleSize[0],
bw + titleSize[1] + opts._height + h / 2 + itemGap
bw + titleSize[1] + legendObj._height + h / 2 + itemGap
);
opts._height += h;
opts._width = Math.max(opts._width, d[0].width);
legendObj._height += h;
legendObj._width = Math.max(legendObj._width, d[0].width);
});

toggleRectWidth = textGap + opts._width;
opts._width += itemGap + textGap + bw2;
opts._height += endPad;
toggleRectWidth = textGap + legendObj._width;
legendObj._width += itemGap + textGap + bw2;
legendObj._height += endPad;

if(isGrouped) {
groups.each(function(d, i) {
Drawing.setTranslate(this, 0, i * opts.tracegroupgap);
Drawing.setTranslate(this, 0, i * legendObj.tracegroupgap);
});
opts._height += (opts._lgroupsLength - 1) * opts.tracegroupgap;
legendObj._height += (legendObj._lgroupsLength - 1) * legendObj.tracegroupgap;
}
} else {
var xanchor = getXanchor(opts);
var isLeftOfPlotArea = opts.x < 0 || (opts.x === 0 && xanchor === 'right');
var isRightOfPlotArea = opts.x > 1 || (opts.x === 1 && xanchor === 'left');
var xanchor = getXanchor(legendObj);
var isLeftOfPlotArea = legendObj.x < 0 || (legendObj.x === 0 && xanchor === 'right');
var isRightOfPlotArea = legendObj.x > 1 || (legendObj.x === 1 && xanchor === 'left');
var isBeyondPlotAreaY = isAbovePlotArea || isBelowPlotArea;
var hw = fullLayout.width / 2;

// - if placed within x-margins, extend the width of the plot area
// - else if below/above plot area and anchored in the margin, extend to opposite margin,
// - otherwise give it the maximum potential margin-push value
opts._maxWidth = Math.max(
legendObj._maxWidth = Math.max(
isLeftOfPlotArea ? ((isBeyondPlotAreaY && xanchor === 'left') ? gs.l + gs.w : hw) :
isRightOfPlotArea ? ((isBeyondPlotAreaY && xanchor === 'right') ? gs.r + gs.w : hw) :
gs.w,
@@ -677,10 +687,10 @@ function computeLegendDimensions(gd, groups, traces, opts) {

var next = maxWidthInGroup + itemGap;

if((next + bw + groupOffsetX) > opts._maxWidth) {
if((next + bw + groupOffsetX) > legendObj._maxWidth) {
maxRowWidth = Math.max(maxRowWidth, groupOffsetX);
groupOffsetX = 0;
groupOffsetY += maxGroupHeightInRow + opts.tracegroupgap;
groupOffsetY += maxGroupHeightInRow + traceGroupGap;
maxGroupHeightInRow = offsetY;
}

@@ -689,11 +699,11 @@ function computeLegendDimensions(gd, groups, traces, opts) {
groupOffsetX += next;
});

opts._width = Math.max(maxRowWidth, groupOffsetX) + bw;
opts._height = groupOffsetY + maxGroupHeightInRow + endPad;
legendObj._width = Math.max(maxRowWidth, groupOffsetX) + bw;
legendObj._height = groupOffsetY + maxGroupHeightInRow + endPad;
} else {
var nTraces = traces.size();
var oneRowLegend = (combinedItemWidth + bw2 + (nTraces - 1) * itemGap) < opts._maxWidth;
var oneRowLegend = (combinedItemWidth + bw2 + (nTraces - 1) * itemGap) < legendObj._maxWidth;

var maxItemHeightInRow = 0;
var offsetX = 0;
@@ -704,11 +714,11 @@ function computeLegendDimensions(gd, groups, traces, opts) {
var w = textGap + d[0].width;
var next = (oneRowLegend ? w : maxItemWidth) + itemGap;

if((next + bw + offsetX - itemGap) >= opts._maxWidth) {
if((next + bw + offsetX - itemGap) >= legendObj._maxWidth) {
maxRowWidth = Math.max(maxRowWidth, rowWidth);
offsetX = 0;
offsetY += maxItemHeightInRow;
opts._height += maxItemHeightInRow;
legendObj._height += maxItemHeightInRow;
maxItemHeightInRow = 0;
}

@@ -723,30 +733,30 @@ function computeLegendDimensions(gd, groups, traces, opts) {
});

if(oneRowLegend) {
opts._width = offsetX + bw2;
opts._height = maxItemHeightInRow + endPad;
legendObj._width = offsetX + bw2;
legendObj._height = maxItemHeightInRow + endPad;
} else {
opts._width = Math.max(maxRowWidth, rowWidth) + bw2;
opts._height += maxItemHeightInRow + endPad;
legendObj._width = Math.max(maxRowWidth, rowWidth) + bw2;
legendObj._height += maxItemHeightInRow + endPad;
}
}
}

opts._width = Math.ceil(
legendObj._width = Math.ceil(
Math.max(
opts._width + titleSize[0],
opts._titleWidth + 2 * (bw + constants.titlePad)
legendObj._width + titleSize[0],
legendObj._titleWidth + 2 * (bw + constants.titlePad)
)
);

opts._height = Math.ceil(
legendObj._height = Math.ceil(
Math.max(
opts._height + titleSize[1],
opts._titleHeight + 2 * (bw + constants.itemGap)
legendObj._height + titleSize[1],
legendObj._titleHeight + 2 * (bw + constants.itemGap)
)
);

opts._effHeight = Math.min(opts._height, opts._maxHeight);
legendObj._effHeight = Math.min(legendObj._height, legendObj._maxHeight);

var edits = gd._context.edits;
var isEditable = edits.legendText || edits.legendPosition;
@@ -761,28 +771,28 @@ function computeLegendDimensions(gd, groups, traces, opts) {

function expandMargin(gd) {
var fullLayout = gd._fullLayout;
var opts = fullLayout.legend;
var xanchor = getXanchor(opts);
var yanchor = getYanchor(opts);
var legendObj = fullLayout.legend;
var xanchor = getXanchor(legendObj);
var yanchor = getYanchor(legendObj);

return Plots.autoMargin(gd, 'legend', {
x: opts.x,
y: opts.y,
l: opts._width * (FROM_TL[xanchor]),
r: opts._width * (FROM_BR[xanchor]),
b: opts._effHeight * (FROM_BR[yanchor]),
t: opts._effHeight * (FROM_TL[yanchor])
x: legendObj.x,
y: legendObj.y,
l: legendObj._width * (FROM_TL[xanchor]),
r: legendObj._width * (FROM_BR[xanchor]),
b: legendObj._effHeight * (FROM_BR[yanchor]),
t: legendObj._effHeight * (FROM_TL[yanchor])
});
}

function getXanchor(opts) {
return Lib.isRightAnchor(opts) ? 'right' :
Lib.isCenterAnchor(opts) ? 'center' :
function getXanchor(legendObj) {
return Lib.isRightAnchor(legendObj) ? 'right' :
Lib.isCenterAnchor(legendObj) ? 'center' :
'left';
}

function getYanchor(opts) {
return Lib.isBottomAnchor(opts) ? 'bottom' :
Lib.isMiddleAnchor(opts) ? 'middle' :
function getYanchor(legendObj) {
return Lib.isBottomAnchor(legendObj) ? 'bottom' :
Lib.isMiddleAnchor(legendObj) ? 'middle' :
'top';
}
3 changes: 1 addition & 2 deletions src/components/legend/get_legend_data.js
Original file line number Diff line number Diff line change
@@ -11,7 +11,6 @@ module.exports = function getLegendData(calcdata, opts) {
var lgroupi = 0;
var maxNameLength = 0;
var i, j;
var main = opts._main;

function addOneItem(legendGroup, legendItem) {
// each '' legend group is treated as a separate group
@@ -37,7 +36,7 @@ module.exports = function getLegendData(calcdata, opts) {
var trace = cd0.trace;
var lgroup = trace.legendgroup;

if(main && (!trace.visible || !trace.showlegend)) continue;
if(!opts._inHover && (!trace.visible || !trace.showlegend)) continue;

if(Registry.traceIs(trace, 'pie-like')) {
if(!slicesShown[lgroup]) slicesShown[lgroup] = {};