@@ -32,62 +32,79 @@ const useUtilityClasses = (ownerState: AxisConfig<any, any, ChartsXAxisProps>) =
32
32
return composeClasses ( slots , getAxisUtilityClass , classes ) ;
33
33
} ;
34
34
35
- type LabelExtraData = { width : number ; height : number ; skipLabel ?: boolean } ;
36
-
37
- function addLabelDimension (
35
+ /* Returns a set of indices of the tick labels that should be visible. */
36
+ function getVisibleLabels (
38
37
xTicks : TickItemType [ ] ,
39
38
{
40
39
tickLabelStyle : style ,
41
40
tickLabelInterval,
42
41
tickLabelMinGap,
43
42
reverse,
44
43
isMounted,
44
+ isPointInside,
45
45
} : Pick < ChartsXAxisProps , 'tickLabelInterval' | 'tickLabelStyle' > &
46
46
Pick < AxisDefaultized < ScaleName , any , ChartsXAxisProps > , 'reverse' > & {
47
47
isMounted : boolean ;
48
48
tickLabelMinGap : NonNullable < ChartsXAxisProps [ 'tickLabelMinGap' ] > ;
49
+ isPointInside : ( position : number ) => boolean ;
49
50
} ,
50
- ) : ( TickItemType & LabelExtraData ) [ ] {
51
- const withDimension = xTicks . map ( ( tick ) => {
51
+ ) : Set < TickItemType > {
52
+ const getTickLabelSize = ( tick : TickItemType ) => {
52
53
if ( ! isMounted || tick . formattedValue === undefined ) {
53
- return { ... tick , width : 0 , height : 0 } ;
54
+ return { width : 0 , height : 0 } ;
54
55
}
56
+
55
57
const tickSizes = getWordsByLines ( { style, needsComputation : true , text : tick . formattedValue } ) ;
58
+
56
59
return {
57
- ...tick ,
58
60
width : Math . max ( ...tickSizes . map ( ( size ) => size . width ) ) ,
59
61
height : Math . max ( tickSizes . length * tickSizes [ 0 ] . height ) ,
60
62
} ;
61
- } ) ;
63
+ } ;
62
64
63
65
if ( typeof tickLabelInterval === 'function' ) {
64
- return withDimension . map ( ( item , index ) => ( {
65
- ...item ,
66
- skipLabel : ! tickLabelInterval ( item . value , index ) ,
67
- } ) ) ;
66
+ return new Set ( xTicks . filter ( ( item , index ) => tickLabelInterval ( item . value , index ) ) ) ;
68
67
}
69
68
70
69
// Filter label to avoid overlap
71
70
let previousTextLimit = 0 ;
72
71
const direction = reverse ? - 1 : 1 ;
73
- return withDimension . map ( ( item , labelIndex ) => {
74
- const { width, offset, labelOffset, height } = item ;
75
-
76
- const distance = getMinXTranslation ( width , height , style ?. angle ) ;
77
- const textPosition = offset + labelOffset ;
78
-
79
- const currentTextLimit = textPosition - ( direction * distance ) / 2 ;
80
- if (
81
- labelIndex > 0 &&
82
- direction * currentTextLimit < direction * ( previousTextLimit + tickLabelMinGap )
83
- ) {
84
- // Except for the first label, we skip all label that overlap with the last accepted.
85
- // Notice that the early return prevents `previousTextLimit` from being updated.
86
- return { ...item , skipLabel : true } ;
87
- }
88
- previousTextLimit = textPosition + ( direction * distance ) / 2 ;
89
- return item ;
90
- } ) ;
72
+
73
+ return new Set (
74
+ xTicks . filter ( ( item , labelIndex ) => {
75
+ const { offset, labelOffset } = item ;
76
+ const textPosition = offset + labelOffset ;
77
+
78
+ if (
79
+ labelIndex > 0 &&
80
+ direction * textPosition < direction * ( previousTextLimit + tickLabelMinGap )
81
+ ) {
82
+ return false ;
83
+ }
84
+
85
+ if ( ! isPointInside ( textPosition ) ) {
86
+ return false ;
87
+ }
88
+
89
+ /* Measuring text width is expensive, so we need to delay it as much as possible to improve performance. */
90
+ const { width, height } = getTickLabelSize ( item ) ;
91
+
92
+ const distance = getMinXTranslation ( width , height , style ?. angle ) ;
93
+
94
+ const currentTextLimit = textPosition - ( direction * distance ) / 2 ;
95
+ if (
96
+ labelIndex > 0 &&
97
+ direction * currentTextLimit < direction * ( previousTextLimit + tickLabelMinGap )
98
+ ) {
99
+ // Except for the first label, we skip all label that overlap with the last accepted.
100
+ // Notice that the early return prevents `previousTextLimit` from being updated.
101
+ return false ;
102
+ }
103
+
104
+ previousTextLimit = textPosition + ( direction * distance ) / 2 ;
105
+ return true ;
106
+ } ) ,
107
+ ) ;
91
108
}
92
109
93
110
const XAxisRoot = styled ( AxisRoot , {
@@ -184,12 +201,13 @@ function ChartsXAxis(inProps: ChartsXAxisProps) {
184
201
tickLabelPlacement,
185
202
} ) ;
186
203
187
- const xTicksWithDimension = addLabelDimension ( xTicks , {
204
+ const visibleLabels = getVisibleLabels ( xTicks , {
188
205
tickLabelStyle : axisTickLabelProps . style ,
189
206
tickLabelInterval,
190
207
tickLabelMinGap,
191
208
reverse,
192
209
isMounted,
210
+ isPointInside : ( x : number ) => instance . isPointInside ( { x, y : - 1 } , { direction : 'x' } ) ,
193
211
} ) ;
194
212
195
213
const labelRefPoint = {
@@ -229,42 +247,39 @@ function ChartsXAxis(inProps: ChartsXAxisProps) {
229
247
< Line x1 = { left } x2 = { left + width } className = { classes . line } { ...slotProps ?. axisLine } />
230
248
) }
231
249
232
- { xTicksWithDimension . map (
233
- ( { formattedValue, offset : tickOffset , labelOffset, skipLabel } , index ) => {
234
- const xTickLabel = labelOffset ?? 0 ;
235
- const yTickLabel = positionSign * ( tickSize + 3 ) ;
236
-
237
- const showTick = instance . isPointInside ( { x : tickOffset , y : - 1 } , { direction : 'x' } ) ;
238
- const showTickLabel = instance . isPointInside (
239
- { x : tickOffset + xTickLabel , y : - 1 } ,
240
- { direction : 'x' } ,
241
- ) ;
242
- return (
243
- < g
244
- key = { index }
245
- transform = { `translate(${ tickOffset } , 0)` }
246
- className = { classes . tickContainer }
247
- >
248
- { ! disableTicks && showTick && (
249
- < Tick
250
- y2 = { positionSign * tickSize }
251
- className = { classes . tick }
252
- { ...slotProps ?. axisTick }
253
- />
254
- ) }
255
-
256
- { formattedValue !== undefined && ! skipLabel && showTickLabel && (
257
- < TickLabel
258
- x = { xTickLabel }
259
- y = { yTickLabel }
260
- { ...axisTickLabelProps }
261
- text = { formattedValue . toString ( ) }
262
- />
263
- ) }
264
- </ g >
265
- ) ;
266
- } ,
267
- ) }
250
+ { xTicks . map ( ( item , index ) => {
251
+ const { formattedValue, offset : tickOffset , labelOffset } = item ;
252
+ const xTickLabel = labelOffset ?? 0 ;
253
+ const yTickLabel = positionSign * ( tickSize + 3 ) ;
254
+
255
+ const showTick = instance . isPointInside ( { x : tickOffset , y : - 1 } , { direction : 'x' } ) ;
256
+ const showTickLabel = visibleLabels . has ( item ) ;
257
+
258
+ return (
259
+ < g
260
+ key = { index }
261
+ transform = { `translate(${ tickOffset } , 0)` }
262
+ className = { classes . tickContainer }
263
+ >
264
+ { ! disableTicks && showTick && (
265
+ < Tick
266
+ y2 = { positionSign * tickSize }
267
+ className = { classes . tick }
268
+ { ...slotProps ?. axisTick }
269
+ />
270
+ ) }
271
+
272
+ { formattedValue !== undefined && showTickLabel && (
273
+ < TickLabel
274
+ x = { xTickLabel }
275
+ y = { yTickLabel }
276
+ { ...axisTickLabelProps }
277
+ text = { formattedValue . toString ( ) }
278
+ />
279
+ ) }
280
+ </ g >
281
+ ) ;
282
+ } ) }
268
283
269
284
{ label && (
270
285
< g className = { classes . label } >
0 commit comments