@@ -65,6 +65,19 @@ module.exports = function draw(gd) {
65
65
66
66
var scrollBox = Lib . ensureSingle ( legend , 'g' , 'scrollbox' ) ;
67
67
68
+ var title = opts . title ;
69
+ opts . _titleWidth = 0 ;
70
+ opts . _titleHeight = 0 ;
71
+ if ( title . text ) {
72
+ var titleEl = Lib . ensureSingle ( scrollBox , 'text' , 'legendtitletext' ) ;
73
+ titleEl . attr ( 'text-anchor' , 'start' )
74
+ . classed ( 'user-select-none' , true )
75
+ . call ( Drawing . font , title . font )
76
+ . text ( title . text ) ;
77
+
78
+ textLayout ( titleEl , scrollBox , gd ) ; // handle mathjax or multi-line text and compute title height
79
+ }
80
+
68
81
var scrollBar = Lib . ensureSingle ( legend , 'rect' , 'scrollbar' , function ( s ) {
69
82
s . attr ( constants . scrollBarEnterAttrs )
70
83
. call ( Color . fill , constants . scrollBarColor ) ;
@@ -121,7 +134,7 @@ module.exports = function draw(gd) {
121
134
}
122
135
123
136
// Set size and position of all the elements that make up a legend:
124
- // legend, background and border, scroll box and scroll bar
137
+ // legend, background and border, scroll box and scroll bar as well as title
125
138
Drawing . setTranslate ( legend , lx , ly ) ;
126
139
127
140
// to be safe, remove previous listeners
@@ -370,23 +383,17 @@ function drawTexts(g, gd) {
370
383
371
384
textEl . attr ( 'text-anchor' , 'start' )
372
385
. classed ( 'user-select-none' , true )
373
- . call ( Drawing . font , fullLayout . legend . font )
386
+ . call ( Drawing . font , opts . font )
374
387
. text ( isEditable ? ensureLength ( name , maxNameLength ) : name ) ;
375
388
376
389
svgTextUtils . positionText ( textEl , constants . textGap , 0 ) ;
377
390
378
- function textLayout ( s ) {
379
- svgTextUtils . convertToTspans ( s , gd , function ( ) {
380
- computeTextDimensions ( g , gd ) ;
381
- } ) ;
382
- }
383
-
384
391
if ( isEditable ) {
385
392
textEl . call ( svgTextUtils . makeEditable , { gd : gd , text : name } )
386
- . call ( textLayout )
393
+ . call ( textLayout , g , gd )
387
394
. on ( 'edit' , function ( newName ) {
388
395
this . text ( ensureLength ( newName , maxNameLength ) )
389
- . call ( textLayout ) ;
396
+ . call ( textLayout , g , gd ) ;
390
397
391
398
var fullInput = legendItem . trace . _fullInput || { } ;
392
399
var update = { } ;
@@ -407,7 +414,7 @@ function drawTexts(g, gd) {
407
414
return Registry . call ( '_guiRestyle' , gd , update , traceIndex ) ;
408
415
} ) ;
409
416
} else {
410
- textLayout ( textEl ) ;
417
+ textLayout ( textEl , g , gd ) ;
411
418
}
412
419
}
413
420
@@ -460,18 +467,24 @@ function setupTraceToggle(g, gd) {
460
467
} ) ;
461
468
}
462
469
470
+ function textLayout ( s , g , gd ) {
471
+ svgTextUtils . convertToTspans ( s , gd , function ( ) {
472
+ computeTextDimensions ( g , gd ) ;
473
+ } ) ;
474
+ }
475
+
463
476
function computeTextDimensions ( g , gd ) {
464
477
var legendItem = g . data ( ) [ 0 ] [ 0 ] ;
465
-
466
- if ( ! legendItem . trace . showlegend ) {
478
+ if ( legendItem && ! legendItem . trace . showlegend ) {
467
479
g . remove ( ) ;
468
480
return ;
469
481
}
470
482
471
483
var mathjaxGroup = g . select ( 'g[class*=math-group]' ) ;
472
484
var mathjaxNode = mathjaxGroup . node ( ) ;
485
+ var bw = gd . _fullLayout . legend . borderwidth ;
473
486
var opts = gd . _fullLayout . legend ;
474
- var lineHeight = opts . font . size * LINE_SPACING ;
487
+ var lineHeight = ( legendItem ? opts : opts . title ) . font . size * LINE_SPACING ;
475
488
var height , width ;
476
489
477
490
if ( mathjaxNode ) {
@@ -480,24 +493,56 @@ function computeTextDimensions(g, gd) {
480
493
height = mathjaxBB . height ;
481
494
width = mathjaxBB . width ;
482
495
483
- Drawing . setTranslate ( mathjaxGroup , 0 , ( height / 4 ) ) ;
496
+ if ( legendItem ) {
497
+ Drawing . setTranslate ( mathjaxGroup , 0 , height * 0.25 ) ;
498
+ } else { // case of title
499
+ Drawing . setTranslate ( mathjaxGroup , bw , height * 0.75 + bw ) ;
500
+ }
484
501
} else {
485
- var text = g . select ( '.legendtext' ) ;
486
- var textLines = svgTextUtils . lineCount ( text ) ;
487
- var textNode = text . node ( ) ;
502
+ var textEl = g . select ( legendItem ?
503
+ '.legendtext' : '.legendtitletext'
504
+ ) ;
505
+ var textLines = svgTextUtils . lineCount ( textEl ) ;
506
+ var textNode = textEl . node ( ) ;
488
507
489
508
height = lineHeight * textLines ;
490
509
width = textNode ? Drawing . bBox ( textNode ) . width : 0 ;
491
510
492
511
// approximation to height offset to center the font
493
512
// to avoid getBoundingClientRect
494
- var textY = lineHeight * ( 0.3 + ( 1 - textLines ) / 2 ) ;
495
- svgTextUtils . positionText ( text , constants . textGap , textY ) ;
513
+ var textY = lineHeight * ( ( textLines - 1 ) / 2 - 0.3 ) ;
514
+ if ( legendItem ) {
515
+ svgTextUtils . positionText ( textEl , constants . textGap , - textY ) ;
516
+ } else { // case of title
517
+ svgTextUtils . positionText ( textEl , constants . titlePad + bw , lineHeight + bw ) ;
518
+ }
519
+ }
520
+
521
+ if ( legendItem ) {
522
+ legendItem . lineHeight = lineHeight ;
523
+ legendItem . height = Math . max ( height , 16 ) + 3 ;
524
+ legendItem . width = width ;
525
+ } else { // case of title
526
+ opts . _titleWidth = width ;
527
+ opts . _titleHeight = height ;
528
+ }
529
+ }
530
+
531
+ function getTitleSize ( opts ) {
532
+ var w = 0 ;
533
+ var h = 0 ;
534
+
535
+ var side = opts . title . side ;
536
+ if ( side ) {
537
+ if ( side . indexOf ( 'left' ) !== - 1 ) {
538
+ w = opts . _titleWidth ;
539
+ }
540
+ if ( side . indexOf ( 'top' ) !== - 1 ) {
541
+ h = opts . _titleHeight ;
542
+ }
496
543
}
497
544
498
- legendItem . lineHeight = lineHeight ;
499
- legendItem . height = Math . max ( height , 16 ) + 3 ;
500
- legendItem . width = width ;
545
+ return [ w , h ] ;
501
546
}
502
547
503
548
/*
@@ -514,6 +559,7 @@ function computeLegendDimensions(gd, groups, traces) {
514
559
var fullLayout = gd . _fullLayout ;
515
560
var opts = fullLayout . legend ;
516
561
var gs = fullLayout . _size ;
562
+
517
563
var isVertical = helpers . isVertical ( opts ) ;
518
564
var isGrouped = helpers . isGrouped ( opts ) ;
519
565
@@ -537,11 +583,15 @@ function computeLegendDimensions(gd, groups, traces) {
537
583
var toggleRectWidth = 0 ;
538
584
opts . _width = 0 ;
539
585
opts . _height = 0 ;
586
+ var titleSize = getTitleSize ( opts ) ;
540
587
541
588
if ( isVertical ) {
542
589
traces . each ( function ( d ) {
543
590
var h = d [ 0 ] . height ;
544
- Drawing . setTranslate ( this , bw , itemGap + bw + opts . _height + h / 2 ) ;
591
+ Drawing . setTranslate ( this ,
592
+ bw + titleSize [ 0 ] ,
593
+ bw + titleSize [ 1 ] + opts . _height + h / 2 + itemGap
594
+ ) ;
545
595
opts . _height += h ;
546
596
opts . _width = Math . max ( opts . _width , d [ 0 ] . width ) ;
547
597
} ) ;
@@ -591,7 +641,10 @@ function computeLegendDimensions(gd, groups, traces) {
591
641
var offsetY = 0 ;
592
642
d3 . select ( this ) . selectAll ( 'g.traces' ) . each ( function ( d ) {
593
643
var h = d [ 0 ] . height ;
594
- Drawing . setTranslate ( this , 0 , itemGap + bw + h / 2 + offsetY ) ;
644
+ Drawing . setTranslate ( this ,
645
+ titleSize [ 0 ] ,
646
+ titleSize [ 1 ] + bw + itemGap + h / 2 + offsetY
647
+ ) ;
595
648
offsetY += h ;
596
649
maxWidthInGroup = Math . max ( maxWidthInGroup , textGap + d [ 0 ] . width ) ;
597
650
} ) ;
@@ -634,7 +687,10 @@ function computeLegendDimensions(gd, groups, traces) {
634
687
maxItemHeightInRow = 0 ;
635
688
}
636
689
637
- Drawing . setTranslate ( this , bw + offsetX , itemGap + bw + h / 2 + offsetY ) ;
690
+ Drawing . setTranslate ( this ,
691
+ titleSize [ 0 ] + bw + offsetX ,
692
+ titleSize [ 1 ] + bw + offsetY + h / 2 + itemGap
693
+ ) ;
638
694
639
695
rowWidth = offsetX + w + itemGap ;
640
696
offsetX += next ;
@@ -651,8 +707,19 @@ function computeLegendDimensions(gd, groups, traces) {
651
707
}
652
708
}
653
709
654
- opts . _width = Math . ceil ( opts . _width ) ;
655
- opts . _height = Math . ceil ( opts . _height ) ;
710
+ opts . _width = Math . ceil (
711
+ Math . max (
712
+ opts . _width + titleSize [ 0 ] ,
713
+ opts . _titleWidth + 2 * ( bw + constants . titlePad )
714
+ )
715
+ ) ;
716
+
717
+ opts . _height = Math . ceil (
718
+ Math . max (
719
+ opts . _height + titleSize [ 1 ] ,
720
+ opts . _titleHeight + 2 * ( bw + constants . itemGap )
721
+ )
722
+ ) ;
656
723
657
724
opts . _effHeight = Math . min ( opts . _height , opts . _maxHeight ) ;
658
725
0 commit comments