@@ -22,6 +22,7 @@ var eventData = require('./event_data');
2222module . exports = function plot ( gd , cdpie ) {
2323 var fullLayout = gd . _fullLayout ;
2424
25+ prerenderTitles ( cdpie , gd ) ;
2526 scalePies ( cdpie , fullLayout . _size ) ;
2627
2728 var pieGroups = Lib . makeTraceGroups ( fullLayout . _pielayer , cdpie , 'trace' ) . each ( function ( cd ) {
@@ -308,6 +309,43 @@ module.exports = function plot(gd, cdpie) {
308309 } ) ;
309310 } ) ;
310311
312+ // add the title
313+ var titleTextGroup = d3 . select ( this ) . selectAll ( 'g.titletext' )
314+ . data ( trace . title ? [ 0 ] : [ ] ) ;
315+
316+ titleTextGroup . enter ( ) . append ( 'g' )
317+ . classed ( 'titletext' , true ) ;
318+ titleTextGroup . exit ( ) . remove ( ) ;
319+
320+ titleTextGroup . each ( function ( ) {
321+ var titleText = Lib . ensureSingle ( d3 . select ( this ) , 'text' , '' , function ( s ) {
322+ // prohibit tex interpretation as above
323+ s . attr ( 'data-notex' , 1 ) ;
324+ } ) ;
325+
326+ titleText . text ( trace . title )
327+ . attr ( {
328+ 'class' : 'titletext' ,
329+ transform : '' ,
330+ 'text-anchor' : 'middle' ,
331+ } )
332+ . call ( Drawing . font , trace . titlefont )
333+ . call ( svgTextUtils . convertToTspans , gd ) ;
334+
335+ var transform ;
336+
337+ if ( trace . titleposition === 'middle center' ) {
338+ transform = positionTitleInside ( cd0 ) ;
339+ } else {
340+ transform = positionTitleOutside ( cd0 , fullLayout . _size ) ;
341+ }
342+
343+ titleText . attr ( 'transform' ,
344+ 'translate(' + transform . x + ',' + transform . y + ')' +
345+ ( transform . scale < 1 ? ( 'scale(' + transform . scale + ')' ) : '' ) +
346+ 'translate(' + transform . tx + ',' + transform . ty + ')' ) ;
347+ } ) ;
348+
311349 // now make sure no labels overlap (at least within one pie)
312350 if ( hasOutsideText ) scootLabels ( quadrants , trace ) ;
313351 slices . each ( function ( pt ) {
@@ -371,6 +409,28 @@ module.exports = function plot(gd, cdpie) {
371409 } , 0 ) ;
372410} ;
373411
412+ function prerenderTitles ( cdpie , gd ) {
413+ var cd0 , trace ;
414+ // Determine the width and height of the title for each pie.
415+ for ( var i = 0 ; i < cdpie . length ; i ++ ) {
416+ cd0 = cdpie [ i ] [ 0 ] ;
417+ trace = cd0 . trace ;
418+
419+ if ( trace . title ) {
420+ var dummyTitle = Drawing . tester . append ( 'text' )
421+ . attr ( 'data-notex' , 1 )
422+ . text ( trace . title )
423+ . call ( Drawing . font , trace . titlefont )
424+ . call ( svgTextUtils . convertToTspans , gd ) ;
425+ var bBox = Drawing . bBox ( dummyTitle . node ( ) , true ) ;
426+ cd0 . titleBox = {
427+ width : bBox . width ,
428+ height : bBox . height ,
429+ } ;
430+ dummyTitle . remove ( ) ;
431+ }
432+ }
433+ }
374434
375435function transformInsideText ( textBB , pt , cd0 ) {
376436 var textDiameter = Math . sqrt ( textBB . width * textBB . width + textBB . height * textBB . height ) ;
@@ -454,6 +514,89 @@ function transformOutsideText(textBB, pt) {
454514 } ;
455515}
456516
517+ function positionTitleInside ( cd0 ) {
518+ var textDiameter =
519+ Math . sqrt ( cd0 . titleBox . width * cd0 . titleBox . width + cd0 . titleBox . height * cd0 . titleBox . height ) ;
520+ return {
521+ x : cd0 . cx ,
522+ y : cd0 . cy ,
523+ scale : cd0 . trace . hole * cd0 . r * 2 / textDiameter ,
524+ tx : 0 ,
525+ ty : - cd0 . titleBox . height / 2 + cd0 . trace . titlefont . size
526+ } ;
527+ }
528+
529+ function positionTitleOutside ( cd0 , plotSize ) {
530+ var scaleX = 1 , scaleY = 1 , maxWidth , maxPull ;
531+ var trace = cd0 . trace ;
532+ // position of the baseline point of the text box in the plot, before scaling.
533+ // we anchored the text in the middle, so the baseline is on the bottom middle
534+ // of the first line of text.
535+ var topMiddle = {
536+ x : cd0 . cx ,
537+ y : cd0 . cy
538+ } ;
539+ // relative translation of the text box after scaling
540+ var translate = {
541+ tx : 0 ,
542+ ty : 0
543+ } ;
544+
545+ // we reason below as if the baseline is the top middle point of the text box.
546+ // so we must add the font size to approximate the y-coord. of the top.
547+ // note that this correction must happen after scaling.
548+ translate . ty += trace . titlefont . size ;
549+ maxPull = getMaxPull ( trace ) ;
550+
551+ if ( trace . titleposition . indexOf ( 'top' ) !== - 1 ) {
552+ topMiddle . y -= ( 1 + maxPull ) * cd0 . r ;
553+ translate . ty -= cd0 . titleBox . height ;
554+ }
555+ else if ( trace . titleposition . indexOf ( 'bottom' ) !== - 1 ) {
556+ topMiddle . y += ( 1 + maxPull ) * cd0 . r ;
557+ }
558+
559+ if ( trace . titleposition . indexOf ( 'left' ) !== - 1 ) {
560+ // we start the text at the left edge of the pie
561+ maxWidth = plotSize . w * ( trace . domain . x [ 1 ] - trace . domain . x [ 0 ] ) / 2 + cd0 . r ;
562+ topMiddle . x -= ( 1 + maxPull ) * cd0 . r ;
563+ translate . tx += cd0 . titleBox . width / 2 ;
564+ } else if ( trace . titleposition . indexOf ( 'center' ) !== - 1 ) {
565+ maxWidth = plotSize . w * ( trace . domain . x [ 1 ] - trace . domain . x [ 0 ] ) ;
566+ } else if ( trace . titleposition . indexOf ( 'right' ) !== - 1 ) {
567+ maxWidth = plotSize . w * ( trace . domain . x [ 1 ] - trace . domain . x [ 0 ] ) / 2 + cd0 . r ;
568+ topMiddle . x += ( 1 + maxPull ) * cd0 . r ;
569+ translate . tx -= cd0 . titleBox . width / 2 ;
570+ }
571+ scaleX = maxWidth / cd0 . titleBox . width ;
572+ scaleY = getTitleSpace ( cd0 , plotSize ) / cd0 . titleBox . height ;
573+ return {
574+ x : topMiddle . x ,
575+ y : topMiddle . y ,
576+ scale : Math . min ( scaleX , scaleY ) ,
577+ tx : translate . tx ,
578+ ty : translate . ty
579+ } ;
580+ }
581+
582+ function getTitleSpace ( cd0 , plotSize ) {
583+ var trace = cd0 . trace ;
584+ var pieBoxHeight = plotSize . h * ( trace . domain . y [ 1 ] - trace . domain . y [ 0 ] ) ;
585+ // use at most half of the plot for the title
586+ return Math . min ( cd0 . titleBox . height , pieBoxHeight / 2 ) ;
587+ }
588+
589+ function getMaxPull ( trace ) {
590+ var maxPull = trace . pull , j ;
591+ if ( Array . isArray ( maxPull ) ) {
592+ maxPull = 0 ;
593+ for ( j = 0 ; j < trace . pull . length ; j ++ ) {
594+ if ( trace . pull [ j ] > maxPull ) maxPull = trace . pull [ j ] ;
595+ }
596+ }
597+ return maxPull ;
598+ }
599+
457600function scootLabels ( quadrants , trace ) {
458601 var xHalf , yHalf , equatorFirst , farthestX , farthestY ,
459602 xDiffSign , yDiffSign , thisQuad , oppositeQuad ,
@@ -570,21 +713,23 @@ function scalePies(cdpie, plotSize) {
570713 for ( i = 0 ; i < cdpie . length ; i ++ ) {
571714 cd0 = cdpie [ i ] [ 0 ] ;
572715 trace = cd0 . trace ;
716+
573717 pieBoxWidth = plotSize . w * ( trace . domain . x [ 1 ] - trace . domain . x [ 0 ] ) ;
574718 pieBoxHeight = plotSize . h * ( trace . domain . y [ 1 ] - trace . domain . y [ 0 ] ) ;
575-
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- }
719+ // leave some space for the title, if it will be displayed outside
720+ if ( trace . title && trace . titleposition !== 'middle center' ) {
721+ pieBoxHeight -= getTitleSpace ( cd0 , plotSize ) ;
582722 }
583723
724+ maxPull = getMaxPull ( trace ) ;
725+
584726 cd0 . r = Math . min ( pieBoxWidth , pieBoxHeight ) / ( 2 + 2 * maxPull ) ;
585727
586728 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 ;
729+ cd0 . cy = plotSize . t + plotSize . h * ( 1 - trace . domain . y [ 0 ] ) - pieBoxHeight / 2 ;
730+ if ( trace . title && trace . titleposition . indexOf ( 'bottom' ) !== - 1 ) {
731+ cd0 . cy -= getTitleSpace ( cd0 , plotSize ) ;
732+ }
588733
589734 if ( trace . scalegroup && scaleGroups . indexOf ( trace . scalegroup ) === - 1 ) {
590735 scaleGroups . push ( trace . scalegroup ) ;
0 commit comments