11import * as React from 'react' ;
22import classNames from 'classnames' ;
33import { useTransitionDuration , defaultProps } from './common' ;
4- import type { ProgressProps , GapPositionType } from './interface' ;
4+ import type { ProgressProps } from './interface' ;
55import useId from './hooks/useId' ;
66
77function stripPercentToNumber ( percent : string ) {
@@ -13,56 +13,52 @@ function toArray<T>(value: T | T[]): T[] {
1313 return Array . isArray ( mergedValue ) ? mergedValue : [ mergedValue ] ;
1414}
1515
16- function getPathStyles (
16+ const VIEW_BOX_SIZE = 100 ;
17+
18+ const getCircleStyle = (
19+ radius : number ,
1720 offset : number ,
1821 percent : number ,
1922 strokeColor : string | Record < string , string > ,
20- strokeWidth : number ,
2123 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+ }
4749 }
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 ;
5250
53- const pathStyle = {
51+ return {
5452 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%' ,
5757 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 ,
5960 } ;
60-
61- return {
62- pathString,
63- pathStyle,
64- } ;
65- }
61+ } ;
6662
6763const Circle : React . FC < ProgressProps > = ( {
6864 id,
@@ -80,16 +76,18 @@ const Circle: React.FC<ProgressProps> = ({
8076 ...restProps
8177} ) => {
8278 const mergedId = useId ( id ) ;
83-
8479 const gradientId = `${ mergedId } -gradient` ;
80+ const radius = VIEW_BOX_SIZE / 2 - strokeWidth / 2 ;
8581
86- const { pathString, pathStyle } = getPathStyles (
82+ const circleStyle = getCircleStyle (
83+ radius ,
8784 0 ,
8885 100 ,
8986 trailColor ,
90- strokeWidth ,
9187 gapDegree ,
9288 gapPosition ,
89+ strokeLinecap ,
90+ strokeWidth ,
9391 ) ;
9492 const percentList = toArray ( percent ) ;
9593 const strokeColorList = toArray ( strokeColor ) ;
@@ -99,32 +97,44 @@ const Circle: React.FC<ProgressProps> = ({
9997
10098 const getStokeList = ( ) => {
10199 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 ( ) ;
122132 } ;
123133
124134 return (
125135 < svg
126136 className = { classNames ( `${ prefixCls } -circle` , className ) }
127- viewBox = " 0 0 100 100"
137+ viewBox = { ` 0 0 ${ VIEW_BOX_SIZE } ${ VIEW_BOX_SIZE } ` }
128138 style = { style }
129139 id = { id }
130140 { ...restProps }
@@ -140,16 +150,17 @@ const Circle: React.FC<ProgressProps> = ({
140150 </ linearGradient >
141151 </ defs >
142152 ) }
143- < path
153+ < circle
144154 className = { `${ prefixCls } -circle-trail` }
145- d = { pathString }
155+ r = { radius }
156+ cx = { VIEW_BOX_SIZE / 2 }
157+ cy = { VIEW_BOX_SIZE / 2 }
146158 stroke = { trailColor }
147159 strokeLinecap = { strokeLinecap }
148160 strokeWidth = { trailWidth || strokeWidth }
149- fillOpacity = "0"
150- style = { pathStyle }
161+ style = { circleStyle }
151162 />
152- { getStokeList ( ) . reverse ( ) }
163+ { getStokeList ( ) }
153164 </ svg >
154165 ) ;
155166} ;
0 commit comments