fileOverview import React , { PureComponent , ReactElement , ReactNode } from 'react' ; import classNames from 'classnames' ; import Animate from 'react-smooth' ; import _ from 'lodash' ; import { Sector , Props as SectorProps } from '../shape/Sector' ; import { Layer } from '../container/Layer' ; import { findAllByType } from '../util/ReactUtils' ; import { Global } from '../util/Global' ; import { ImplicitLabelListType , LabelList } from '../component/LabelList' ; import { Cell } from '../component/Cell' ; import { mathSign , interpolateNumber } from '../util/DataUtils' ; import { getCateCoordinateOfBar , findPositionOfBar , getValueByDataKey , truncateByDomain , getBaseValueOfBar , getTooltipItem , } from '../util/ChartUtils' ; import { LegendType , TooltipType , AnimationTiming , filterProps , TickItem , adaptEventsOfChild , PresentationAttributesAdaptChildEvent , } from '../util/types' ; import { polarToCartesian } from '../util/PolarUtils' ; type RadialBarDataItem = SectorProps & { value ? : any ; payload ? : any ; background ? : SectorProps ; } ; type RadialBarShape = ReactElement | ( ( props : Props ) => ReactNode ) ; type RadialBarBackground = ReactElement | ( ( props : Props ) => ReactNode ) | SectorProps | boolean ; interface RadialBarProps { animationId ? : string | number ; className ? : string ; angleAxisId ? : string | number ; radiusAxisId ? : string | number ; startAngle ? : number ; endAngle ? : number ; shape ? : RadialBarShape ; activeShape ? : RadialBarShape ; activeIndex ? : number ; dataKey : string | number | ( ( obj : any ) => any ) ; cornerRadius ? : string | number ; forceCornerRadius ? : boolean ; cornerIsExternal ? : boolean ; minPointSize ? : number ; maxBarSize ? : number ; data ? : RadialBarDataItem [ ] ; legendType ? : LegendType ; tooltipType ? : TooltipType ; hide ? : boolean ; label ? : ImplicitLabelListType < any > ; stackId ? : string | number ; background ? : RadialBarBackground ; onAnimationStart ? : ( ) => void ; onAnimationEnd ? : ( ) => void ; isAnimationActive ? : boolean ; animationBegin ? : number ; animationDuration ? : number ; animationEasing ? : AnimationTiming ; } export type Props = PresentationAttributesAdaptChildEvent < any , SVGElement > & RadialBarProps ; interface State { readonly isAnimationFinished ? : boolean ; readonly prevData ? : RadialBarDataItem [ ] ; readonly curData ? : RadialBarDataItem [ ] ; readonly prevAnimationId ? : string | number ; } export class RadialBar extends PureComponent < Props , State > { static displayName = 'RadialBar' ; static defaultProps = { angleAxisId : 0 , radiusAxisId : 0 , minPointSize : 0 , hide : false , legendType : 'rect' , data : [ ] as RadialBarDataItem [ ] , isAnimationActive : ! Global . isSsr , animationBegin : 0 , animationDuration : 1500 , animationEasing : 'ease' , forceCornerRadius : false , cornerIsExternal : false , } ; static getComposedData = ( { item , props , radiusAxis , radiusAxisTicks , angleAxis , angleAxisTicks , displayedData , dataKey , stackedData , barPosition , bandSize , dataStartIndex , } ) => { const pos = findPositionOfBar ( barPosition , item ) ; if ( ! pos ) { return null ; } const { cx , cy } = angleAxis ; const { layout } = props ; const { children , minPointSize } = item . props ; const numericAxis = layout === 'radial' ? angleAxis : radiusAxis ; const stackedDomain = stackedData ? numericAxis . scale . domain ( ) : null ; const baseValue = getBaseValueOfBar ( { numericAxis } ) ; const cells = findAllByType ( children , Cell . displayName ) ; const sectors = displayedData . map ( ( entry , index ) => { let value , innerRadius , outerRadius , startAngle , endAngle , backgroundSector ; if ( stackedData ) { value = truncateByDomain ( stackedData [ dataStartIndex + index ] , stackedDomain ) ; } else { value = getValueByDataKey ( entry , dataKey ) ; if ( ! _ . isArray ( value ) ) { value = [ baseValue , value ] ; } } if ( layout === 'radial' ) { innerRadius = getCateCoordinateOfBar ( { axis : radiusAxis , ticks : radiusAxisTicks , bandSize , offset : pos . offset , entry , index , } ) ; endAngle = angleAxis . scale ( value [ 1 ] ) ; startAngle = angleAxis . scale ( value [ 0 ] ) ; outerRadius = innerRadius + pos . size ; const deltaAngle = endAngle - startAngle ; if ( Math . abs ( minPointSize ) > 0 && Math . abs ( deltaAngle ) < Math . abs ( minPointSize ) ) { const delta = mathSign ( deltaAngle || minPointSize ) * ( Math . abs ( minPointSize ) - Math . abs ( deltaAngle ) ) ; endAngle += delta ; } backgroundSector = { background : { cx , cy , innerRadius , outerRadius , startAngle : props . startAngle , endAngle : props . endAngle , } , } ; } else { innerRadius = radiusAxis . scale ( value [ 0 ] ) ; outerRadius = radiusAxis . scale ( value [ 1 ] ) ; startAngle = getCateCoordinateOfBar ( { axis : angleAxis , ticks : angleAxisTicks , bandSize , offset : pos . offset , entry , index , } ) ; endAngle = startAngle + pos . size ; const deltaRadius = outerRadius - innerRadius ; if ( Math . abs ( minPointSize ) > 0 && Math . abs ( deltaRadius ) < Math . abs ( minPointSize ) ) { const delta = mathSign ( deltaRadius || minPointSize ) * ( Math . abs ( minPointSize ) - Math . abs ( deltaRadius ) ) ; outerRadius += delta ; } } return { ... entry , ... backgroundSector , payload : entry , value : stackedData ? value : value [ 1 ] , cx , cy , innerRadius , outerRadius , startAngle , endAngle , ... ( cells && cells [ index ] && cells [ index ] . props ) , tooltipPayload : [ getTooltipItem ( item , entry ) ] , tooltipPosition : polarToCartesian ( cx , cy , ( innerRadius + outerRadius ) / 2 , ( startAngle + endAngle ) / 2 ) , } ; } ) ; return { data : sectors , layout } ; } ; state : State = { isAnimationFinished : false , } ; static getDerivedStateFromProps ( nextProps , prevState ) { if ( nextProps . animationId !== prevState . prevAnimationId ) { return { prevAnimationId : nextProps . animationId , curData : nextProps . data , prevData : prevState . curData , } ; } if ( nextProps . data !== prevState . curData ) { return { curData : nextProps . data , } ; } return null ; } getDeltaAngle ( ) { const { startAngle , endAngle } = this . props ; const sign = mathSign ( endAngle - startAngle ) ; const deltaAngle = Math . min ( Math . abs ( endAngle - startAngle ) , 360 ) ; return sign * deltaAngle ; } handleAnimationEnd = ( ) => { const { onAnimationEnd } = this . props ; this . setState ( { isAnimationFinished : true } ) ; if ( _ . isFunction ( onAnimationEnd ) ) { onAnimationEnd ( ) ; } } ; handleAnimationStart = ( ) => { const { onAnimationStart } = this . props ; this . setState ( { isAnimationFinished : false } ) ; if ( _ . isFunction ( onAnimationStart ) ) { onAnimationStart ( ) ; } } ; static renderSectorShape ( shape , props ) { let sectorShape ; if ( React . isValidElement ( shape ) ) { sectorShape = React . cloneElement ( shape , props ) ; } else if ( _ . isFunction ( shape ) ) { sectorShape = shape ( props ) ; } else { sectorShape = React . createElement ( Sector , props ) ; } return sectorShape ; } renderSectorsStatically ( sectors ) { const { shape , activeShape , activeIndex , cornerRadius , ... others } = this . props ; const baseProps = filterProps ( others ) ; return sectors . map ( ( entry , i ) => { const props = { ... baseProps , cornerRadius , ... entry , ... adaptEventsOfChild ( this . props , entry , i ) , key : `sector-${ i }` , className : 'recharts-radial-bar-sector' , forceCornerRadius : others . forceCornerRadius , cornerIsExternal : others . cornerIsExternal , } ; return RadialBar . renderSectorShape ( i === activeIndex ? activeShape : shape , props ) ; } ) ; } renderSectorsWithAnimation ( ) { const { data , isAnimationActive , animationBegin , animationDuration , animationEasing , animationId } = this . props ; const { prevData } = this . state ; return ( < Animate begin = { animationBegin } duration = { animationDuration } isActive = { isAnimationActive } easing = { animationEasing } from = { { t : 0 } } to = { { t : 1 } } key = { `radialBar-${ animationId }` } onAnimationStart = { this . handleAnimationStart } onAnimationEnd = { this . handleAnimationEnd } > { ( { t } ) => { const stepData = data . map ( ( entry , index ) => { const prev = prevData && prevData [ index ] ; if ( prev ) { const interpolatorStartAngle = interpolateNumber ( prev . startAngle , entry . startAngle ) ; const interpolatorEndAngle = interpolateNumber ( prev . endAngle , entry . endAngle ) ; return { ... entry , startAngle : interpolatorStartAngle ( t ) , endAngle : interpolatorEndAngle ( t ) , } ; } const { endAngle , startAngle } = entry ; const interpolator = interpolateNumber ( startAngle , endAngle ) ; return { ... entry , endAngle : interpolator ( t ) } ; } ) ; return < Layer > { this . renderSectorsStatically ( stepData ) } < /Layer>; } } < /Animate>); } renderSectors ( ) { const { data , isAnimationActive } = this . props ; const { prevData } = this . state ; if ( isAnimationActive && data && data . length && ( ! prevData || ! _ . isEqual ( prevData , data ) ) ) { return this . renderSectorsWithAnimation ( ) ; } return this . renderSectorsStatically ( data ) ; } renderBackground ( sectors ? ) { const { cornerRadius } = this . props ; const backgroundProps = filterProps ( this . props . background ) ; return sectors . map ( ( entry , i ) => { const { value , background , ... rest } = entry ; if ( ! background ) { return null ; } const props = { cornerRadius , ... rest , fill : '#eee' , ... background , ... backgroundProps , ... adaptEventsOfChild ( this . props , entry , i ) , index : i , key : `sector-${ i }` , className : 'recharts-radial-bar-background-sector' , } ; return RadialBar . renderSectorShape ( background , props ) ; } ) ; } render ( ) { const { hide , data , className , background , isAnimationActive } = this . props ; if ( hide || ! data || ! data . length ) { return null ; } const { isAnimationFinished } = this . state ; const layerClass = classNames ( 'recharts-area' , className ) ; return ( < Layer className = { layerClass } > { background && < Layer className = "recharts-radial-bar-background" > { this . renderBackground ( data ) } < /Layer>} < Layer className = "recharts-radial-bar-sectors" > { this . renderSectors ( ) } < /Layer> { ( ! isAnimationActive || isAnimationFinished ) && LabelList . renderCallByParent ( { ... this . props , clockWise : this . getDeltaAngle ( ) < 0 , } , data ) } < /Layer>); } }