@@ -16,6 +16,7 @@ var Color = require('../../components/color');
16
16
var Drawing = require ( '../../components/drawing' ) ;
17
17
var Lib = require ( '../../lib' ) ;
18
18
var svgTextUtils = require ( '../../lib/svg_text_utils' ) ;
19
+ var recordMinTextSize = require ( '../bar/plot' ) . recordMinTextSize ;
19
20
20
21
var helpers = require ( './helpers' ) ;
21
22
var eventData = require ( './event_data' ) ;
@@ -52,7 +53,7 @@ function plot(gd, cdModule) {
52
53
] ;
53
54
var hasOutsideText = false ;
54
55
55
- slices . each ( function ( pt ) {
56
+ slices . each ( function ( pt , i ) {
56
57
if ( pt . hidden ) {
57
58
d3 . select ( this ) . selectAll ( 'path,g' ) . remove ( ) ;
58
59
return ;
@@ -131,7 +132,7 @@ function plot(gd, cdModule) {
131
132
formatSliceLabel ( gd , pt , cd0 ) ;
132
133
var textPosition = helpers . castOption ( trace . textposition , pt . pts ) ;
133
134
var sliceTextGroup = sliceTop . selectAll ( 'g.slicetext' )
134
- . data ( pt . text && ( textPosition !== 'none' ) ? [ 0 ] : [ ] ) ;
135
+ . data ( pt . text && ( textPosition !== 'none' ) ? [ pt ] : [ ] ) ;
135
136
136
137
sliceTextGroup . enter ( ) . append ( 'g' )
137
138
. classed ( 'slicetext' , true ) ;
@@ -144,15 +145,19 @@ function plot(gd, cdModule) {
144
145
s . attr ( 'data-notex' , 1 ) ;
145
146
} ) ;
146
147
148
+ var font = Lib . extendFlat ( { } , textPosition === 'outside' ?
149
+ determineOutsideTextFont ( trace , pt , fullLayout . font ) :
150
+ determineInsideTextFont ( trace , pt , fullLayout . font ) , { }
151
+ ) ;
152
+ font . size = Math . max ( font . size , fullLayout . uniformtext . minsize || 0 ) ;
153
+
147
154
sliceText . text ( pt . text )
148
155
. attr ( {
149
156
'class' : 'slicetext' ,
150
157
transform : '' ,
151
158
'text-anchor' : 'middle'
152
159
} )
153
- . call ( Drawing . font , textPosition === 'outside' ?
154
- determineOutsideTextFont ( trace , pt , gd . _fullLayout . font ) :
155
- determineInsideTextFont ( trace , pt , gd . _fullLayout . font ) )
160
+ . call ( Drawing . font , font )
156
161
. call ( svgTextUtils . convertToTspans , gd ) ;
157
162
158
163
// position the text relative to the slice
@@ -164,36 +169,39 @@ function plot(gd, cdModule) {
164
169
} else {
165
170
transform = transformInsideText ( textBB , pt , cd0 ) ;
166
171
if ( textPosition === 'auto' && transform . scale < 1 ) {
167
- sliceText . call ( Drawing . font , trace . outsidetextfont ) ;
168
- if ( trace . outsidetextfont . family !== trace . insidetextfont . family ||
169
- trace . outsidetextfont . size !== trace . insidetextfont . size ) {
172
+ var newFont = Lib . extendFlat ( { } , trace . outsidetextfont , { } ) ;
173
+ newFont . size = Math . max ( newFont . size , fullLayout . uniformtext . minsize || 0 ) ;
174
+
175
+ sliceText . call ( Drawing . font , newFont ) ;
176
+ if ( newFont . family !== font . family || newFont . size !== font . size ) {
177
+ // recompute bounding box
170
178
textBB = Drawing . bBox ( sliceText . node ( ) ) ;
171
179
}
172
180
transform = transformOutsideText ( textBB , pt ) ;
173
181
}
174
182
}
175
183
176
- var translateX = cx + pt . pxmid [ 0 ] * transform . rCenter + ( transform . x || 0 ) ;
177
- var translateY = cy + pt . pxmid [ 1 ] * transform . rCenter + ( transform . y || 0 ) ;
184
+ var pxtxt = pt . pxtxt || pt . pxmid ;
185
+ transform . targetX = cx + pxtxt [ 0 ] * transform . rCenter + ( transform . x || 0 ) ;
186
+ transform . targetY = cy + pxtxt [ 1 ] * transform . rCenter + ( transform . y || 0 ) ;
187
+ computeTransform ( transform , textBB ) ;
178
188
179
189
// save some stuff to use later ensure no labels overlap
180
190
if ( transform . outside ) {
181
- pt . yLabelMin = translateY - textBB . height / 2 ;
182
- pt . yLabelMid = translateY ;
183
- pt . yLabelMax = translateY + textBB . height / 2 ;
191
+ var targetY = transform . targetY ;
192
+ pt . yLabelMin = targetY - textBB . height / 2 ;
193
+ pt . yLabelMid = targetY ;
194
+ pt . yLabelMax = targetY + textBB . height / 2 ;
184
195
pt . labelExtraX = 0 ;
185
196
pt . labelExtraY = 0 ;
186
197
hasOutsideText = true ;
187
198
}
188
199
189
- sliceText . attr ( 'transform' ,
190
- 'translate(' + translateX + ',' + translateY + ')' +
191
- ( transform . scale < 1 ? ( 'scale(' + transform . scale + ')' ) : '' ) +
192
- ( transform . rotate ? ( 'rotate(' + transform . rotate + ')' ) : '' ) +
193
- 'translate(' +
194
- ( - ( textBB . left + textBB . right ) / 2 ) + ',' +
195
- ( - ( textBB . top + textBB . bottom ) / 2 ) +
196
- ')' ) ;
200
+ transform . fontSize = font . size ;
201
+ recordMinTextSize ( trace . type , transform , fullLayout ) ;
202
+ cd [ i ] . transform = transform ;
203
+
204
+ sliceText . attr ( 'transform' , Lib . getTextTransform ( transform , true ) ) ;
197
205
} ) ;
198
206
} ) ;
199
207
@@ -298,8 +306,10 @@ function plotTextLines(slices, trace) {
298
306
// first move the text to its new location
299
307
var sliceText = sliceTop . select ( 'g.slicetext text' ) ;
300
308
301
- sliceText . attr ( 'transform' , 'translate(' + pt . labelExtraX + ',' + pt . labelExtraY + ')' +
302
- sliceText . attr ( 'transform' ) ) ;
309
+ pt . transform . targetX += pt . labelExtraX ;
310
+ pt . transform . targetY += pt . labelExtraY ;
311
+
312
+ sliceText . attr ( 'transform' , Lib . getTextTransform ( pt . transform ) ) ;
303
313
304
314
// then add a line to the new location
305
315
var lineStartX = pt . cxFinal + pt . pxmid [ 0 ] ;
@@ -549,59 +559,126 @@ function prerenderTitles(cdModule, gd) {
549
559
550
560
function transformInsideText ( textBB , pt , cd0 ) {
551
561
var textDiameter = Math . sqrt ( textBB . width * textBB . width + textBB . height * textBB . height ) ;
552
- var textAspect = textBB . width / textBB . height ;
553
562
var halfAngle = pt . halfangle ;
563
+ var midAngle = pt . midangle ;
554
564
var ring = pt . ring ;
555
565
var rInscribed = pt . rInscribed ;
556
566
var r = cd0 . r || pt . rpx1 ;
567
+ var orientation = cd0 . trace . insidetextorientation ;
568
+ var allTransforms = [ ] ;
569
+
570
+ var isCircle = ( ring === 1 ) && ( Math . abs ( pt . startangle - pt . stopangle ) === Math . PI * 2 ) ;
571
+
572
+ if ( isCircle || orientation === 'auto' || orientation === 'h' ) {
573
+ // max size text can be inserted inside without rotating it
574
+ // this inscribes the text rectangle in a circle, which is then inscribed
575
+ // in the slice, so it will be an underestimate, which some day we may want
576
+ // to improve so this case can get more use
577
+ var transform = {
578
+ scale : rInscribed * r * 2 / textDiameter ,
579
+
580
+ // and the center position and rotation in this case
581
+ rCenter : 1 - rInscribed ,
582
+ rotate : 0
583
+ } ;
557
584
558
- // max size text can be inserted inside without rotating it
559
- // this inscribes the text rectangle in a circle, which is then inscribed
560
- // in the slice, so it will be an underestimate, which some day we may want
561
- // to improve so this case can get more use
562
- var transform = {
563
- scale : rInscribed * r * 2 / textDiameter ,
585
+ if ( transform . scale >= 1 ) return transform ;
564
586
565
- // and the center position and rotation in this case
566
- rCenter : 1 - rInscribed ,
567
- rotate : 0
568
- } ;
587
+ allTransforms . push ( transform ) ;
588
+ }
569
589
570
- if ( transform . scale >= 1 ) return transform ;
590
+ if ( orientation === 'h' ) {
591
+ // max size if text is placed (horizontally) at the top or bottom of the arc
571
592
572
- // max size if text is rotated radially
573
- var Qr = textAspect + 1 / ( 2 * Math . tan ( halfAngle ) ) ;
574
- var maxHalfHeightRotRadial = r * Math . min (
575
- 1 / ( Math . sqrt ( Qr * Qr + 0.5 ) + Qr ) ,
576
- ring / ( Math . sqrt ( textAspect * textAspect + ring / 2 ) + textAspect )
593
+ var considerCrossing = function ( angle , key ) {
594
+ if ( isCrossing ( pt , angle ) ) {
595
+ var dStart = Math . abs ( angle - pt . startangle ) ;
596
+ var dStop = Math . abs ( angle - pt . stopangle ) ;
597
+
598
+ var closestEdge = dStart < dStop ? dStart : dStop ;
599
+
600
+ var newT ;
601
+ if ( key === 'tan' ) {
602
+ newT = calcTanTransform ( textBB , r , ring , closestEdge , 0 ) ;
603
+ } else { // case of 'rad'
604
+ newT = calcRadTransform ( textBB , r , ring , closestEdge , Math . PI / 2 ) ;
605
+ }
606
+ newT . _repos = getCoords ( r , angle ) ;
607
+
608
+ allTransforms . push ( newT ) ;
609
+ }
610
+ } ;
611
+
612
+ for ( var i = 3 ; i >= - 3 ; i -- ) { // to cover all cases with trace.rotation added
613
+ considerCrossing ( Math . PI * ( i + 0.0 ) , 'tan' ) ;
614
+ considerCrossing ( Math . PI * ( i + 0.5 ) , 'rad' ) ;
615
+ }
616
+ }
617
+
618
+ if ( orientation === 'auto' || orientation === 'r' ) {
619
+ allTransforms . push ( calcRadTransform ( textBB , r , ring , halfAngle , midAngle ) ) ;
620
+ }
621
+
622
+ if ( orientation === 'auto' || orientation === 't' ) {
623
+ allTransforms . push ( calcTanTransform ( textBB , r , ring , halfAngle , midAngle ) ) ;
624
+ }
625
+
626
+ var maxScaleTransform = allTransforms . sort ( function ( a , b ) {
627
+ return b . scale - a . scale ;
628
+ } ) [ 0 ] ;
629
+
630
+ if ( maxScaleTransform . _repos ) {
631
+ pt . pxtxt = maxScaleTransform . _repos ;
632
+ }
633
+
634
+ return maxScaleTransform ;
635
+ }
636
+
637
+ function isCrossing ( pt , angle ) {
638
+ var start = pt . startangle ;
639
+ var stop = pt . stopangle ;
640
+ return (
641
+ ( start > angle && angle > stop ) ||
642
+ ( start < angle && angle < stop )
577
643
) ;
578
- var radialTransform = {
579
- scale : maxHalfHeightRotRadial * 2 / textBB . height ,
580
- rCenter : Math . cos ( maxHalfHeightRotRadial / r ) -
581
- maxHalfHeightRotRadial * textAspect / r ,
582
- rotate : ( 180 / Math . PI * pt . midangle + 720 ) % 180 - 90
644
+ }
645
+
646
+ function calcRadTransform ( textBB , r , ring , halfAngle , midAngle ) {
647
+ // max size if text is rotated radially
648
+ var a = textBB . width / textBB . height ;
649
+ var s = calcMaxHalfSize ( a , halfAngle , r , ring ) ;
650
+ return {
651
+ scale : s * 2 / textBB . height ,
652
+ rCenter : calcRCenter ( a , s / r ) ,
653
+ rotate : calcRotate ( midAngle )
583
654
} ;
655
+ }
584
656
657
+ function calcTanTransform ( textBB , r , ring , halfAngle , midAngle ) {
585
658
// max size if text is rotated tangentially
586
- var aspectInv = 1 / textAspect ;
587
- var Qt = aspectInv + 1 / ( 2 * Math . tan ( halfAngle ) ) ;
588
- var maxHalfWidthTangential = r * Math . min (
589
- 1 / ( Math . sqrt ( Qt * Qt + 0.5 ) + Qt ) ,
590
- ring / ( Math . sqrt ( aspectInv * aspectInv + ring / 2 ) + aspectInv )
591
- ) ;
592
- var tangentialTransform = {
593
- scale : maxHalfWidthTangential * 2 / textBB . width ,
594
- rCenter : Math . cos ( maxHalfWidthTangential / r ) -
595
- maxHalfWidthTangential / textAspect / r ,
596
- rotate : ( 180 / Math . PI * pt . midangle + 810 ) % 180 - 90
659
+ var a = textBB . height / textBB . width ;
660
+ var s = calcMaxHalfSize ( a , halfAngle , r , ring ) ;
661
+ return {
662
+ scale : s * 2 / textBB . width ,
663
+ rCenter : calcRCenter ( a , s / r ) ,
664
+ rotate : calcRotate ( midAngle + Math . PI / 2 )
597
665
} ;
598
- // if we need a rotated transform, pick the biggest one
599
- // even if both are bigger than 1
600
- var rotatedTransform = tangentialTransform . scale > radialTransform . scale ?
601
- tangentialTransform : radialTransform ;
666
+ }
667
+
668
+ function calcRCenter ( a , b ) {
669
+ return Math . cos ( b ) - a * b ;
670
+ }
671
+
672
+ function calcRotate ( t ) {
673
+ return ( 180 / Math . PI * t + 720 ) % 180 - 90 ;
674
+ }
602
675
603
- if ( transform . scale < 1 && rotatedTransform . scale > transform . scale ) return rotatedTransform ;
604
- return transform ;
676
+ function calcMaxHalfSize ( a , halfAngle , r , ring ) {
677
+ var q = a + 1 / ( 2 * Math . tan ( halfAngle ) ) ;
678
+ return r * Math . min (
679
+ 1 / ( Math . sqrt ( q * q + 0.5 ) + q ) ,
680
+ ring / ( Math . sqrt ( a * a + ring / 2 ) + a )
681
+ ) ;
605
682
}
606
683
607
684
function getInscribedRadiusFraction ( pt , cd0 ) {
@@ -921,6 +998,7 @@ function groupScale(cdModule, scaleGroups) {
921
998
922
999
function setCoords ( cd ) {
923
1000
var cd0 = cd [ 0 ] ;
1001
+ var r = cd0 . r ;
924
1002
var trace = cd0 . trace ;
925
1003
var currentAngle = trace . rotation * Math . PI / 180 ;
926
1004
var angleFactor = 2 * Math . PI / cd0 . vTotal ;
@@ -941,24 +1019,21 @@ function setCoords(cd) {
941
1019
lastPt = 'px0' ;
942
1020
}
943
1021
944
- function getCoords ( angle ) {
945
- return [ cd0 . r * Math . sin ( angle ) , - cd0 . r * Math . cos ( angle ) ] ;
946
- }
947
-
948
- currentCoords = getCoords ( currentAngle ) ;
1022
+ currentCoords = getCoords ( r , currentAngle ) ;
949
1023
950
1024
for ( i = 0 ; i < cd . length ; i ++ ) {
951
1025
cdi = cd [ i ] ;
952
1026
if ( cdi . hidden ) continue ;
953
1027
954
1028
cdi [ firstPt ] = currentCoords ;
955
1029
1030
+ cdi . startangle = currentAngle ;
956
1031
currentAngle += angleFactor * cdi . v / 2 ;
957
- cdi . pxmid = getCoords ( currentAngle ) ;
1032
+ cdi . pxmid = getCoords ( r , currentAngle ) ;
958
1033
cdi . midangle = currentAngle ;
959
-
960
1034
currentAngle += angleFactor * cdi . v / 2 ;
961
- currentCoords = getCoords ( currentAngle ) ;
1035
+ currentCoords = getCoords ( r , currentAngle ) ;
1036
+ cdi . stopangle = currentAngle ;
962
1037
963
1038
cdi [ lastPt ] = currentCoords ;
964
1039
@@ -970,6 +1045,10 @@ function setCoords(cd) {
970
1045
}
971
1046
}
972
1047
1048
+ function getCoords ( r , angle ) {
1049
+ return [ r * Math . sin ( angle ) , - r * Math . cos ( angle ) ] ;
1050
+ }
1051
+
973
1052
function formatSliceLabel ( gd , pt , cd0 ) {
974
1053
var fullLayout = gd . _fullLayout ;
975
1054
var trace = cd0 . trace ;
@@ -1024,6 +1103,24 @@ function formatSliceLabel(gd, pt, cd0) {
1024
1103
}
1025
1104
}
1026
1105
}
1106
+
1107
+ function computeTransform (
1108
+ transform , // inout
1109
+ textBB // in
1110
+ ) {
1111
+ var rotate = transform . rotate ;
1112
+ var scale = transform . scale ;
1113
+ if ( scale > 1 ) scale = 1 ;
1114
+
1115
+ var a = rotate * Math . PI / 180 ;
1116
+ var cosA = Math . cos ( a ) ;
1117
+ var sinA = Math . sin ( a ) ;
1118
+ var midX = ( textBB . left + textBB . right ) / 2 ;
1119
+ var midY = ( textBB . top + textBB . bottom ) / 2 ;
1120
+ transform . textX = midX * cosA - midY * sinA ;
1121
+ transform . textY = midX * sinA + midY * cosA ;
1122
+ }
1123
+
1027
1124
module . exports = {
1028
1125
plot : plot ,
1029
1126
formatSliceLabel : formatSliceLabel ,
@@ -1033,4 +1130,5 @@ module.exports = {
1033
1130
prerenderTitles : prerenderTitles ,
1034
1131
layoutAreas : layoutAreas ,
1035
1132
attachFxHandlers : attachFxHandlers ,
1133
+ computeTransform : computeTransform
1036
1134
} ;
0 commit comments