1
1
import * as React from 'react' ;
2
2
import classNames from 'classnames' ;
3
3
import { useTransitionDuration , defaultProps } from './common' ;
4
- import type { ProgressProps , GapPositionType } from './interface' ;
4
+ import type { ProgressProps } from './interface' ;
5
5
import useId from './hooks/useId' ;
6
6
7
7
function stripPercentToNumber ( percent : string ) {
@@ -13,56 +13,52 @@ function toArray<T>(value: T | T[]): T[] {
13
13
return Array . isArray ( mergedValue ) ? mergedValue : [ mergedValue ] ;
14
14
}
15
15
16
- function getPathStyles (
16
+ const VIEW_BOX_SIZE = 100 ;
17
+
18
+ const getCircleStyle = (
19
+ radius : number ,
17
20
offset : number ,
18
21
percent : number ,
19
22
strokeColor : string | Record < string , string > ,
20
- strokeWidth : number ,
21
23
gapDegree = 0 ,
22
- gapPosition : GapPositionType ,
23
- ) {
24
- const radius = 50 - strokeWidth / 2 ;
25
- let beginPositionX = 0 ;
26
- let beginPositionY = - radius ;
27
- let endPositionX = 0 ;
28
- let endPositionY = - 2 * radius ;
29
- switch ( gapPosition ) {
30
- case 'left' :
31
- beginPositionX = - radius ;
32
- beginPositionY = 0 ;
33
- endPositionX = 2 * radius ;
34
- endPositionY = 0 ;
35
- break ;
36
- case 'right' :
37
- beginPositionX = radius ;
38
- beginPositionY = 0 ;
39
- endPositionX = - 2 * radius ;
40
- endPositionY = 0 ;
41
- break ;
42
- case 'bottom' :
43
- beginPositionY = radius ;
44
- endPositionY = 2 * radius ;
45
- break ;
46
- default :
24
+ gapPosition : ProgressProps [ 'gapPosition' ] ,
25
+ strokeLinecap : ProgressProps [ 'strokeLinecap' ] ,
26
+ strokeWidth ,
27
+ ) => {
28
+ const rotateDeg = gapDegree > 0 ? 90 + gapDegree / 2 : - 90 ;
29
+ const perimeter = Math . PI * 2 * radius ;
30
+ const perimeterWithoutGap = perimeter * ( ( 360 - gapDegree ) / 360 ) ;
31
+ const offsetDeg = ( offset / 100 ) * 360 * ( ( 360 - gapDegree ) / 360 ) ;
32
+
33
+ const positionDeg = {
34
+ bottom : 0 ,
35
+ top : 180 ,
36
+ left : 90 ,
37
+ right : - 90 ,
38
+ } [ gapPosition ] ;
39
+
40
+ let strokeDashoffset = ( ( 100 - percent ) / 100 ) * perimeterWithoutGap ;
41
+ // Fix percent accuracy when strokeLinecap is round
42
+ // https://github.com/ant-design/ant-design/issues/35009
43
+ if ( strokeLinecap === 'round' && percent !== 100 ) {
44
+ strokeDashoffset += strokeWidth / 2 ;
45
+ // when percent is small enough (<= 1%), keep smallest value to avoid it's disapperance
46
+ if ( strokeDashoffset >= perimeterWithoutGap ) {
47
+ strokeDashoffset = perimeterWithoutGap - 0.01 ;
48
+ }
47
49
}
48
- const pathString = `M 50,50 m ${ beginPositionX } ,${ beginPositionY }
49
- a ${ radius } ,${ radius } 0 1 1 ${ endPositionX } ,${ - endPositionY }
50
- a ${ radius } ,${ radius } 0 1 1 ${ - endPositionX } ,${ endPositionY } ` ;
51
- const len = Math . PI * 2 * radius ;
52
50
53
- const pathStyle = {
51
+ return {
54
52
stroke : typeof strokeColor === 'string' ? strokeColor : undefined ,
55
- strokeDasharray : `${ ( percent / 100 ) * ( len - gapDegree ) } px ${ len } px` ,
56
- strokeDashoffset : `-${ gapDegree / 2 + ( offset / 100 ) * ( len - gapDegree ) } px` ,
53
+ strokeDasharray : `${ perimeterWithoutGap } px ${ perimeter } ` ,
54
+ strokeDashoffset,
55
+ transform : `rotate(${ rotateDeg + offsetDeg + positionDeg } deg)` ,
56
+ transformOrigin : '50% 50%' ,
57
57
transition :
58
- 'stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s, stroke-width .06s ease .3s, opacity .3s ease 0s' , // eslint-disable-line
58
+ 'stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s, stroke-width .06s ease .3s, opacity .3s ease 0s' ,
59
+ fillOpacity : 0 ,
59
60
} ;
60
-
61
- return {
62
- pathString,
63
- pathStyle,
64
- } ;
65
- }
61
+ } ;
66
62
67
63
const Circle : React . FC < ProgressProps > = ( {
68
64
id,
@@ -80,16 +76,18 @@ const Circle: React.FC<ProgressProps> = ({
80
76
...restProps
81
77
} ) => {
82
78
const mergedId = useId ( id ) ;
83
-
84
79
const gradientId = `${ mergedId } -gradient` ;
80
+ const radius = VIEW_BOX_SIZE / 2 - strokeWidth / 2 ;
85
81
86
- const { pathString, pathStyle } = getPathStyles (
82
+ const circleStyle = getCircleStyle (
83
+ radius ,
87
84
0 ,
88
85
100 ,
89
86
trailColor ,
90
- strokeWidth ,
91
87
gapDegree ,
92
88
gapPosition ,
89
+ strokeLinecap ,
90
+ strokeWidth ,
93
91
) ;
94
92
const percentList = toArray ( percent ) ;
95
93
const strokeColorList = toArray ( strokeColor ) ;
@@ -99,32 +97,44 @@ const Circle: React.FC<ProgressProps> = ({
99
97
100
98
const getStokeList = ( ) => {
101
99
let stackPtg = 0 ;
102
- return percentList . map ( ( ptg , index ) => {
103
- const color = strokeColorList [ index ] || strokeColorList [ strokeColorList . length - 1 ] ;
104
- const stroke = color && typeof color === 'object' ? `url(#${ gradientId } )` : '' ;
105
- const pathStyles = getPathStyles ( stackPtg , ptg , color , strokeWidth , gapDegree , gapPosition ) ;
106
- stackPtg += ptg ;
107
- return (
108
- < path
109
- key = { index }
110
- className = { `${ prefixCls } -circle-path` }
111
- d = { pathStyles . pathString }
112
- stroke = { stroke }
113
- strokeLinecap = { strokeLinecap }
114
- strokeWidth = { strokeWidth }
115
- opacity = { ptg === 0 ? 0 : 1 }
116
- fillOpacity = "0"
117
- style = { pathStyles . pathStyle }
118
- ref = { paths [ index ] }
119
- />
120
- ) ;
121
- } ) ;
100
+ return percentList
101
+ . map ( ( ptg , index ) => {
102
+ const color = strokeColorList [ index ] || strokeColorList [ strokeColorList . length - 1 ] ;
103
+ const stroke = color && typeof color === 'object' ? `url(#${ gradientId } )` : undefined ;
104
+ const circleStyleForStack = getCircleStyle (
105
+ radius ,
106
+ stackPtg ,
107
+ ptg ,
108
+ color ,
109
+ gapDegree ,
110
+ gapPosition ,
111
+ strokeLinecap ,
112
+ strokeWidth ,
113
+ ) ;
114
+ stackPtg += ptg ;
115
+ return (
116
+ < circle
117
+ key = { index }
118
+ className = { `${ prefixCls } -circle-path` }
119
+ r = { radius }
120
+ cx = { VIEW_BOX_SIZE / 2 }
121
+ cy = { VIEW_BOX_SIZE / 2 }
122
+ stroke = { stroke }
123
+ strokeLinecap = { strokeLinecap }
124
+ strokeWidth = { strokeWidth }
125
+ opacity = { ptg === 0 ? 0 : 1 }
126
+ style = { circleStyleForStack }
127
+ ref = { paths [ index ] }
128
+ />
129
+ ) ;
130
+ } )
131
+ . reverse ( ) ;
122
132
} ;
123
133
124
134
return (
125
135
< svg
126
136
className = { classNames ( `${ prefixCls } -circle` , className ) }
127
- viewBox = " 0 0 100 100"
137
+ viewBox = { ` 0 0 ${ VIEW_BOX_SIZE } ${ VIEW_BOX_SIZE } ` }
128
138
style = { style }
129
139
id = { id }
130
140
{ ...restProps }
@@ -140,16 +150,17 @@ const Circle: React.FC<ProgressProps> = ({
140
150
</ linearGradient >
141
151
</ defs >
142
152
) }
143
- < path
153
+ < circle
144
154
className = { `${ prefixCls } -circle-trail` }
145
- d = { pathString }
155
+ r = { radius }
156
+ cx = { VIEW_BOX_SIZE / 2 }
157
+ cy = { VIEW_BOX_SIZE / 2 }
146
158
stroke = { trailColor }
147
159
strokeLinecap = { strokeLinecap }
148
160
strokeWidth = { trailWidth || strokeWidth }
149
- fillOpacity = "0"
150
- style = { pathStyle }
161
+ style = { circleStyle }
151
162
/>
152
- { getStokeList ( ) . reverse ( ) }
163
+ { getStokeList ( ) }
153
164
</ svg >
154
165
) ;
155
166
} ;
0 commit comments