From 5c94e46512874ce66ec13c40eddbe889fceadb57 Mon Sep 17 00:00:00 2001 From: hannahker Date: Mon, 1 May 2023 10:33:43 -0700 Subject: [PATCH 01/13] Add attributes to plot schema --- src/components/legend/attributes.js | 22 ++++++++++++++++++++++ test/plot-schema.json | 20 ++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/components/legend/attributes.js b/src/components/legend/attributes.js index 135f1209b69..14340a3d207 100644 --- a/src/components/legend/attributes.js +++ b/src/components/legend/attributes.js @@ -168,6 +168,17 @@ module.exports = { 'defaults to *0* for horizontal legends.' ].join(' ') }, + xref: { + valType: 'enumerated', + dflt: 'container', + values: ['container', 'paper'], + editType: 'layoutstyle', + description: [ + 'Sets the container `x` refers to.', + '*container* spans the entire `width` of the plot.', + '*paper* refers to the width of the plotting area only.' + ].join(' ') + }, xanchor: { valType: 'enumerated', values: ['auto', 'left', 'center', 'right'], @@ -194,6 +205,17 @@ module.exports = { 'defaults to *1.1* for horizontal legends on graph with one or multiple range sliders.' ].join(' ') }, + yref: { + valType: 'enumerated', + dflt: 'container', + values: ['container', 'paper'], + editType: 'layoutstyle', + description: [ + 'Sets the container `y` refers to.', + '*container* spans the entire `height` of the plot.', + '*paper* refers to the height of the plotting area only.' + ].join(' ') + }, yanchor: { valType: 'enumerated', values: ['auto', 'top', 'middle', 'bottom'], diff --git a/test/plot-schema.json b/test/plot-schema.json index 8a2c9e85e44..b58fae68a2a 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -2982,6 +2982,16 @@ "right" ] }, + "xref": { + "description": "Sets the container `x` refers to. *container* spans the entire `width` of the plot. *paper* refers to the width of the plotting area only.", + "dflt": "container", + "editType": "layoutstyle", + "valType": "enumerated", + "values": [ + "container", + "paper" + ] + }, "y": { "description": "Sets the y position (in normalized coordinates) of the legend. Defaults to *1* for vertical legends, defaults to *-0.1* for horizontal legends on graphs w/o range sliders and defaults to *1.1* for horizontal legends on graph with one or multiple range sliders.", "editType": "legend", @@ -2999,6 +3009,16 @@ "middle", "bottom" ] + }, + "yref": { + "description": "Sets the container `y` refers to. *container* spans the entire `height` of the plot. *paper* refers to the height of the plotting area only.", + "dflt": "container", + "editType": "layoutstyle", + "valType": "enumerated", + "values": [ + "container", + "paper" + ] } }, "mapbox": { From 8f06d8aaf1ac5be7cc31bdd251f2f0e70cf4960f Mon Sep 17 00:00:00 2001 From: hannahker Date: Mon, 1 May 2023 15:39:49 -0700 Subject: [PATCH 02/13] Adjust default setting and compute position based on xref and yref --- src/components/legend/attributes.js | 21 +++++++++++------- src/components/legend/defaults.js | 32 +++++++++++++++++++++++----- src/components/legend/draw.js | 16 ++++++++++++-- test/image/mocks/legend_outside.json | 12 ++++++++--- test/plot-schema.json | 8 +++---- 5 files changed, 67 insertions(+), 22 deletions(-) diff --git a/src/components/legend/attributes.js b/src/components/legend/attributes.js index 14340a3d207..d9f441073e2 100644 --- a/src/components/legend/attributes.js +++ b/src/components/legend/attributes.js @@ -163,14 +163,17 @@ module.exports = { max: 3, editType: 'legend', description: [ - 'Sets the x position (in normalized coordinates) of the legend.', - 'Defaults to *1.02* for vertical legends and', - 'defaults to *0* for horizontal legends.' + 'Sets the x position with respect to `xref` (in normalized coordinates) of the legend.', + 'When `xref` is *paper*, defaults to *1.02* for vertical legends and', + 'defaults to *0* for horizontal legends.', + 'When `xref` is *container*, defaults to *1* for vertical legends and', + 'defaults to *0* for horizontal legends.', + 'Must be between *0* and *1* if `xref` is *container*.' ].join(' ') }, xref: { valType: 'enumerated', - dflt: 'container', + dflt: 'paper', values: ['container', 'paper'], editType: 'layoutstyle', description: [ @@ -199,15 +202,17 @@ module.exports = { max: 3, editType: 'legend', description: [ - 'Sets the y position (in normalized coordinates) of the legend.', - 'Defaults to *1* for vertical legends,', + 'Sets the y position with respect to `yref` (in normalized coordinates) of the legend.', + 'When `yref` is *paper*, defaults to *1* for vertical legends,', 'defaults to *-0.1* for horizontal legends on graphs w/o range sliders and', - 'defaults to *1.1* for horizontal legends on graph with one or multiple range sliders.' + 'defaults to *1.1* for horizontal legends on graph with one or multiple range sliders.', + 'When `yref` is *container*, defaults to *1*.', + 'Must be between *0* and *1* if `yref` is *container*.' ].join(' ') }, yref: { valType: 'enumerated', - dflt: 'container', + dflt: 'paper', values: ['container', 'paper'], editType: 'layoutstyle', description: [ diff --git a/src/components/legend/defaults.js b/src/components/legend/defaults.js index 3906070cb1c..b1a46dc0521 100644 --- a/src/components/legend/defaults.js +++ b/src/components/legend/defaults.js @@ -101,26 +101,48 @@ function groupDefaults(legendId, layoutIn, layoutOut, fullData) { coerce('borderwidth'); var orientation = coerce('orientation'); + + var yref = coerce('yref'); + var xref = coerce('xref'); + var isHorizontal = orientation === 'h'; + var isPaperY = yref === 'paper'; + var isPaperX = xref === 'paper'; var defaultX, defaultY, defaultYAnchor; + // TODO: Adjust default xanchor if needed for container ref? + // TODO: Constrain x or y if container ref to be within 0-1 if(isHorizontal) { defaultX = 0; if(Registry.getComponentMethod('rangeslider', 'isVisible')(layoutIn.xaxis)) { - defaultY = 1.1; - defaultYAnchor = 'bottom'; + if(isPaperY) { + defaultY = 1.1; + defaultYAnchor = 'bottom'; + } else { + defaultY = 1; + defaultYAnchor = 'top'; + } } else { // maybe use y=1.1 / yanchor=bottom as above // to avoid https://github.com/plotly/plotly.js/issues/1199 // in v3 - defaultY = -0.1; - defaultYAnchor = 'top'; + if(isPaperY) { + defaultY = -0.1; + defaultYAnchor = 'top'; + } else { + defaultY = 0; + defaultYAnchor = 'bottom'; + } } } else { - defaultX = 1.02; defaultY = 1; defaultYAnchor = 'auto'; + if(isPaperX) { + defaultX = 1.02; + } else { + defaultX = 1; + } } coerce('traceorder', defaultOrder); diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js index dcb9093b927..7c1908e2ef6 100644 --- a/src/components/legend/draw.js +++ b/src/components/legend/draw.js @@ -163,9 +163,21 @@ function drawOne(gd, opts) { // draw the remaining pieces below if(expMargin) return; - 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(legendObj.xref === 'paper') { + var lx = gs.l + gs.w * legendObj.x - FROM_TL[getXanchor(legendObj)] * legendObj._width; + } else { + legendObj.x = Lib.constrain(legendObj.x, 0, 1); // TODO: Move this to defaults setting? + var lx = fullLayout.width * legendObj.x - FROM_TL[getXanchor(legendObj)] * legendObj._width; + } + + if(legendObj.yref === 'paper') { + var ly = gs.t + gs.h * (1 - legendObj.y) - FROM_TL[getYanchor(legendObj)] * legendObj._effHeight; + } else { + legendObj.y = Lib.constrain(legendObj.y, 0, 1); // TODO: Move this to defaults setting? + var ly = fullLayout.height * (1 - legendObj.y) - FROM_TL[getYanchor(legendObj)] * legendObj._effHeight; + } + // TODO: Does this also apply if y/xref=container? if(fullLayout.margin.autoexpand) { var lx0 = lx; var ly0 = ly; diff --git a/test/image/mocks/legend_outside.json b/test/image/mocks/legend_outside.json index 3ac49c11e3c..051ec7497eb 100644 --- a/test/image/mocks/legend_outside.json +++ b/test/image/mocks/legend_outside.json @@ -54,9 +54,15 @@ "layout":{ "showlegend":true, "legend":{ - "x":1, - "y":1, - "xanchor":"left" + "x": 0, + "y": 0, + "xref": "container", + "yref": "container", + "orientation": "h" + }, + "margin": { + "t": 75, + "r": 115 } } } diff --git a/test/plot-schema.json b/test/plot-schema.json index b58fae68a2a..293a56522b1 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -2964,7 +2964,7 @@ "valType": "boolean" }, "x": { - "description": "Sets the x position (in normalized coordinates) of the legend. Defaults to *1.02* for vertical legends and defaults to *0* for horizontal legends.", + "description": "Sets the x position with respect to `xref` (in normalized coordinates) of the legend. When `xref` is *paper*, defaults to *1.02* for vertical legends and defaults to *0* for horizontal legends. When `xref` is *container*, defaults to *1* for vertical legends and defaults to *0* for horizontal legends. Must be between *0* and *1* if `xref` is *container*.", "editType": "legend", "max": 3, "min": -2, @@ -2984,7 +2984,7 @@ }, "xref": { "description": "Sets the container `x` refers to. *container* spans the entire `width` of the plot. *paper* refers to the width of the plotting area only.", - "dflt": "container", + "dflt": "paper", "editType": "layoutstyle", "valType": "enumerated", "values": [ @@ -2993,7 +2993,7 @@ ] }, "y": { - "description": "Sets the y position (in normalized coordinates) of the legend. Defaults to *1* for vertical legends, defaults to *-0.1* for horizontal legends on graphs w/o range sliders and defaults to *1.1* for horizontal legends on graph with one or multiple range sliders.", + "description": "Sets the y position with respect to `yref` (in normalized coordinates) of the legend. When `yref` is *paper*, defaults to *1* for vertical legends, defaults to *-0.1* for horizontal legends on graphs w/o range sliders and defaults to *1.1* for horizontal legends on graph with one or multiple range sliders. When `yref` is *container*, defaults to *1*. Must be between *0* and *1* if `yref` is *container*.", "editType": "legend", "max": 3, "min": -2, @@ -3012,7 +3012,7 @@ }, "yref": { "description": "Sets the container `y` refers to. *container* spans the entire `height` of the plot. *paper* refers to the height of the plotting area only.", - "dflt": "container", + "dflt": "paper", "editType": "layoutstyle", "valType": "enumerated", "values": [ From 24542718747893ccc0cd4744013682a6e5c0e12a Mon Sep 17 00:00:00 2001 From: hannahker Date: Mon, 1 May 2023 15:41:41 -0700 Subject: [PATCH 03/13] Fix syntax and revert change to existing mock --- src/components/legend/draw.js | 10 ++++++---- test/image/mocks/legend_outside.json | 12 +++--------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js index 7c1908e2ef6..9bb628035ec 100644 --- a/src/components/legend/draw.js +++ b/src/components/legend/draw.js @@ -163,18 +163,20 @@ function drawOne(gd, opts) { // draw the remaining pieces below if(expMargin) return; + var lx, ly; + if(legendObj.xref === 'paper') { - var lx = gs.l + gs.w * legendObj.x - FROM_TL[getXanchor(legendObj)] * legendObj._width; + lx = gs.l + gs.w * legendObj.x - FROM_TL[getXanchor(legendObj)] * legendObj._width; } else { legendObj.x = Lib.constrain(legendObj.x, 0, 1); // TODO: Move this to defaults setting? - var lx = fullLayout.width * legendObj.x - FROM_TL[getXanchor(legendObj)] * legendObj._width; + lx = fullLayout.width * legendObj.x - FROM_TL[getXanchor(legendObj)] * legendObj._width; } if(legendObj.yref === 'paper') { - var ly = gs.t + gs.h * (1 - legendObj.y) - FROM_TL[getYanchor(legendObj)] * legendObj._effHeight; + ly = gs.t + gs.h * (1 - legendObj.y) - FROM_TL[getYanchor(legendObj)] * legendObj._effHeight; } else { legendObj.y = Lib.constrain(legendObj.y, 0, 1); // TODO: Move this to defaults setting? - var ly = fullLayout.height * (1 - legendObj.y) - FROM_TL[getYanchor(legendObj)] * legendObj._effHeight; + ly = fullLayout.height * (1 - legendObj.y) - FROM_TL[getYanchor(legendObj)] * legendObj._effHeight; } // TODO: Does this also apply if y/xref=container? diff --git a/test/image/mocks/legend_outside.json b/test/image/mocks/legend_outside.json index 051ec7497eb..3ac49c11e3c 100644 --- a/test/image/mocks/legend_outside.json +++ b/test/image/mocks/legend_outside.json @@ -54,15 +54,9 @@ "layout":{ "showlegend":true, "legend":{ - "x": 0, - "y": 0, - "xref": "container", - "yref": "container", - "orientation": "h" - }, - "margin": { - "t": 75, - "r": 115 + "x":1, + "y":1, + "xanchor":"left" } } } From 09cb67993476bad08ce3066ab7e19c239f30bb3f Mon Sep 17 00:00:00 2001 From: hannahker Date: Tue, 2 May 2023 13:01:03 -0700 Subject: [PATCH 04/13] Add support for reservedMargins when container ref'd --- src/components/legend/draw.js | 71 ++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 21 deletions(-) diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js index 9bb628035ec..1a45d62b62f 100644 --- a/src/components/legend/draw.js +++ b/src/components/legend/draw.js @@ -154,38 +154,39 @@ function drawOne(gd, opts) { function() { var gs = fullLayout._size; var bw = legendObj.borderwidth; + var isPaperX = legendObj.xref === 'paper'; + var isPaperY = legendObj.yref === 'paper'; if(!inHover) { - var expMargin = expandMargin(gd, legendId); - - // 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(expMargin) return; - var lx, ly; - if(legendObj.xref === 'paper') { + if(isPaperX) { lx = gs.l + gs.w * legendObj.x - FROM_TL[getXanchor(legendObj)] * legendObj._width; } else { legendObj.x = Lib.constrain(legendObj.x, 0, 1); // TODO: Move this to defaults setting? lx = fullLayout.width * legendObj.x - FROM_TL[getXanchor(legendObj)] * legendObj._width; } - if(legendObj.yref === 'paper') { + if(isPaperY) { ly = gs.t + gs.h * (1 - legendObj.y) - FROM_TL[getYanchor(legendObj)] * legendObj._effHeight; } else { legendObj.y = Lib.constrain(legendObj.y, 0, 1); // TODO: Move this to defaults setting? ly = fullLayout.height * (1 - legendObj.y) - FROM_TL[getYanchor(legendObj)] * legendObj._effHeight; } - // TODO: Does this also apply if y/xref=container? + var expMargin = expandMargin(gd, legendId, lx, ly); + + // 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(expMargin) return; + if(fullLayout.margin.autoexpand) { var lx0 = lx; var ly0 = ly; - lx = Lib.constrain(lx, 0, fullLayout.width - legendObj._width); - ly = Lib.constrain(ly, 0, fullLayout.height - legendObj._effHeight); + lx = isPaperX ? Lib.constrain(lx, 0, fullLayout.width - legendObj._width) : lx0; + ly = isPaperY ? Lib.constrain(ly, 0, fullLayout.height - legendObj._effHeight) : ly0; if(lx !== lx0) { Lib.log('Constrain ' + legendId + '.x to make legend fit inside graph'); @@ -890,20 +891,48 @@ function computeLegendDimensions(gd, groups, traces, legendObj) { }); } -function expandMargin(gd, legendId) { +function expandMargin(gd, legendId, lx, ly) { var fullLayout = gd._fullLayout; var legendObj = fullLayout[legendId]; var xanchor = getXanchor(legendObj); var yanchor = getYanchor(legendObj); - return Plots.autoMargin(gd, legendId, { - 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]) - }); + var isPaperX = legendObj.xref === 'paper'; + var isPaperY = legendObj.yref === 'paper'; + + gd._fullLayout._reservedMargin[legendId] = {}; + var sideY = legendObj.y < 0.5 ? 'b' : 't'; + var sideX = legendObj.x < 0.5 ? 'l' : 'r'; + var possibleReservedMargins = { + r: (fullLayout.width - lx), + l: lx + legendObj._width, + b: (fullLayout.height - ly), + t: ly + legendObj._effHeight + }; + + if(isPaperX && isPaperY) { + return Plots.autoMargin(gd, legendId, { + 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]) + }); + } else if(isPaperX) { + gd._fullLayout._reservedMargin[legendId][sideY] = possibleReservedMargins[sideY]; + return; + } else if(isPaperY) { + gd._fullLayout._reservedMargin[legendId][sideX] = possibleReservedMargins[sideX]; + return; + } else { + if(legendObj.orientation === 'v') { + gd._fullLayout._reservedMargin[legendId][sideX] = possibleReservedMargins[sideX]; + } else { + gd._fullLayout._reservedMargin[legendId][sideY] = possibleReservedMargins[sideY]; + } + return; + } } function getXanchor(legendObj) { From 06f63c7d800f6d364894b331b656ac17bf7390cf Mon Sep 17 00:00:00 2001 From: hannahker Date: Tue, 2 May 2023 15:10:06 -0700 Subject: [PATCH 05/13] Set default for xanchor dynamically --- src/components/legend/defaults.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/legend/defaults.js b/src/components/legend/defaults.js index b1a46dc0521..118975bfdec 100644 --- a/src/components/legend/defaults.js +++ b/src/components/legend/defaults.js @@ -109,6 +109,7 @@ function groupDefaults(legendId, layoutIn, layoutOut, fullData) { var isPaperY = yref === 'paper'; var isPaperX = xref === 'paper'; var defaultX, defaultY, defaultYAnchor; + var defaultXAnchor = 'left'; // TODO: Adjust default xanchor if needed for container ref? // TODO: Constrain x or y if container ref to be within 0-1 @@ -142,6 +143,7 @@ function groupDefaults(legendId, layoutIn, layoutOut, fullData) { defaultX = 1.02; } else { defaultX = 1; + defaultXAnchor = 'right'; } } @@ -158,7 +160,7 @@ function groupDefaults(legendId, layoutIn, layoutOut, fullData) { coerce('groupclick'); coerce('x', defaultX); - coerce('xanchor'); + coerce('xanchor', defaultXAnchor); coerce('y', defaultY); coerce('yanchor', defaultYAnchor); coerce('valign'); From 9c69c766dc7e3afee95f8288962d96e062719b00 Mon Sep 17 00:00:00 2001 From: hannahker Date: Tue, 2 May 2023 15:10:20 -0700 Subject: [PATCH 06/13] Add mock --- test/image/mocks/zz-container-legend.json | 75 +++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 test/image/mocks/zz-container-legend.json diff --git a/test/image/mocks/zz-container-legend.json b/test/image/mocks/zz-container-legend.json new file mode 100644 index 00000000000..08bd25d8694 --- /dev/null +++ b/test/image/mocks/zz-container-legend.json @@ -0,0 +1,75 @@ +{ + "data": [ + { + "y": [0] + }, + { + "y": [1] + }, + { + "y": [2] + }, + { + "y": [3], + "legend": "legend2" + }, + { + "y": [4], + "legend": "legend3" + }, + { + "y": [5], + "legend": "legend3" + } + ], + "layout": { + "margin": {"t": 0, "b": 0, "r": 0, "l": 0}, + "title": { + "text": "Multiple legends | Legends 1 & 2 with container ref", + "automargin": true, + "yref": "container" + }, + "width": 500, + "height": 500, + "yaxis": { + "autorange": "reversed", + "title": {"text": "Long axis title with standoff", "font": {"size": 24}, "standoff": 25}, + "side": "right", + "automargin": true + }, + "xaxis": {"title": {"text": "Xaxis title"}, "automargin": true}, + "legend": { + "bgcolor": "lightgray", + "xref": "container", + "title": { + "text": "Legend" + } + }, + "legend2": { + "x": 0, + "y": 0.5, + "xanchor": "right", + "yanchor": "top", + "bgcolor": "lightblue", + "title": { + "text": "Legend 2" + } + }, + "legend3": { + "y": 0, + "x": 0.5, + "orientation": "h", + "yref": "container", + "xref": "container", + "xanchor": "center", + "bgcolor": "yellow", + "title": { + "text": "Legend 3" + } + }, + "hovermode": "x unified" + }, + "config": { + "editable": true + } +} From ae785dcef99e8f6752454f522b355c02eb1de0d5 Mon Sep 17 00:00:00 2001 From: hannahker Date: Tue, 2 May 2023 15:31:46 -0700 Subject: [PATCH 07/13] Add baseline for mock --- test/image/baselines/zz-container-legend.png | Bin 0 -> 34610 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/image/baselines/zz-container-legend.png diff --git a/test/image/baselines/zz-container-legend.png b/test/image/baselines/zz-container-legend.png new file mode 100644 index 0000000000000000000000000000000000000000..2d73c25edc8feb7eb31d738bd1605eacfa0e36d3 GIT binary patch literal 34610 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

Szluxsp&)AwIe8$Kw=oKPlU(bG0BX=CL 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 literal 0 HcmV?d00001 From fd486dd736f23da38b1a325405fcf78df2ec2be5 Mon Sep 17 00:00:00 2001 From: hannahker Date: Tue, 2 May 2023 16:00:07 -0700 Subject: [PATCH 08/13] Add draftlog --- draftlogs/6589_add.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 draftlogs/6589_add.md diff --git a/draftlogs/6589_add.md b/draftlogs/6589_add.md new file mode 100644 index 00000000000..726c396b2ef --- /dev/null +++ b/draftlogs/6589_add.md @@ -0,0 +1 @@ +- Add `legend.xref` and `legend.yref` to enable container-referenced positioning for plot legends [[#6589](https://github.com/plotly/plotly.js/pull/6589)], with thanks to [Gamma Technologies](https://www.gtisoft.com/) for sponsoring the related development. \ No newline at end of file From fa5e448fc55f4f2e9350095f8cc258dc433f4667 Mon Sep 17 00:00:00 2001 From: hannahker Date: Wed, 3 May 2023 11:03:39 -0700 Subject: [PATCH 09/13] Remove unneeded return statements --- src/components/legend/draw.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js index 1a45d62b62f..78316f1ae99 100644 --- a/src/components/legend/draw.js +++ b/src/components/legend/draw.js @@ -921,17 +921,14 @@ function expandMargin(gd, legendId, lx, ly) { }); } else if(isPaperX) { gd._fullLayout._reservedMargin[legendId][sideY] = possibleReservedMargins[sideY]; - return; } else if(isPaperY) { gd._fullLayout._reservedMargin[legendId][sideX] = possibleReservedMargins[sideX]; - return; } else { if(legendObj.orientation === 'v') { gd._fullLayout._reservedMargin[legendId][sideX] = possibleReservedMargins[sideX]; } else { gd._fullLayout._reservedMargin[legendId][sideY] = possibleReservedMargins[sideY]; } - return; } } From a01361cb6b3fdda202ce5ae17606d5dfd278784c Mon Sep 17 00:00:00 2001 From: hannahker Date: Thu, 11 May 2023 11:55:29 -0700 Subject: [PATCH 10/13] Coerce min/max or x and y depending on xref and yref --- src/components/legend/attributes.js | 10 ++++------ src/components/legend/defaults.js | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/components/legend/attributes.js b/src/components/legend/attributes.js index d9f441073e2..60eb1f8bc22 100644 --- a/src/components/legend/attributes.js +++ b/src/components/legend/attributes.js @@ -159,8 +159,6 @@ module.exports = { }, x: { valType: 'number', - min: -2, - max: 3, editType: 'legend', description: [ 'Sets the x position with respect to `xref` (in normalized coordinates) of the legend.', @@ -168,7 +166,8 @@ module.exports = { 'defaults to *0* for horizontal legends.', 'When `xref` is *container*, defaults to *1* for vertical legends and', 'defaults to *0* for horizontal legends.', - 'Must be between *0* and *1* if `xref` is *container*.' + 'Must be between *0* and *1* if `xref` is *container*.', + 'and between *-2* and *3* if `xref` is *paper*.' ].join(' ') }, xref: { @@ -198,8 +197,6 @@ module.exports = { }, y: { valType: 'number', - min: -2, - max: 3, editType: 'legend', description: [ 'Sets the y position with respect to `yref` (in normalized coordinates) of the legend.', @@ -207,7 +204,8 @@ module.exports = { 'defaults to *-0.1* for horizontal legends on graphs w/o range sliders and', 'defaults to *1.1* for horizontal legends on graph with one or multiple range sliders.', 'When `yref` is *container*, defaults to *1*.', - 'Must be between *0* and *1* if `yref` is *container*.' + 'Must be between *0* and *1* if `yref` is *container*', + 'and between *-2* and *3* if `yref` is *paper*.' ].join(' ') }, yref: { diff --git a/src/components/legend/defaults.js b/src/components/legend/defaults.js index 118975bfdec..f05e4040bc2 100644 --- a/src/components/legend/defaults.js +++ b/src/components/legend/defaults.js @@ -147,6 +147,26 @@ function groupDefaults(legendId, layoutIn, layoutOut, fullData) { } } + Lib.coerce(containerIn, containerOut, { + x: { + valType: 'number', + editType: 'legend', + min: isPaperX ? -2 : 0, + max: isPaperX ? 3 : 1, + dflt: defaultX, + } + }, 'x'); + + Lib.coerce(containerIn, containerOut, { + y: { + valType: 'number', + editType: 'legend', + min: isPaperY ? -2 : 0, + max: isPaperY ? 3 : 1, + dflt: defaultY, + } + }, 'y'); + coerce('traceorder', defaultOrder); if(helpers.isGrouped(layoutOut.legend)) coerce('tracegroupgap'); From 3cb9887eb9da726709c52b890b22ad36a4221388 Mon Sep 17 00:00:00 2001 From: hannahker Date: Thu, 11 May 2023 12:06:04 -0700 Subject: [PATCH 11/13] Update schema --- test/plot-schema.json | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/test/plot-schema.json b/test/plot-schema.json index 293a56522b1..78f29591c5d 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -2964,10 +2964,8 @@ "valType": "boolean" }, "x": { - "description": "Sets the x position with respect to `xref` (in normalized coordinates) of the legend. When `xref` is *paper*, defaults to *1.02* for vertical legends and defaults to *0* for horizontal legends. When `xref` is *container*, defaults to *1* for vertical legends and defaults to *0* for horizontal legends. Must be between *0* and *1* if `xref` is *container*.", + "description": "Sets the x position with respect to `xref` (in normalized coordinates) of the legend. When `xref` is *paper*, defaults to *1.02* for vertical legends and defaults to *0* for horizontal legends. When `xref` is *container*, defaults to *1* for vertical legends and defaults to *0* for horizontal legends. Must be between *0* and *1* if `xref` is *container*. and between *-2* and *3* if `xref` is *paper*.", "editType": "legend", - "max": 3, - "min": -2, "valType": "number" }, "xanchor": { @@ -2993,10 +2991,8 @@ ] }, "y": { - "description": "Sets the y position with respect to `yref` (in normalized coordinates) of the legend. When `yref` is *paper*, defaults to *1* for vertical legends, defaults to *-0.1* for horizontal legends on graphs w/o range sliders and defaults to *1.1* for horizontal legends on graph with one or multiple range sliders. When `yref` is *container*, defaults to *1*. Must be between *0* and *1* if `yref` is *container*.", + "description": "Sets the y position with respect to `yref` (in normalized coordinates) of the legend. When `yref` is *paper*, defaults to *1* for vertical legends, defaults to *-0.1* for horizontal legends on graphs w/o range sliders and defaults to *1.1* for horizontal legends on graph with one or multiple range sliders. When `yref` is *container*, defaults to *1*. Must be between *0* and *1* if `yref` is *container* and between *-2* and *3* if `yref` is *paper*.", "editType": "legend", - "max": 3, - "min": -2, "valType": "number" }, "yanchor": { From 2af00deba034c75eda660793934c3d92f8d9089b Mon Sep 17 00:00:00 2001 From: hannahker Date: Thu, 11 May 2023 13:28:40 -0700 Subject: [PATCH 12/13] Remove original coerce --- src/components/legend/defaults.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/legend/defaults.js b/src/components/legend/defaults.js index f05e4040bc2..229fd25fd2c 100644 --- a/src/components/legend/defaults.js +++ b/src/components/legend/defaults.js @@ -179,9 +179,7 @@ function groupDefaults(legendId, layoutIn, layoutOut, fullData) { coerce('itemdoubleclick'); coerce('groupclick'); - coerce('x', defaultX); coerce('xanchor', defaultXAnchor); - coerce('y', defaultY); coerce('yanchor', defaultYAnchor); coerce('valign'); Lib.noneOrAll(containerIn, containerOut, ['x', 'y']); From e53e6bc2bd1fe38cdd2a1f2abd8d28228e9b4fd6 Mon Sep 17 00:00:00 2001 From: hannahker Date: Fri, 12 May 2023 10:44:17 -0700 Subject: [PATCH 13/13] Remove todos and unneeded Lib.constrain --- src/components/legend/defaults.js | 2 -- src/components/legend/draw.js | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/components/legend/defaults.js b/src/components/legend/defaults.js index 229fd25fd2c..ea14eeaaa55 100644 --- a/src/components/legend/defaults.js +++ b/src/components/legend/defaults.js @@ -111,8 +111,6 @@ function groupDefaults(legendId, layoutIn, layoutOut, fullData) { var defaultX, defaultY, defaultYAnchor; var defaultXAnchor = 'left'; - // TODO: Adjust default xanchor if needed for container ref? - // TODO: Constrain x or y if container ref to be within 0-1 if(isHorizontal) { defaultX = 0; diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js index 78316f1ae99..ecc2e46e82b 100644 --- a/src/components/legend/draw.js +++ b/src/components/legend/draw.js @@ -163,14 +163,12 @@ function drawOne(gd, opts) { if(isPaperX) { lx = gs.l + gs.w * legendObj.x - FROM_TL[getXanchor(legendObj)] * legendObj._width; } else { - legendObj.x = Lib.constrain(legendObj.x, 0, 1); // TODO: Move this to defaults setting? lx = fullLayout.width * legendObj.x - FROM_TL[getXanchor(legendObj)] * legendObj._width; } if(isPaperY) { ly = gs.t + gs.h * (1 - legendObj.y) - FROM_TL[getYanchor(legendObj)] * legendObj._effHeight; } else { - legendObj.y = Lib.constrain(legendObj.y, 0, 1); // TODO: Move this to defaults setting? ly = fullLayout.height * (1 - legendObj.y) - FROM_TL[getYanchor(legendObj)] * legendObj._effHeight; }