@@ -65,6 +65,19 @@ module.exports = function draw(gd) {
6565
6666 var scrollBox = Lib . ensureSingle ( legend , 'g' , 'scrollbox' ) ;
6767
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+
6881 var scrollBar = Lib . ensureSingle ( legend , 'rect' , 'scrollbar' , function ( s ) {
6982 s . attr ( constants . scrollBarEnterAttrs )
7083 . call ( Color . fill , constants . scrollBarColor ) ;
@@ -121,7 +134,7 @@ module.exports = function draw(gd) {
121134 }
122135
123136 // 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
125138 Drawing . setTranslate ( legend , lx , ly ) ;
126139
127140 // to be safe, remove previous listeners
@@ -370,23 +383,17 @@ function drawTexts(g, gd) {
370383
371384 textEl . attr ( 'text-anchor' , 'start' )
372385 . classed ( 'user-select-none' , true )
373- . call ( Drawing . font , fullLayout . legend . font )
386+ . call ( Drawing . font , opts . font )
374387 . text ( isEditable ? ensureLength ( name , maxNameLength ) : name ) ;
375388
376389 svgTextUtils . positionText ( textEl , constants . textGap , 0 ) ;
377390
378- function textLayout ( s ) {
379- svgTextUtils . convertToTspans ( s , gd , function ( ) {
380- computeTextDimensions ( g , gd ) ;
381- } ) ;
382- }
383-
384391 if ( isEditable ) {
385392 textEl . call ( svgTextUtils . makeEditable , { gd : gd , text : name } )
386- . call ( textLayout )
393+ . call ( textLayout , g , gd )
387394 . on ( 'edit' , function ( newName ) {
388395 this . text ( ensureLength ( newName , maxNameLength ) )
389- . call ( textLayout ) ;
396+ . call ( textLayout , g , gd ) ;
390397
391398 var fullInput = legendItem . trace . _fullInput || { } ;
392399 var update = { } ;
@@ -407,7 +414,7 @@ function drawTexts(g, gd) {
407414 return Registry . call ( '_guiRestyle' , gd , update , traceIndex ) ;
408415 } ) ;
409416 } else {
410- textLayout ( textEl ) ;
417+ textLayout ( textEl , g , gd ) ;
411418 }
412419}
413420
@@ -460,18 +467,24 @@ function setupTraceToggle(g, gd) {
460467 } ) ;
461468}
462469
470+ function textLayout ( s , g , gd ) {
471+ svgTextUtils . convertToTspans ( s , gd , function ( ) {
472+ computeTextDimensions ( g , gd ) ;
473+ } ) ;
474+ }
475+
463476function computeTextDimensions ( g , gd ) {
464477 var legendItem = g . data ( ) [ 0 ] [ 0 ] ;
465-
466- if ( ! legendItem . trace . showlegend ) {
478+ if ( legendItem && ! legendItem . trace . showlegend ) {
467479 g . remove ( ) ;
468480 return ;
469481 }
470482
471483 var mathjaxGroup = g . select ( 'g[class*=math-group]' ) ;
472484 var mathjaxNode = mathjaxGroup . node ( ) ;
485+ var bw = gd . _fullLayout . legend . borderwidth ;
473486 var opts = gd . _fullLayout . legend ;
474- var lineHeight = opts . font . size * LINE_SPACING ;
487+ var lineHeight = ( legendItem ? opts : opts . title ) . font . size * LINE_SPACING ;
475488 var height , width ;
476489
477490 if ( mathjaxNode ) {
@@ -480,24 +493,56 @@ function computeTextDimensions(g, gd) {
480493 height = mathjaxBB . height ;
481494 width = mathjaxBB . width ;
482495
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+ }
484501 } 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 ( ) ;
488507
489508 height = lineHeight * textLines ;
490509 width = textNode ? Drawing . bBox ( textNode ) . width : 0 ;
491510
492511 // approximation to height offset to center the font
493512 // 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+ }
496543 }
497544
498- legendItem . lineHeight = lineHeight ;
499- legendItem . height = Math . max ( height , 16 ) + 3 ;
500- legendItem . width = width ;
545+ return [ w , h ] ;
501546}
502547
503548/*
@@ -514,6 +559,7 @@ function computeLegendDimensions(gd, groups, traces) {
514559 var fullLayout = gd . _fullLayout ;
515560 var opts = fullLayout . legend ;
516561 var gs = fullLayout . _size ;
562+
517563 var isVertical = helpers . isVertical ( opts ) ;
518564 var isGrouped = helpers . isGrouped ( opts ) ;
519565
@@ -537,11 +583,15 @@ function computeLegendDimensions(gd, groups, traces) {
537583 var toggleRectWidth = 0 ;
538584 opts . _width = 0 ;
539585 opts . _height = 0 ;
586+ var titleSize = getTitleSize ( opts ) ;
540587
541588 if ( isVertical ) {
542589 traces . each ( function ( d ) {
543590 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+ ) ;
545595 opts . _height += h ;
546596 opts . _width = Math . max ( opts . _width , d [ 0 ] . width ) ;
547597 } ) ;
@@ -591,7 +641,10 @@ function computeLegendDimensions(gd, groups, traces) {
591641 var offsetY = 0 ;
592642 d3 . select ( this ) . selectAll ( 'g.traces' ) . each ( function ( d ) {
593643 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+ ) ;
595648 offsetY += h ;
596649 maxWidthInGroup = Math . max ( maxWidthInGroup , textGap + d [ 0 ] . width ) ;
597650 } ) ;
@@ -634,7 +687,10 @@ function computeLegendDimensions(gd, groups, traces) {
634687 maxItemHeightInRow = 0 ;
635688 }
636689
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+ ) ;
638694
639695 rowWidth = offsetX + w + itemGap ;
640696 offsetX += next ;
@@ -651,8 +707,19 @@ function computeLegendDimensions(gd, groups, traces) {
651707 }
652708 }
653709
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+ ) ;
656723
657724 opts . _effHeight = Math . min ( opts . _height , opts . _maxHeight ) ;
658725
0 commit comments