@@ -173,7 +173,18 @@ module.exports = function draw(gd) {
173173 } ) ;
174174
175175 // Position and size the legend
176- repositionLegend ( gd , traces ) ;
176+ var lyMin = 0 ,
177+ lyMax = fullLayout . height ;
178+
179+ computeLegendDimensions ( gd , traces ) ;
180+
181+ if ( opts . height > lyMax ) {
182+ // If the legend doesn't fit in the plot area,
183+ // do not expand the vertical margins.
184+ expandHorizontalMargin ( gd ) ;
185+ } else {
186+ expandMargin ( gd ) ;
187+ }
177188
178189 // Scroll section must be executed after repositionLegend.
179190 // It requires the legend width, height, x and y to position the scrollbox
@@ -185,27 +196,41 @@ module.exports = function draw(gd) {
185196 if ( anchorUtils . isRightAnchor ( opts ) ) {
186197 lx -= opts . width ;
187198 }
188- if ( anchorUtils . isCenterAnchor ( opts ) ) {
199+ else if ( anchorUtils . isCenterAnchor ( opts ) ) {
189200 lx -= opts . width / 2 ;
190201 }
191202
192203 if ( anchorUtils . isBottomAnchor ( opts ) ) {
193204 ly -= opts . height ;
194205 }
195- if ( anchorUtils . isMiddleAnchor ( opts ) ) {
206+ else if ( anchorUtils . isMiddleAnchor ( opts ) ) {
196207 ly -= opts . height / 2 ;
197208 }
198209
210+ // Make sure the legend top and bottom are visible
211+ // (legends with a scroll bar are not allowed to stretch beyond the extended
212+ // margins)
213+ var legendHeight = opts . height ,
214+ legendHeightMax = gs . h ;
215+
216+ if ( legendHeight > legendHeightMax ) {
217+ ly = gs . t ;
218+ legendHeight = legendHeightMax ;
219+ }
220+ else {
221+ if ( ly > lyMax ) ly = lyMax - legendHeight ;
222+ if ( ly < lyMin ) ly = lyMin ;
223+ legendHeight = Math . min ( lyMax - ly , opts . height ) ;
224+ }
225+
199226 // Deal with scrolling
200- var plotHeight = fullLayout . height - fullLayout . margin . b ,
201- scrollheight = Math . min ( plotHeight - ly , opts . height ) ,
202- scrollPosition = scrollBox . attr ( 'data-scroll' ) ? scrollBox . attr ( 'data-scroll' ) : 0 ;
227+ var scrollPosition = scrollBox . attr ( 'data-scroll' ) || 0 ;
203228
204229 scrollBox . attr ( 'transform' , 'translate(0, ' + scrollPosition + ')' ) ;
205230
206231 bg . attr ( {
207232 width : opts . width - 2 * opts . borderwidth ,
208- height : scrollheight - 2 * opts . borderwidth ,
233+ height : legendHeight - 2 * opts . borderwidth ,
209234 x : opts . borderwidth ,
210235 y : opts . borderwidth
211236 } ) ;
@@ -214,71 +239,81 @@ module.exports = function draw(gd) {
214239
215240 clipPath . select ( 'rect' ) . attr ( {
216241 width : opts . width ,
217- height : scrollheight ,
242+ height : legendHeight ,
218243 x : 0 ,
219244 y : 0
220245 } ) ;
221246
222247 legend . call ( Drawing . setClipUrl , clipId ) ;
223248
224249 // If scrollbar should be shown.
225- if ( opts . height - scrollheight > 0 && ! gd . _context . staticPlot ) {
250+ if ( opts . height - legendHeight > 0 && ! gd . _context . staticPlot ) {
226251
252+ // increase the background and clip-path width
253+ // by the scrollbar width and margin
227254 bg . attr ( {
228- width : opts . width - 2 * opts . borderwidth + constants . scrollBarWidth
255+ width : opts . width -
256+ 2 * opts . borderwidth +
257+ constants . scrollBarWidth +
258+ constants . scrollBarMargin
229259 } ) ;
230260
231- clipPath . attr ( {
232- width : opts . width + constants . scrollBarWidth
261+ clipPath . select ( 'rect' ) . attr ( {
262+ width : opts . width +
263+ constants . scrollBarWidth +
264+ constants . scrollBarMargin
233265 } ) ;
234266
235267 if ( gd . firstRender ) {
236268 // Move scrollbar to starting position
237- scrollBar . call (
238- Drawing . setRect ,
239- opts . width - ( constants . scrollBarWidth + constants . scrollBarMargin ) ,
240- constants . scrollBarMargin ,
241- constants . scrollBarWidth ,
242- constants . scrollBarHeight
243- ) ;
244- scrollBox . attr ( 'data-scroll' , 0 ) ;
269+ scrollHandler ( constants . scrollBarMargin , 0 ) ;
245270 }
246271
247- scrollHandler ( 0 , scrollheight ) ;
272+ var scrollBarYMax = legendHeight -
273+ constants . scrollBarHeight -
274+ 2 * constants . scrollBarMargin ,
275+ scrollBoxYMax = opts . height - legendHeight ,
276+ scrollBarY = constants . scrollBarMargin ,
277+ scrollBoxY = 0 ;
248278
249- legend . on ( 'wheel' , null ) ;
279+ scrollHandler ( scrollBarY , scrollBoxY ) ;
250280
281+ legend . on ( 'wheel' , null ) ;
251282 legend . on ( 'wheel' , function ( ) {
252- var e = d3 . event ;
253- e . preventDefault ( ) ;
254- scrollHandler ( e . deltaY / 20 , scrollheight ) ;
283+ scrollBoxY = Lib . constrain (
284+ scrollBox . attr ( 'data-scroll' ) -
285+ d3 . event . deltaY / scrollBarYMax * scrollBoxYMax ,
286+ - scrollBoxYMax , 0 ) ;
287+ scrollBarY = constants . scrollBarMargin -
288+ scrollBoxY / scrollBoxYMax * scrollBarYMax ;
289+ scrollHandler ( scrollBarY , scrollBoxY ) ;
290+ d3 . event . preventDefault ( ) ;
255291 } ) ;
256292
257293 scrollBar . on ( '.drag' , null ) ;
258294 scrollBox . on ( '.drag' , null ) ;
259- var drag = d3 . behavior . drag ( )
260- . on ( 'drag' , function ( ) {
261- scrollHandler ( d3 . event . dy , scrollheight ) ;
262- } ) ;
295+ var drag = d3 . behavior . drag ( ) . on ( 'drag' , function ( ) {
296+ scrollBarY = Lib . constrain (
297+ d3 . event . y - constants . scrollBarHeight / 2 ,
298+ constants . scrollBarMargin ,
299+ constants . scrollBarMargin + scrollBarYMax ) ;
300+ scrollBoxY = - ( scrollBarY - constants . scrollBarMargin ) /
301+ scrollBarYMax * scrollBoxYMax ;
302+ scrollHandler ( scrollBarY , scrollBoxY ) ;
303+ } ) ;
263304
264305 scrollBar . call ( drag ) ;
265306 scrollBox . call ( drag ) ;
266307
267308 }
268309
269310
270- function scrollHandler ( delta , scrollheight ) {
271-
272- var scrollBarTrack = scrollheight - constants . scrollBarHeight - 2 * constants . scrollBarMargin ,
273- translateY = scrollBox . attr ( 'data-scroll' ) ,
274- scrollBoxY = Lib . constrain ( translateY - delta , scrollheight - opts . height , 0 ) ,
275- scrollBarY = - scrollBoxY / ( opts . height - scrollheight ) * scrollBarTrack + constants . scrollBarMargin ;
276-
311+ function scrollHandler ( scrollBarY , scrollBoxY ) {
277312 scrollBox . attr ( 'data-scroll' , scrollBoxY ) ;
278313 scrollBox . attr ( 'transform' , 'translate(0, ' + scrollBoxY + ')' ) ;
279314 scrollBar . call (
280315 Drawing . setRect ,
281- opts . width - ( constants . scrollBarWidth + constants . scrollBarMargin ) ,
316+ opts . width ,
282317 scrollBarY ,
283318 constants . scrollBarWidth ,
284319 constants . scrollBarHeight
@@ -348,7 +383,10 @@ function drawTexts(context, gd, d, i, traces) {
348383
349384 function textLayout ( s ) {
350385 Plotly . util . convertToTspans ( s , function ( ) {
351- if ( gd . firstRender ) repositionLegend ( gd , traces ) ;
386+ if ( gd . firstRender ) {
387+ computeLegendDimensions ( gd , traces ) ;
388+ expandMargin ( gd ) ;
389+ }
352390 } ) ;
353391 s . selectAll ( 'tspan.line' ) . attr ( { x : s . attr ( 'x' ) } ) ;
354392 }
@@ -367,9 +405,8 @@ function drawTexts(context, gd, d, i, traces) {
367405 else text . call ( textLayout ) ;
368406}
369407
370- function repositionLegend ( gd , traces ) {
408+ function computeLegendDimensions ( gd , traces ) {
371409 var fullLayout = gd . _fullLayout ,
372- gs = fullLayout . _size ,
373410 opts = fullLayout . legend ,
374411 borderwidth = opts . borderwidth ;
375412
@@ -421,7 +458,6 @@ function repositionLegend(gd, traces) {
421458 opts . width = Math . max ( opts . width , tWidth || 0 ) ;
422459 } ) ;
423460
424-
425461 opts . width += 45 + borderwidth * 2 ;
426462 opts . height += 10 + borderwidth * 2 ;
427463
@@ -432,41 +468,31 @@ function repositionLegend(gd, traces) {
432468 traces . selectAll ( '.legendtoggle' )
433469 . attr ( 'width' , ( gd . _context . editable ? 0 : opts . width ) + 40 ) ;
434470
435- // now position the legend. for both x,y the positions are recorded as
436- // fractions of the plot area (left, bottom = 0,0). Outside the plot
437- // area is allowed but position will be clipped to the page.
438- // values <1/3 align the low side at that fraction, 1/3-2/3 align the
439- // center at that fraction, >2/3 align the right at that fraction
471+ // make sure we're only getting full pixels
472+ opts . width = Math . ceil ( opts . width ) ;
473+ opts . height = Math . ceil ( opts . height ) ;
474+ }
440475
441- var lx = gs . l + gs . w * opts . x ,
442- ly = gs . t + gs . h * ( 1 - opts . y ) ;
476+ function expandMargin ( gd ) {
477+ var fullLayout = gd . _fullLayout ,
478+ opts = fullLayout . legend ;
443479
444480 var xanchor = 'left' ;
445481 if ( anchorUtils . isRightAnchor ( opts ) ) {
446- lx -= opts . width ;
447482 xanchor = 'right' ;
448483 }
449- if ( anchorUtils . isCenterAnchor ( opts ) ) {
450- lx -= opts . width / 2 ;
484+ else if ( anchorUtils . isCenterAnchor ( opts ) ) {
451485 xanchor = 'center' ;
452486 }
453487
454488 var yanchor = 'top' ;
455489 if ( anchorUtils . isBottomAnchor ( opts ) ) {
456- ly -= opts . height ;
457490 yanchor = 'bottom' ;
458491 }
459- if ( anchorUtils . isMiddleAnchor ( opts ) ) {
460- ly -= opts . height / 2 ;
492+ else if ( anchorUtils . isMiddleAnchor ( opts ) ) {
461493 yanchor = 'middle' ;
462494 }
463495
464- // make sure we're only getting full pixels
465- opts . width = Math . ceil ( opts . width ) ;
466- opts . height = Math . ceil ( opts . height ) ;
467- lx = Math . round ( lx ) ;
468- ly = Math . round ( ly ) ;
469-
470496 // lastly check if the margin auto-expand has changed
471497 Plots . autoMargin ( gd , 'legend' , {
472498 x : opts . x ,
@@ -477,3 +503,26 @@ function repositionLegend(gd, traces) {
477503 t : opts . height * ( { bottom : 1 , middle : 0.5 } [ yanchor ] || 0 )
478504 } ) ;
479505}
506+
507+ function expandHorizontalMargin ( gd ) {
508+ var fullLayout = gd . _fullLayout ,
509+ opts = fullLayout . legend ;
510+
511+ var xanchor = 'left' ;
512+ if ( anchorUtils . isRightAnchor ( opts ) ) {
513+ xanchor = 'right' ;
514+ }
515+ else if ( anchorUtils . isCenterAnchor ( opts ) ) {
516+ xanchor = 'center' ;
517+ }
518+
519+ // lastly check if the margin auto-expand has changed
520+ Plots . autoMargin ( gd , 'legend' , {
521+ x : opts . x ,
522+ y : 0.5 ,
523+ l : opts . width * ( { right : 1 , center : 0.5 } [ xanchor ] || 0 ) ,
524+ r : opts . width * ( { left : 1 , center : 0.5 } [ xanchor ] || 0 ) ,
525+ b : 0 ,
526+ t : 0
527+ } ) ;
528+ }
0 commit comments