@@ -27,6 +27,7 @@ export default (View) => {
27
27
IProps extends AxisProps < TRecord > = AxisProps < TRecord >
28
28
> extends Component < IProps & ChartChildProps , { } > {
29
29
axisStyle : Style = { } ;
30
+ ticks : Tick [ ] ;
30
31
31
32
constructor ( props : IProps & ChartChildProps ) {
32
33
super ( props ) ;
@@ -223,22 +224,66 @@ export default (View) => {
223
224
} ) ;
224
225
}
225
226
226
- measureLayout ( ) : PositionLayout | PositionLayout [ ] {
227
+ calculateLabelOverflow ( lastTick , label ) : number {
228
+ if ( ! lastTick || ! label ) {
229
+ return 0 ;
230
+ }
231
+ const { props, context } = this ;
232
+ const { measureText } = context ;
233
+ const { coord } = props ;
234
+ const { labelStyle = { } , text } = lastTick ;
235
+
236
+ const tickBBox = measureText ( labelStyle . text || text , { ...label , ...labelStyle } ) ;
237
+
238
+ const lastTickPoint = coord . convertPoint ( {
239
+ x : lastTick . value ,
240
+ y : 0 ,
241
+ } ) ;
242
+
243
+ let labelRightEdge = lastTickPoint . x ;
244
+ const align = label . align || 'center' ;
245
+
246
+ if ( align === 'center' ) {
247
+ labelRightEdge += tickBBox . width / 2 ;
248
+ } else if ( align === 'left' || align === 'start' ) {
249
+ labelRightEdge += tickBBox . width ;
250
+ }
251
+
252
+ return labelRightEdge > coord . right ? labelRightEdge - coord . right : 0 ;
253
+ }
254
+
255
+ _getXTicksDistance ( ticks ) {
256
+ const { props } = this ;
257
+ const { coord } = props ;
258
+
259
+ const firstPoint = coord . convertPoint ( {
260
+ x : ticks [ 0 ] . value ,
261
+ y : 0 ,
262
+ } ) ;
263
+
264
+ const secondPoint = coord . convertPoint ( {
265
+ x : ticks [ 1 ] . value ,
266
+ y : 0 ,
267
+ } ) ;
268
+ return Math . abs ( secondPoint . x - firstPoint . x ) ;
269
+ }
270
+
271
+ measureLayout ( ticks ) : PositionLayout | PositionLayout [ ] {
227
272
const { props, context } = this ;
228
- const { visible, coord, style } = props ;
273
+ const { visible, coord, style, labelAutoRotate = false , labelAutoHide = false } = props ;
229
274
if ( visible === false ) {
230
275
return null ;
231
276
}
232
277
const { width : customWidth , height : customHeight } = style || { } ;
233
278
234
- const ticks = this . getTicks ( ) ;
235
279
const bbox = this . getMaxBBox ( ticks , this . axisStyle ) ;
236
280
237
281
const { isPolar } = coord ;
238
282
const dimType = this . _getDimType ( ) ;
239
- // const { width, height } = bbox;
283
+
240
284
const width = isNil ( customWidth ) ? bbox . width : context . px2hd ( customWidth ) ;
241
285
const height = isNil ( customHeight ) ? bbox . height : context . px2hd ( customHeight ) ;
286
+
242
287
if ( isPolar ) {
243
288
// 机坐标系的 y 不占位置
244
289
if ( dimType === 'y' ) {
@@ -258,31 +303,164 @@ export default (View) => {
258
303
259
304
// 直角坐标系下
260
305
const position = this . _getPosition ( ) ;
306
+
307
+ if ( ( labelAutoRotate || labelAutoHide ) && dimType === 'x' ) {
308
+ const { label } = this . axisStyle ;
309
+ const lastTick = ticks [ ticks . length - 1 ] ;
310
+
311
+ const overflowWidth = this . calculateLabelOverflow ( lastTick , label ) ;
312
+
313
+ return [
314
+ {
315
+ position,
316
+ width,
317
+ height,
318
+ } ,
319
+ {
320
+ position : 'right' ,
321
+ width : overflowWidth ,
322
+ height : 0 ,
323
+ } ,
324
+ ] ;
325
+ }
326
+
261
327
return {
262
328
position,
263
329
width,
264
330
height,
265
331
} ;
266
332
}
267
333
334
+ findSuitableRotation ( ticks ) {
335
+ const { context } = this ;
336
+ const { measureText } = context ;
337
+
338
+ const averageSpace = this . _getXTicksDistance ( [ ticks [ 0 ] , ticks [ 1 ] ] ) ;
339
+ const { label } = this . axisStyle ;
340
+ const { labelStyle = { } , text } = ticks [ 0 ] ;
341
+ const bbox = measureText ( labelStyle . text || text , { ...label , ...labelStyle } ) ;
342
+ const labelHeight = bbox . height ;
343
+
344
+ // 安全距离
345
+ const safetyDistance = 2 ;
346
+
347
+ const availableSpace = labelHeight + safetyDistance ;
348
+
349
+ const cosValue = availableSpace / averageSpace ;
350
+
351
+ const clampedCosValue = Math . max ( - 1 , Math . min ( 1 , cosValue ) ) ;
352
+
353
+ const theoreticalAngle = ( Math . acos ( clampedCosValue ) * 180 ) / Math . PI ;
354
+
355
+ const ceiledAngle = Math . ceil ( theoreticalAngle ) ;
356
+
357
+ if ( ceiledAngle > 0 && ceiledAngle <= 90 ) {
358
+ this . axisStyle . label . align = 'start' ;
359
+ this . axisStyle . label . transform = `rotate(${ ceiledAngle } deg)` ;
360
+ this . axisStyle . label . transformOrigin = `0 50%` ;
361
+ }
362
+ }
363
+
364
+ hasOverlapAtSeq ( ticks , step ) {
365
+ const safetyMargin = 2 ;
366
+ const XDistance = this . _getXTicksDistance ( [ ticks [ 0 ] , ticks [ step ] ] ) ;
367
+
368
+ let prevIdx = 0 ;
369
+ for ( let currIdx = step ; currIdx <= ticks . length - 1 ; currIdx += step ) {
370
+ const minDistance =
371
+ ( ticks [ prevIdx ] . labelWidth + ticks [ currIdx ] . labelWidth ) / 2 + safetyMargin ;
372
+
373
+ if ( XDistance < minDistance ) {
374
+ return true ;
375
+ }
376
+
377
+ prevIdx = currIdx ;
378
+ }
379
+
380
+ return false ;
381
+ }
382
+
383
+ findLabelsToHide ( ticks ) {
384
+ const { props, context } = this ;
385
+ const { coord } = props ;
386
+ const { measureText } = context ;
387
+
388
+ const tickCount = ticks . length ;
389
+
390
+ const { label } = this . axisStyle ;
391
+
392
+ let maxLabelWidth = 0 ;
393
+ for ( let i = 0 ; i < tickCount ; i ++ ) {
394
+ const tick = ticks [ i ] ;
395
+ const { labelStyle = { } , text } = tick ;
396
+ const bbox = measureText ( labelStyle . text || text , { ...label , ...labelStyle } ) ;
397
+ tick . labelWidth = bbox . width ;
398
+ maxLabelWidth = Math . max ( maxLabelWidth , bbox . width ) ;
399
+ }
400
+
401
+ const initialSeq = Math . floor ( maxLabelWidth / ( coord . width / ( tickCount - 1 ) ) ) ;
402
+
403
+ const range = tickCount - 1 ;
404
+ const maxSeq = Math . floor ( range / 2 ) ;
405
+
406
+ let finalSeq = initialSeq ;
407
+
408
+ while ( finalSeq <= maxSeq && range % finalSeq !== 0 ) {
409
+ finalSeq ++ ;
410
+ }
411
+
412
+ while ( finalSeq <= maxSeq && this . hasOverlapAtSeq ( ticks , finalSeq ) ) {
413
+ finalSeq ++ ;
414
+ while ( finalSeq <= maxSeq && range % finalSeq !== 0 ) {
415
+ finalSeq ++ ;
416
+ }
417
+ }
418
+
419
+ if ( finalSeq > maxSeq || finalSeq === 1 ) {
420
+ return ;
421
+ }
422
+
423
+ ticks . forEach ( ( tick ) => {
424
+ tick . visible = false ;
425
+ } ) ;
426
+
427
+ for ( let i = 0 ; i <= range ; i += finalSeq ) {
428
+ ticks [ i ] . visible = true ;
429
+ }
430
+ }
431
+
268
432
// 主要是计算coord的布局
269
433
updateCoord ( ) {
270
434
const { props } = this ;
271
- const { chart } = props ;
272
- const layout = this . measureLayout ( ) ;
435
+ const { chart, labelAutoRotate = false , labelAutoHide = false } = props ;
436
+ const dimType = this . _getDimType ( ) ;
437
+ const ticks = this . getTicks ( ) ;
438
+
439
+ if ( labelAutoRotate && dimType === 'x' ) {
440
+ this . findSuitableRotation ( ticks ) ;
441
+ this . ticks = ticks ;
442
+ }
443
+ if ( labelAutoHide && dimType === 'x' ) {
444
+ this . findLabelsToHide ( ticks ) ;
445
+ this . ticks = ticks ;
446
+ }
447
+
448
+ const layout = this . measureLayout ( ticks ) ;
449
+
273
450
chart . updateCoordFor ( this , layout ) ;
274
451
}
275
452
276
453
render ( ) {
277
454
const { props, axisStyle } = this ;
278
455
const { visible, coord } = props ;
456
+ const dimType = this . _getDimType ( ) ;
457
+
279
458
if ( visible === false ) {
280
459
return null ;
281
460
}
282
461
283
- const ticks = this . getTicks ( ) ;
462
+ const ticks = this . ticks ? this . ticks : this . getTicks ( ) ;
284
463
const position = this . _getPosition ( ) ;
285
- const dimType = this . _getDimType ( ) ;
286
464
287
465
return (
288
466
< View
0 commit comments