diff --git a/draftlogs/7072-fix.md b/draftlogs/7072-fix.md new file mode 100644 index 00000000000..f363f589e40 --- /dev/null +++ b/draftlogs/7072-fix.md @@ -0,0 +1,2 @@ + - Fix maximum dimensions for legend that is anchored to container [[#7072](https://github.com/plotly/plotly.js/pull/7072)], + with thanks to @attatrol for the contribution! \ No newline at end of file diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js index 6a8106bbcc1..a5b570d299c 100644 --- a/src/components/legend/draw.js +++ b/src/components/legend/draw.js @@ -751,6 +751,9 @@ function computeLegendDimensions(gd, groups, traces, legendObj) { legendObj = fullLayout[legendId]; } var gs = fullLayout._size; + const plotBottom = gs.b; + const plotTop = gs.b + gs.h; + const plotHeight = gs.h; var isVertical = helpers.isVertical(legendObj); var isGrouped = helpers.isGrouped(legendObj); @@ -763,19 +766,60 @@ function computeLegendDimensions(gd, groups, traces, legendObj) { var endPad = 2 * (bw + itemGap); var yanchor = getYanchor(legendObj); - var isBelowPlotArea = legendObj.y < 0 || (legendObj.y === 0 && yanchor === 'top'); - var isAbovePlotArea = legendObj.y > 1 || (legendObj.y === 1 && yanchor === 'bottom'); + let isBelowPlotArea, isAbovePlotArea; var traceGroupGap = legendObj.tracegroupgap; var legendGroupWidths = {}; - const { orientation, yref } = legendObj; + const { orientation, yref, y } = legendObj; let { maxheight } = legendObj; - const useFullLayoutHeight = isBelowPlotArea || isAbovePlotArea || orientation !== "v" || yref !== "paper" - // Set default maxheight here since it depends on values passed in by user - maxheight ||= useFullLayoutHeight ? 0.5 : 1; - const heightToBeScaled = useFullLayoutHeight ? fullLayout.height : gs.h; - legendObj._maxHeight = Math.max(maxheight > 1 ? maxheight : maxheight * heightToBeScaled, 30); + let yPixels; + + if (yref === 'paper') { + // Calculate pixel value of y position + yPixels = (y * plotHeight) + plotBottom; + // Check if legend anchor point is below or above plot area + isBelowPlotArea = yPixels < plotBottom || (yPixels === plotBottom && yanchor === 'top'); + isAbovePlotArea = yPixels > plotTop || (yPixels === plotTop && yanchor === 'bottom'); + console.log(yPixels, plotBottom,isBelowPlotArea, isAbovePlotArea); + + const useFullLayoutHeight = isBelowPlotArea || isAbovePlotArea || orientation !== "v"; + // Set default maxheight if not provided by user + maxheight ||= useFullLayoutHeight ? 0.5 : 1; + // Convert maxheight to pixels if it's a ratio (≤1), otherwise use as-is + if (maxheight <= 1) { + const referenceHeight = useFullLayoutHeight ? fullLayout.height : plotHeight; + maxheight = maxheight * referenceHeight; + } + } + else { + // Calculate pixel value of y position + yPixels = y * fullLayout.height; + // Check if legend anchor point is below or above plot area + isBelowPlotArea = yPixels < plotBottom || (yPixels === plotBottom && yanchor === 'top'); + isAbovePlotArea = yPixels > plotTop || (yPixels === plotTop && yanchor === 'bottom'); + + // Set default maxheight if not provided by user + maxheight ||= 0.5; + // If maxheight is greater than 1 (pixel value), use as is, otherwise multiply by the full layout height + if (maxheight <= 1) { + maxheight = maxheight * fullLayout.height; + } + } + + // Calculate the maximum available height based on the anchor point + let maxAvailableHeight; + if (yanchor === 'top') { + maxAvailableHeight = yPixels; + } else if (yanchor === 'bottom') { + maxAvailableHeight = fullLayout.height - yPixels; + } else { + // If yanchor is 'middle' + maxAvailableHeight = 2 * Math.min(yPixels, fullLayout.height - yPixels); + } + + maxheight = Math.min(maxheight, maxAvailableHeight); + legendObj._maxHeight = Math.max(maxheight, 30); var toggleRectWidth = 0; legendObj._width = 0; @@ -805,19 +849,30 @@ function computeLegendDimensions(gd, groups, traces, legendObj) { } } else { 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 - legendObj._maxWidth = Math.max( - isLeftOfPlotArea ? ((isBeyondPlotAreaY && xanchor === 'left') ? gs.l + gs.w : hw) : - isRightOfPlotArea ? ((isBeyondPlotAreaY && xanchor === 'right') ? gs.r + gs.w : hw) : - gs.w, - 2 * textGap); + if(legendObj.xref === 'paper') { + 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 + legendObj._maxWidth = Math.max( + isLeftOfPlotArea ? ((isBeyondPlotAreaY && xanchor === 'left') ? gs.l + gs.w : hw) : + isRightOfPlotArea ? ((isBeyondPlotAreaY && xanchor === 'right') ? gs.r + gs.w : hw) : + gs.w, + 2 * textGap); + } else { + if(xanchor === 'right') + legendObj._maxWidth = legendObj.x * fullLayout.width; + else if(xanchor === 'left') + legendObj._maxWidth = (1 - legendObj.x) * fullLayout.width; + else // if (xanchor === 'center') + legendObj._maxWidth = 2 * Math.min(1 - legendObj.x, legendObj.x) * fullLayout.width; + legendObj._maxWidth = Math.max(legendObj._maxWidth, 2 * textGap); + } + var maxItemWidth = 0; var combinedItemWidth = 0; traces.each(function(d) { diff --git a/test/image/baselines/legend_overflow_height.png b/test/image/baselines/legend_overflow_height.png new file mode 100644 index 00000000000..d3e85c9313f Binary files /dev/null and b/test/image/baselines/legend_overflow_height.png differ diff --git a/test/image/baselines/sunburst_coffee.png b/test/image/baselines/sunburst_coffee.png index 523744ad90d..590edb08ba9 100644 Binary files a/test/image/baselines/sunburst_coffee.png and b/test/image/baselines/sunburst_coffee.png differ diff --git a/test/image/baselines/treemap_sunburst_marker_colors.png b/test/image/baselines/treemap_sunburst_marker_colors.png index beebd418791..209427c1b2a 100644 Binary files a/test/image/baselines/treemap_sunburst_marker_colors.png and b/test/image/baselines/treemap_sunburst_marker_colors.png differ diff --git a/test/image/baselines/treemap_textfit.png b/test/image/baselines/treemap_textfit.png index b9b0a41090d..c5822e62271 100644 Binary files a/test/image/baselines/treemap_textfit.png and b/test/image/baselines/treemap_textfit.png differ diff --git a/test/image/mocks/legend_overflow_height.json b/test/image/mocks/legend_overflow_height.json new file mode 100644 index 00000000000..579e17c24a2 --- /dev/null +++ b/test/image/mocks/legend_overflow_height.json @@ -0,0 +1,145 @@ +{ + "data": [ + { + "x": [1, 2, 3], + "y": [40, 50, 60], + "name": "long trace name #0", + "type": "scatter" + }, + { + "x": [1, 2, 3], + "y": [41, 51, 61], + "name": "long trace name #1", + "type": "scatter" + }, + { + "x": [1, 2, 3], + "y": [42, 52, 62], + "name": "long trace name #2", + "type": "scatter" + }, + { + "x": [1, 2, 3], + "y": [43, 53, 63], + "name": "long trace name #3", + "type": "scatter" + }, + { + "x": [1, 2, 3], + "y": [44, 54, 64], + "name": "long trace name #4", + "type": "scatter" + }, + { + "x": [1, 2, 3], + "y": [45, 55, 65], + "name": "long trace name #5", + "type": "scatter" + }, + { + "x": [1, 2, 3], + "y": [46, 56, 66], + "name": "long trace name #6", + "type": "scatter" + }, + { + "x": [1, 2, 3], + "y": [47, 57, 67], + "name": "long trace name #7", + "type": "scatter" + }, + { + "x": [1, 2, 3], + "y": [48, 58, 68], + "name": "long trace name #8", + "type": "scatter" + }, + { + "x": [1, 2, 3], + "y": [49, 59, 69], + "name": "long trace name #9", + "type": "scatter" + }, + { + "x": [1, 2, 3], + "y": [50, 60, 70], + "name": "long trace name #10", + "type": "scatter" + }, + { + "x": [1, 2, 3], + "y": [51, 61, 71], + "name": "long trace name #11", + "type": "scatter" + }, + { + "x": [1, 2, 3], + "y": [52, 62, 72], + "name": "long trace name #12", + "type": "scatter" + }, + { + "x": [1, 2, 3], + "y": [53, 63, 73], + "name": "long trace name #13", + "type": "scatter" + }, + { + "x": [1, 2, 3], + "y": [54, 64, 74], + "name": "long trace name #14", + "type": "scatter" + }, + { + "x": [1, 2, 3], + "y": [55, 65, 75], + "name": "long trace name #15", + "type": "scatter" + }, + { + "x": [1, 2, 3], + "y": [56, 66, 76], + "name": "long trace name #16", + "type": "scatter" + }, + { + "x": [1, 2, 3], + "y": [57, 67, 77], + "name": "long trace name #17", + "type": "scatter" + }, + { + "x": [1, 2, 3], + "y": [58, 68, 78], + "name": "long trace name #18", + "type": "scatter" + }, + { + "x": [1, 2, 3], + "y": [59, 69, 79], + "name": "long trace name #19", + "type": "scatter" + } + ], + "layout": { + "width": 1000, + "height": 400, + "margin": { + "l": 0, + "t": 120, + "r": 0, + "b": 120, + "autoexpand": false + }, + "legend": { + "orientation": "v", + "xref": "container", + "yref": "container", + "xanchor": "left", + "yanchor": "top", + "x": 0.05, + "y": 0.25, + "bgcolor": "lightgrey" + } + } +}