@@ -173,7 +173,18 @@ module.exports = function draw(gd) {
173
173
} ) ;
174
174
175
175
// 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
+ }
177
188
178
189
// Scroll section must be executed after repositionLegend.
179
190
// It requires the legend width, height, x and y to position the scrollbox
@@ -185,27 +196,41 @@ module.exports = function draw(gd) {
185
196
if ( anchorUtils . isRightAnchor ( opts ) ) {
186
197
lx -= opts . width ;
187
198
}
188
- if ( anchorUtils . isCenterAnchor ( opts ) ) {
199
+ else if ( anchorUtils . isCenterAnchor ( opts ) ) {
189
200
lx -= opts . width / 2 ;
190
201
}
191
202
192
203
if ( anchorUtils . isBottomAnchor ( opts ) ) {
193
204
ly -= opts . height ;
194
205
}
195
- if ( anchorUtils . isMiddleAnchor ( opts ) ) {
206
+ else if ( anchorUtils . isMiddleAnchor ( opts ) ) {
196
207
ly -= opts . height / 2 ;
197
208
}
198
209
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
+
199
226
// 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 ;
203
228
204
229
scrollBox . attr ( 'transform' , 'translate(0, ' + scrollPosition + ')' ) ;
205
230
206
231
bg . attr ( {
207
232
width : opts . width - 2 * opts . borderwidth ,
208
- height : scrollheight - 2 * opts . borderwidth ,
233
+ height : legendHeight - 2 * opts . borderwidth ,
209
234
x : opts . borderwidth ,
210
235
y : opts . borderwidth
211
236
} ) ;
@@ -214,71 +239,81 @@ module.exports = function draw(gd) {
214
239
215
240
clipPath . select ( 'rect' ) . attr ( {
216
241
width : opts . width ,
217
- height : scrollheight ,
242
+ height : legendHeight ,
218
243
x : 0 ,
219
244
y : 0
220
245
} ) ;
221
246
222
247
legend . call ( Drawing . setClipUrl , clipId ) ;
223
248
224
249
// If scrollbar should be shown.
225
- if ( opts . height - scrollheight > 0 && ! gd . _context . staticPlot ) {
250
+ if ( opts . height - legendHeight > 0 && ! gd . _context . staticPlot ) {
226
251
252
+ // increase the background and clip-path width
253
+ // by the scrollbar width and margin
227
254
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
229
259
} ) ;
230
260
231
- clipPath . attr ( {
232
- width : opts . width + constants . scrollBarWidth
261
+ clipPath . select ( 'rect' ) . attr ( {
262
+ width : opts . width +
263
+ constants . scrollBarWidth +
264
+ constants . scrollBarMargin
233
265
} ) ;
234
266
235
267
if ( gd . firstRender ) {
236
268
// 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 ) ;
245
270
}
246
271
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 ;
248
278
249
- legend . on ( 'wheel' , null ) ;
279
+ scrollHandler ( scrollBarY , scrollBoxY ) ;
250
280
281
+ legend . on ( 'wheel' , null ) ;
251
282
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 ( ) ;
255
291
} ) ;
256
292
257
293
scrollBar . on ( '.drag' , null ) ;
258
294
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
+ } ) ;
263
304
264
305
scrollBar . call ( drag ) ;
265
306
scrollBox . call ( drag ) ;
266
307
267
308
}
268
309
269
310
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 ) {
277
312
scrollBox . attr ( 'data-scroll' , scrollBoxY ) ;
278
313
scrollBox . attr ( 'transform' , 'translate(0, ' + scrollBoxY + ')' ) ;
279
314
scrollBar . call (
280
315
Drawing . setRect ,
281
- opts . width - ( constants . scrollBarWidth + constants . scrollBarMargin ) ,
316
+ opts . width ,
282
317
scrollBarY ,
283
318
constants . scrollBarWidth ,
284
319
constants . scrollBarHeight
@@ -348,7 +383,10 @@ function drawTexts(context, gd, d, i, traces) {
348
383
349
384
function textLayout ( s ) {
350
385
Plotly . util . convertToTspans ( s , function ( ) {
351
- if ( gd . firstRender ) repositionLegend ( gd , traces ) ;
386
+ if ( gd . firstRender ) {
387
+ computeLegendDimensions ( gd , traces ) ;
388
+ expandMargin ( gd ) ;
389
+ }
352
390
} ) ;
353
391
s . selectAll ( 'tspan.line' ) . attr ( { x : s . attr ( 'x' ) } ) ;
354
392
}
@@ -367,9 +405,8 @@ function drawTexts(context, gd, d, i, traces) {
367
405
else text . call ( textLayout ) ;
368
406
}
369
407
370
- function repositionLegend ( gd , traces ) {
408
+ function computeLegendDimensions ( gd , traces ) {
371
409
var fullLayout = gd . _fullLayout ,
372
- gs = fullLayout . _size ,
373
410
opts = fullLayout . legend ,
374
411
borderwidth = opts . borderwidth ;
375
412
@@ -421,7 +458,6 @@ function repositionLegend(gd, traces) {
421
458
opts . width = Math . max ( opts . width , tWidth || 0 ) ;
422
459
} ) ;
423
460
424
-
425
461
opts . width += 45 + borderwidth * 2 ;
426
462
opts . height += 10 + borderwidth * 2 ;
427
463
@@ -432,41 +468,31 @@ function repositionLegend(gd, traces) {
432
468
traces . selectAll ( '.legendtoggle' )
433
469
. attr ( 'width' , ( gd . _context . editable ? 0 : opts . width ) + 40 ) ;
434
470
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
+ }
440
475
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 ;
443
479
444
480
var xanchor = 'left' ;
445
481
if ( anchorUtils . isRightAnchor ( opts ) ) {
446
- lx -= opts . width ;
447
482
xanchor = 'right' ;
448
483
}
449
- if ( anchorUtils . isCenterAnchor ( opts ) ) {
450
- lx -= opts . width / 2 ;
484
+ else if ( anchorUtils . isCenterAnchor ( opts ) ) {
451
485
xanchor = 'center' ;
452
486
}
453
487
454
488
var yanchor = 'top' ;
455
489
if ( anchorUtils . isBottomAnchor ( opts ) ) {
456
- ly -= opts . height ;
457
490
yanchor = 'bottom' ;
458
491
}
459
- if ( anchorUtils . isMiddleAnchor ( opts ) ) {
460
- ly -= opts . height / 2 ;
492
+ else if ( anchorUtils . isMiddleAnchor ( opts ) ) {
461
493
yanchor = 'middle' ;
462
494
}
463
495
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
-
470
496
// lastly check if the margin auto-expand has changed
471
497
Plots . autoMargin ( gd , 'legend' , {
472
498
x : opts . x ,
@@ -477,3 +503,26 @@ function repositionLegend(gd, traces) {
477
503
t : opts . height * ( { bottom : 1 , middle : 0.5 } [ yanchor ] || 0 )
478
504
} ) ;
479
505
}
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