@@ -308,6 +308,53 @@ module.exports = function plot(gd, cdpie) {
308308 } ) ;
309309 } ) ;
310310
311+ // add the title
312+ var hasTitle = trace . title &&
313+ ( ( trace . titleposition === 'inhole' && trace . hole > 0 ) ||
314+ ( trace . titleposition === 'outside' ) ) ;
315+ var titleTextGroup = d3 . select ( this ) . selectAll ( 'g.titletext' )
316+ . data ( hasTitle ? [ 0 ] : [ ] ) ;
317+
318+ titleTextGroup . enter ( ) . append ( 'g' )
319+ . classed ( 'titletext' , true ) ;
320+ titleTextGroup . exit ( ) . remove ( ) ;
321+
322+ titleTextGroup . each ( function ( ) {
323+ var titleText = Lib . ensureSingle ( d3 . select ( this ) , 'text' , '' , function ( s ) {
324+ // prohibit tex interpretation as above
325+ s . attr ( 'data-notex' , 1 ) ;
326+ } ) ;
327+
328+ titleText . text ( trace . title )
329+ . attr ( {
330+ 'class' : 'titletext' ,
331+ transform : '' ,
332+ 'text-anchor' : 'middle'
333+ } )
334+ . call ( Drawing . font , trace . titlefont )
335+ . call ( svgTextUtils . convertToTspans , gd ) ;
336+
337+
338+ var titleBB = Drawing . bBox ( titleText . node ( ) ) ;
339+ // translation and scaling for the title text box.
340+ // The translation is for the center point.
341+ var transform ;
342+
343+ if ( trace . titleposition === 'outside' ) {
344+ transform = positionTitleOutside ( titleBB , cd0 , fullLayout . _size ) ;
345+ } else {
346+ transform = positionTitleInside ( titleBB , cd0 ) ;
347+ }
348+
349+ titleText . attr ( 'transform' ,
350+ 'translate(' + transform . x + ',' + transform . y + ')' +
351+ ( transform . scale < 1 ? ( 'scale(' + transform . scale + ')' ) : '' ) +
352+ 'translate(' +
353+ ( - ( titleBB . left + titleBB . right ) / 2 ) + ',' +
354+ ( - ( titleBB . top + titleBB . bottom ) / 2 ) +
355+ ')' ) ;
356+ } ) ;
357+
311358 // now make sure no labels overlap (at least within one pie)
312359 if ( hasOutsideText ) scootLabels ( quadrants , trace ) ;
313360 slices . each ( function ( pt ) {
@@ -454,6 +501,72 @@ function transformOutsideText(textBB, pt) {
454501 } ;
455502}
456503
504+ function positionTitleInside ( titleBB , cd0 ) {
505+ var textDiameter = Math . sqrt ( titleBB . width * titleBB . width + titleBB . height * titleBB . height ) ;
506+ return {
507+ x : cd0 . cx ,
508+ y : cd0 . cy ,
509+ scale : cd0 . trace . hole * cd0 . r * 2 / textDiameter
510+ } ;
511+ }
512+
513+ function positionTitleOutside ( titleBB , cd0 , plotSize ) {
514+ var scaleX , scaleY , chartWidth , titleSpace , titleShift , maxPull ;
515+ var trace = cd0 . trace ;
516+
517+ maxPull = getMaxPull ( trace ) ;
518+ chartWidth = plotSize . w * ( trace . domain . x [ 1 ] - trace . domain . x [ 0 ] ) ;
519+ scaleX = chartWidth / titleBB . width ;
520+ if ( isSinglePie ( trace ) ) {
521+ titleShift = trace . titlefont . size / 2 ;
522+ // we need to leave enough free space for an outside label
523+ if ( trace . outsidetextfont ) titleShift += 3 * trace . outsidetextfont . size / 2 ;
524+ else titleShift += trace . titlefont . size / 4 ;
525+ return {
526+ x : cd0 . cx ,
527+ y : cd0 . cy - ( 1 + maxPull ) * cd0 . r - titleShift ,
528+ scale : scaleX
529+ } ;
530+ }
531+ titleSpace = getTitleSpace ( trace , plotSize ) ;
532+ // we previously left a free space of height titleSpace.
533+ // The text must fit in this space.
534+ scaleY = titleSpace / titleBB . height ;
535+ return {
536+ x : cd0 . cx ,
537+ y : cd0 . cy - ( 1 + maxPull ) * cd0 . r - ( titleSpace / 2 ) ,
538+ scale : Math . min ( scaleX , scaleY )
539+ } ;
540+ }
541+
542+ function isSinglePie ( trace ) {
543+ // check if there is a single pie per y-column
544+ if ( trace . domain . y [ 0 ] === 0 && trace . domain . y [ 1 ] === 1 ) return true ;
545+ return false ;
546+ }
547+
548+ function getTitleSpace ( trace , plotSize ) {
549+ var chartHeight = plotSize . h * ( trace . domain . y [ 1 ] - trace . domain . y [ 0 ] ) ;
550+ // leave 3/2 * titlefont.size free space. We need at least titlefont.size
551+ // space, and the 1/2 * titlefont.size is a small buffer to avoid the text
552+ // touching the pie.
553+ var titleSpace = ( trace . title && trace . titleposition === 'outside' ) ?
554+ ( 3 * trace . titlefont . size / 2 ) : 0 ;
555+ if ( chartHeight > titleSpace ) return titleSpace ;
556+ else return chartHeight / 2 ;
557+ }
558+
559+ function getMaxPull ( trace ) {
560+ var maxPull = trace . pull , j ;
561+ if ( Array . isArray ( maxPull ) ) {
562+ maxPull = 0 ;
563+ for ( j = 0 ; j < trace . pull . length ; j ++ ) {
564+ if ( trace . pull [ j ] > maxPull ) maxPull = trace . pull [ j ] ;
565+ }
566+ }
567+ return maxPull ;
568+ }
569+
457570function scootLabels ( quadrants , trace ) {
458571 var xHalf , yHalf , equatorFirst , farthestX , farthestY ,
459572 xDiffSign , yDiffSign , thisQuad , oppositeQuad ,
@@ -570,21 +683,18 @@ function scalePies(cdpie, plotSize) {
570683 for ( i = 0 ; i < cdpie . length ; i ++ ) {
571684 cd0 = cdpie [ i ] [ 0 ] ;
572685 trace = cd0 . trace ;
686+
573687 pieBoxWidth = plotSize . w * ( trace . domain . x [ 1 ] - trace . domain . x [ 0 ] ) ;
574688 pieBoxHeight = plotSize . h * ( trace . domain . y [ 1 ] - trace . domain . y [ 0 ] ) ;
689+ // leave some space for the title, if it will be displayed outside
690+ if ( ! isSinglePie ( trace ) ) pieBoxHeight -= getTitleSpace ( trace , plotSize ) ;
575691
576- maxPull = trace . pull ;
577- if ( Array . isArray ( maxPull ) ) {
578- maxPull = 0 ;
579- for ( j = 0 ; j < trace . pull . length ; j ++ ) {
580- if ( trace . pull [ j ] > maxPull ) maxPull = trace . pull [ j ] ;
581- }
582- }
692+ maxPull = getMaxPull ( trace ) ;
583693
584694 cd0 . r = Math . min ( pieBoxWidth , pieBoxHeight ) / ( 2 + 2 * maxPull ) ;
585695
586696 cd0 . cx = plotSize . l + plotSize . w * ( trace . domain . x [ 1 ] + trace . domain . x [ 0 ] ) / 2 ;
587- cd0 . cy = plotSize . t + plotSize . h * ( 2 - trace . domain . y [ 1 ] - trace . domain . y [ 0 ] ) / 2 ;
697+ cd0 . cy = plotSize . t + plotSize . h * ( 1 - trace . domain . y [ 0 ] ) - pieBoxHeight / 2 ;
588698
589699 if ( trace . scalegroup && scaleGroups . indexOf ( trace . scalegroup ) === - 1 ) {
590700 scaleGroups . push ( trace . scalegroup ) ;
0 commit comments