@@ -812,6 +812,52 @@ document.addEventListener("DOMContentLoaded", function () {
812812 . text ( title ) ;
813813 }
814814
815+ function wrapSvgText ( textSelection , maxWidth ) {
816+ textSelection . each ( function ( ) {
817+ const text = d3 . select ( this ) ;
818+ const originalText = text . text ( ) ;
819+
820+ const words = originalText
821+ . replace ( / \s + · \s + / g, " · " )
822+ . split ( / \s + / )
823+ . reverse ( ) ;
824+
825+ let word ;
826+ let line = [ ] ;
827+ let lineNumber = 0 ;
828+
829+ const lineHeight = 1.1 ;
830+ const x = text . attr ( "x" ) ;
831+ const y = text . attr ( "y" ) ;
832+ const dy = parseFloat ( text . attr ( "dy" ) || 0 ) ;
833+
834+ text . text ( null ) ;
835+
836+ let tspan = text . append ( "tspan" )
837+ . attr ( "x" , x )
838+ . attr ( "y" , y )
839+ . attr ( "dy" , `${ dy } em` ) ;
840+
841+ while ( ( word = words . pop ( ) ) ) {
842+ line . push ( word ) ;
843+ tspan . text ( line . join ( " " ) ) ;
844+
845+ if ( tspan . node ( ) . getComputedTextLength ( ) > maxWidth && line . length > 1 ) {
846+ line . pop ( ) ;
847+ tspan . text ( line . join ( " " ) ) ;
848+
849+ line = [ word ] ;
850+
851+ tspan = text . append ( "tspan" )
852+ . attr ( "x" , x )
853+ . attr ( "y" , y )
854+ . attr ( "dy" , `${ ++ lineNumber * lineHeight + dy } em` )
855+ . text ( word ) ;
856+ }
857+ }
858+ } ) ;
859+ }
860+
815861 function renderChangeBars ( data , indicator , endYear ) {
816862 const container = d3 . select ( "#change-chart" ) ;
817863 if ( container . empty ( ) ) return ;
@@ -893,10 +939,14 @@ document.addEventListener("DOMContentLoaded", function () {
893939 } )
894940 ) ;
895941
896- svg . append ( "g" )
942+ const yAxis = svg . append ( "g" )
897943 . attr ( "transform" , `translate(${ margin . left } ,0)` )
898944 . call ( d3 . axisLeft ( y ) ) ;
899945
946+ yAxis . selectAll ( ".tick text" )
947+ . attr ( "font-size" , 12 )
948+ . call ( wrapSvgText , margin . left - 24 ) ;
949+
900950 svg . selectAll ( "rect.bar" )
901951 . data ( rows )
902952 . enter ( )
@@ -915,21 +965,46 @@ document.addEventListener("DOMContentLoaded", function () {
915965 . append ( "text" )
916966 . attr ( "class" , "value" )
917967 . attr ( "x" , ( d ) => {
918- const offset = d . delta >= 0 ? 8 : - 8 ;
919- const rawX = x ( d . delta ) + offset ;
920- return Math . max ( margin . left + 6 , Math . min ( width - margin . right - 6 , rawX ) ) ;
968+ const zeroX = x ( 0 ) ;
969+ const endX = x ( d . delta ) ;
970+
971+ const outsideOffset = d . delta >= 0 ? 8 : - 8 ;
972+
973+ // Normal outside-label position
974+ let labelX = endX + outsideOffset ;
975+
976+ // If a negative label would crash into the y-axis labels,
977+ // move it inside the bar near the zero line instead.
978+ if ( d . delta < 0 && labelX < margin . left + 50 ) {
979+ labelX = endX + 50 ;
980+ }
981+
982+ // If a positive label would fly off the right edge, pull it inside.
983+ if ( d . delta > 0 && labelX > width - margin . right - 12 ) {
984+ labelX = width - margin . right - 12 ;
985+ }
986+
987+ return labelX ;
921988 } )
922989 . attr ( "y" , ( d ) => y ( d . category ) + y . bandwidth ( ) / 2 + 4 )
923990 . attr ( "text-anchor" , ( d ) => {
924- const offset = d . delta >= 0 ? 8 : - 8 ;
925- const rawX = x ( d . delta ) + offset ;
991+ const zeroX = x ( 0 ) ;
992+ const endX = x ( d . delta ) ;
926993
927- if ( rawX > width - margin . right - 6 ) return "end" ;
928- if ( rawX < margin . left + 6 ) return "start" ;
994+ let labelX = endX + ( d . delta >= 0 ? 8 : - 8 ) ;
995+
996+ if ( d . delta < 0 && labelX < margin . left + 12 ) {
997+ return "end" ;
998+ }
999+
1000+ if ( d . delta > 0 && labelX > width - margin . right - 12 ) {
1001+ return "end" ;
1002+ }
9291003
9301004 return d . delta >= 0 ? "start" : "end" ;
9311005 } )
9321006 . attr ( "font-size" , 12 )
1007+ . attr ( "font-weight" , 600 )
9331008 . attr ( "fill" , "#333" )
9341009 . text ( ( d ) => formatValue ( d . delta ) ) ;
9351010
0 commit comments