@@ -8,6 +8,9 @@ var Registry = require('../../registry');
88var Axes = require ( '../../plots/cartesian/axes' ) ;
99var getAxisGroup = require ( '../../plots/cartesian/constraints' ) . getAxisGroup ;
1010var Sieve = require ( './sieve.js' ) ;
11+ var TEXTPAD = require ( './constants' ) . TEXTPAD ;
12+ var Drawing = require ( '../../components/drawing' ) ;
13+ var d3 = require ( '@plotly/d3' ) ;
1114
1215/*
1316 * Bar chart stacking/grouping positioning and autoscaling calculations
@@ -567,10 +570,14 @@ function setBaseAndTop(sa, sieve) {
567570 }
568571 }
569572
570- fullTrace . _extremes [ sa . _id ] = Axes . findExtremes ( sa , pts , {
573+ var extremesOpts = {
571574 tozero : tozero ,
572575 padded : true
573- } ) ;
576+ } ;
577+
578+ addTextPadding ( extremesOpts , fullTrace , sa ) ;
579+
580+ fullTrace . _extremes [ sa . _id ] = Axes . findExtremes ( sa , pts , extremesOpts ) ;
574581 }
575582}
576583
@@ -641,12 +648,17 @@ function stackBars(sa, sieve, opts) {
641648
642649 // if barnorm is set, let normalizeBars update the axis range
643650 if ( ! opts . norm ) {
644- fullTrace . _extremes [ sa . _id ] = Axes . findExtremes ( sa , pts , {
651+ var extremesOpts = {
645652 // N.B. we don't stack base with 'base',
646653 // so set tozero:true always!
647654 tozero : true ,
648655 padded : true
649- } ) ;
656+ } ;
657+
658+ // Add text padding if needed
659+ addTextPadding ( extremesOpts , fullTrace , sa ) ;
660+
661+ fullTrace . _extremes [ sa . _id ] = Axes . findExtremes ( sa , pts , extremesOpts ) ;
650662 }
651663 }
652664}
@@ -752,10 +764,13 @@ function normalizeBars(sa, sieve, opts) {
752764 }
753765 }
754766
755- fullTrace . _extremes [ sa . _id ] = Axes . findExtremes ( sa , pts , {
767+ var extremesOpts = {
756768 tozero : tozero ,
757769 padded : padded
758- } ) ;
770+ } ;
771+ addTextPadding ( extremesOpts , fullTrace , sa ) ;
772+
773+ fullTrace . _extremes [ sa . _id ] = Axes . findExtremes ( sa , pts , extremesOpts ) ;
759774 }
760775}
761776
@@ -864,6 +879,98 @@ function getAxisLetter(ax) {
864879 return ax . _id . charAt ( 0 ) ;
865880}
866881
882+ function measureTextWidth ( txt , font ) {
883+ var element = document . createElementNS ( 'http://www.w3.org/2000/svg' , 'text' ) ;
884+ var sel = d3 . select ( element ) ;
885+ sel . text ( txt )
886+ . attr ( 'x' , 0 )
887+ . attr ( 'y' , 0 )
888+ . attr ( 'data-unformatted' , txt )
889+ . call ( Drawing . font , font ) ;
890+
891+ var width = Drawing . bBox ( sel . node ( ) ) . width ;
892+
893+ // Clean up the temporary element
894+ element . remove ( ) ;
895+
896+ return width ;
897+ }
898+
899+ function findExtremeIndices ( values ) {
900+ var maxPositiveIdx = - 1 , maxNegativeIdx = - 1 ;
901+ var maxPositiveVal = - Infinity , maxNegativeVal = Infinity ;
902+
903+ for ( var i = 0 ; i < values . length ; i ++ ) {
904+ if ( values [ i ] > 0 && values [ i ] > maxPositiveVal ) {
905+ maxPositiveVal = values [ i ] ;
906+ maxPositiveIdx = i ;
907+ }
908+ if ( values [ i ] < 0 && values [ i ] < maxNegativeVal ) {
909+ maxNegativeVal = values [ i ] ;
910+ maxNegativeIdx = i ;
911+ }
912+ }
913+ return { positive : maxPositiveIdx , negative : maxNegativeIdx } ;
914+ }
915+
916+ function getMaxLineCount ( texts ) {
917+ var maxLines = 1 ;
918+ for ( var i = 0 ; i < texts . length ; i ++ ) {
919+ if ( texts [ i ] ) {
920+ var lineCount = ( texts [ i ] . toString ( ) . match ( / < b r > / gi) || [ ] ) . length + 1 ;
921+ maxLines = Math . max ( maxLines , lineCount ) ;
922+ }
923+ }
924+ return maxLines ;
925+ }
926+
927+ function addTextPadding ( extremesOpts , trace , sa ) {
928+ var textpos = trace . textposition ;
929+ if ( ! trace . text || ! textpos ) return ;
930+
931+ var hasOutsideText = Array . isArray ( textpos ) ?
932+ textpos . indexOf ( 'outside' ) !== - 1 : textpos === 'outside' ;
933+ if ( ! hasOutsideText ) return ;
934+
935+ var font = trace . outsidetextfont || trace . textfont ;
936+ var fontSize = font && font . size ;
937+ if ( ! fontSize ) return ;
938+
939+ if ( Array . isArray ( fontSize ) ) {
940+ fontSize = Math . max . apply ( Math , fontSize . filter ( function ( s ) { return typeof s === 'number' ; } ) ) ;
941+ if ( ! fontSize ) return ;
942+ }
943+
944+ var isHorizontal = trace . orientation === 'h' ;
945+ var values = isHorizontal ? trace . x : trace . y ;
946+ if ( ! values ) return ;
947+
948+ var extremes = findExtremeIndices ( values ) ;
949+ var positivePadding = 0 , negativePadding = 0 ;
950+
951+ if ( isHorizontal ) {
952+ if ( extremes . positive >= 0 && trace . text [ extremes . positive ] ) {
953+ positivePadding = measureTextWidth ( trace . text [ extremes . positive ] , font ) + TEXTPAD ;
954+ }
955+ if ( extremes . negative >= 0 && trace . text [ extremes . negative ] ) {
956+ negativePadding = measureTextWidth ( trace . text [ extremes . negative ] , font ) + TEXTPAD ;
957+ }
958+ } else {
959+ // For vertical bars, calculate height based on text for each extreme bar
960+ if ( extremes . positive >= 0 && trace . text [ extremes . positive ] ) {
961+ var positiveTexts = [ trace . text [ extremes . positive ] ] ;
962+ positivePadding = fontSize * getMaxLineCount ( positiveTexts ) + TEXTPAD ;
963+ }
964+ if ( extremes . negative >= 0 && trace . text [ extremes . negative ] ) {
965+ var negativeTexts = [ trace . text [ extremes . negative ] ] ;
966+ negativePadding = fontSize * getMaxLineCount ( negativeTexts ) + TEXTPAD ;
967+ }
968+ }
969+
970+ if ( extremes . positive >= 0 && positivePadding > 0 ) extremesOpts . ppadplus = positivePadding ;
971+ if ( extremes . negative >= 0 && negativePadding > 0 ) extremesOpts . ppadminus = negativePadding ;
972+ }
973+
867974module . exports = {
868975 crossTraceCalc : crossTraceCalc ,
869976 setGroupPositions : setGroupPositions
0 commit comments