@@ -128,50 +128,110 @@ export const MainGraph = ({
128128 }
129129 ] )
130130
131- // Create the SVG container.
131+ // create the SVG container
132132 const svg = d3 . select ( svgRef . current )
133133
134134 const maxXTicks = 8
135135 const xTickCount = Math . min ( remappedData . length , maxXTicks )
136- // Add the x-axis.
137- svg
136+ // add the x-axis
137+ let xAxisBoundingRect : DOMRect
138+ const xAxisSelection = svg
138139 . append ( 'g' )
140+ . attr ( 'class' , 'x-axis--container' )
139141 . attr ( 'transform' , `translate(0,${ yBottomEdge } )` )
140- . call (
141- d3
142- . axisBottom ( x )
143- . ticks ( xTickCount )
144- . tickSize ( 0 )
145- . tickFormat ( ( bucketIndex ) => {
146- // for low tick counts, it may try to render ticks
147- // with the index 0.5, 1.5, etc which don't have data defined
148- const datum = remappedData [ bucketIndex . valueOf ( ) ]
149- return datum
150- ? datum . timeLabel
151- ? getXLabel ( datum . timeLabel , {
152- shouldShowYear : hasMultipleYears ,
153- period,
154- interval,
155- bucketIndex : bucketIndex . valueOf ( ) ,
156- totalBuckets : remappedData . length
157- } )
142+ . each ( function ( ) {
143+ const elem = this as SVGGraphicsElement
144+ xAxisBoundingRect = elem . getBoundingClientRect ( )
145+ } )
146+
147+ // try a few x-axis labels until one fits
148+ const tries = xTickCount - 2
149+ for ( const [ index , tickCount ] of new Array ( tries )
150+ . fill ( null )
151+ . map ( ( _ , index ) => xTickCount - index )
152+ . entries ( ) ) {
153+ const axis = xAxisSelection
154+ . append ( 'g' )
155+ . attr ( 'class' , 'x-axis' )
156+ . attr ( 'opacity' , 0 )
157+ . call (
158+ d3
159+ . axisBottom ( x )
160+ . ticks ( tickCount )
161+ . tickSize ( 4 )
162+ . tickFormat ( ( bucketIndex ) => {
163+ // for low tick counts, it may try to render ticks
164+ // with the index 0.5, 1.5, etc which don't have data defined
165+ const datum = remappedData [ bucketIndex . valueOf ( ) ]
166+ return datum
167+ ? datum . timeLabel
168+ ? getXLabel ( datum . timeLabel , {
169+ shouldShowYear : hasMultipleYears ,
170+ period,
171+ interval,
172+ bucketIndex : bucketIndex . valueOf ( ) ,
173+ totalBuckets : remappedData . length
174+ } )
175+ : ''
158176 : ''
159- : ''
160- } )
161- )
162- . call ( ( g ) => g . select ( '.domain' ) . remove ( ) )
163- . call ( ( g ) => g . selectAll ( '.tick' ) . attr ( 'class' , 'tick group' ) )
164- . call ( ( g ) =>
165- g
166- . selectAll ( '.tick text' )
167- . attr ( 'class' , classNames ( tickClass , 'translate-y-2' ) )
177+ } )
178+ )
179+ . call ( ( g ) => g . select ( '.domain' ) . remove ( ) )
180+ . call ( ( g ) => g . selectAll ( '.tick' ) . attr ( 'class' , 'tick group' ) )
181+ . call ( ( g ) =>
182+ g . selectAll ( '.tick line' ) . attr ( 'class' , classNames ( xTickLineClass ) )
183+ )
184+ . call ( ( g ) =>
185+ g
186+ . selectAll ( '.tick text' )
187+ . attr ( 'class' , classNames ( tickTextClass , 'translate-y-2' ) )
188+ )
189+
190+ let overlapCount = 0
191+ let lastTickTextRightEdge = 0
192+ axis . call ( ( g ) =>
193+ g . selectAll ( '.tick text' ) . each ( function ( _ , index , groups ) {
194+ const elem = this as SVGGraphicsElement
195+ let textRect = elem . getBoundingClientRect ( )
196+
197+ const minX = xAxisBoundingRect . left
198+ const maxX = xAxisBoundingRect . left + width
199+
200+ // nudge over first tick text if needed, remeasure
201+ if ( index === 0 ) {
202+ const distanceFromAxisEdge = textRect . left - minX
203+ if ( distanceFromAxisEdge < 0 ) {
204+ d3 . select ( elem ) . attr ( 'dx' , - distanceFromAxisEdge )
205+ textRect = elem . getBoundingClientRect ( )
206+ }
207+ }
208+ // nudge back last tick text if needed, remeasure
209+ if ( index === groups . length - 1 ) {
210+ const distanceFromAxisEdge = maxX - textRect . right
211+ if ( distanceFromAxisEdge < 0 ) {
212+ d3 . select ( elem ) . attr ( 'dx' , distanceFromAxisEdge )
213+ textRect = elem . getBoundingClientRect ( )
214+ }
215+ }
216+ const isOverlappingPrevious = textRect . left < lastTickTextRightEdge
217+ if ( isOverlappingPrevious ) {
218+ overlapCount ++
219+ }
220+ lastTickTextRightEdge = textRect . right
221+ } )
168222 )
169- // Add the y-axis, remove the domain line, add grid lines and a label.
170- // TODO: make dynamic
171- // const maxYTicks = 8
223+ if ( overlapCount > 0 && index !== tries - 1 ) {
224+ axis . remove ( )
225+ } else {
226+ break
227+ }
228+ }
229+ xAxisSelection . call ( ( g ) => g . select ( '.x-axis' ) . attr ( 'opacity' , 1 ) )
230+
172231 const yTickCount = 8
173232 svg
174233 . append ( 'g' )
234+ . attr ( 'class' , 'y-axis--container' )
175235 . attr ( 'transform' , `translate(${ xLeftEdge } , 0)` )
176236 . call (
177237 d3
@@ -182,13 +242,13 @@ export const MainGraph = ({
182242 )
183243 . call ( ( g ) => g . select ( '.domain' ) . remove ( ) )
184244 . call ( ( g ) => g . selectAll ( '.tick' ) . attr ( 'class' , 'tick group' ) )
185- . call ( ( g ) => g . selectAll ( '.tick text' ) . attr ( 'class' , tickClass ) )
245+ . call ( ( g ) => g . selectAll ( '.tick text' ) . attr ( 'class' , tickTextClass ) )
186246 . call ( ( g ) =>
187247 g
188248 . selectAll ( '.tick line' )
189249 . clone ( )
190250 . attr ( 'x2' , chartAreaWidth )
191- . attr ( 'class' , tickLineClass )
251+ . attr ( 'class' , yTickLineClass )
192252 )
193253
194254 const mainGradientId = addGradient ( {
@@ -763,9 +823,10 @@ const paletteByTheme = {
763823 }
764824}
765825
766- const tickLineClass =
826+ const yTickLineClass =
767827 'stroke-gray-150 dark:stroke-gray-800/75 group-first:stroke-gray-300 dark:group-first:stroke-gray-700'
768- const tickClass = 'fill-gray-500 dark:fill-gray-400 text-xs'
828+ const tickTextClass = 'fill-gray-500 dark:fill-gray-400 text-xs'
829+ const xTickLineClass = 'stroke-gray-300 dark:stroke-gray-700'
769830
770831const mainDotClass = 'fill-indigo-500 dark:fill-indigo-400'
771832const comparisonDotClass = 'fill-indigo-500/20 dark:fill-indigo-400/20'
0 commit comments