import React from 'react' ; import _ from 'lodash' ; import Svg , { G , LinearGradient , Path , Defs , Stop } from 'react-native-svg' ; import { interpolateHcl as interpolateGradient } from 'd3-interpolate' ; import { Coordinate , ColorItem } from './interface' ; const fullDeg = Math . PI * 2 ; interface Props { outerRadius : number ; innerRadius : number ; segmentAngle : number ; offsetAngle : number ; colors : ColorItem [ ] ; } export default class RingBackground extends React . Component < Props > { static defaultProps = { outerRadius : 120 , innerRadius : 80 , offsetAngle : 0 , colors : [ ] , segmentAngle : fullDeg / 360 , } ; constructor ( props ) { super ( props ) ; const { outerRadius , innerRadius , colors , segmentAngle , offsetAngle } = this . props ; this . handleSize ( outerRadius , innerRadius , offsetAngle ) ; this . handleColors ( colors , segmentAngle ) ; } componentWillReceiveProps ( nextProps ) { if ( ! _ . isEqual ( nextProps , this . props ) ) { this . setNativeProps ( nextProps ) ; } } setNativeProps ( props ) { const { outerRadius : oldOuterRadius , innerRadius : oldInnerRadius , colors : oldColors , segmentAngle : oldSegmentAngle , offsetAngle : oldOffsetAngle , } = this . props ; const { outerRadius = oldOuterRadius , innerRadius = oldInnerRadius , colors = oldColors , segmentAngle = oldSegmentAngle , offsetAngle = oldOffsetAngle , } = props ; this . handleSize ( outerRadius , innerRadius , offsetAngle ) ; this . handleColors ( colors , segmentAngle ) ; this . forceUpdate ( ) ; } getPoint ( angle , radius ) { const { offsetAngle , outerRadius } = this ; const deg = Math . round ( 10000 * ( angle + offsetAngle ) ) / 10000 ; return { x : radius * Math . cos ( deg ) + outerRadius , y : radius * Math . sin ( deg ) + outerRadius , } ; } getPath ( start , end ) { const { radius } = this ; const { x : x1 , y : y1 } = start ; const { x : x2 , y : y2 } = end ; return `M${ x1 } ${ y1 } A${ radius } ${ radius } 0 0 1 ${ x2 } ${ y2 }` ; } private offsetAngle = 0 ; private size = 0 ; private radius = 0 ; private innerRadius = 0 ; private outerRadius = 0 ; private colors : ColorItem [ ] = [ ] ; handleColors ( colors , segmentAngle ) { let prev = colors [ colors . length - 1 ] ; const list = [ ] ; colors . forEach ( ( item ) => { const { angle : start , color : startColor } = prev ; let { angle : end , color : endColor } = item ; if ( end <= start ) { end += fullDeg ; } const steps = Math . floor ( ( end - start ) / segmentAngle ) ; const interpolate = interpolateGradient ( startColor , endColor ) ; const startDeg = Math . ceil ( start / segmentAngle ) * segmentAngle ; for ( let i = 0 ; i < steps ; i ++ ) { const deg = startDeg + segmentAngle * i ; const percent = ( deg - start ) / ( end - start ) ; const stepColor = interpolate ( percent ) ; list . push ( { angle : deg > fullDeg ? deg % fullDeg : deg , color : stepColor , } ) ; } prev = item ; list . push ( { angle : item . angle , color : interpolate ( 1 ) , } ) ; } ) ; prev = list [ list . length - 1 ] ; this . colors = list . map ( current => { const { angle : startAngle , color : startColor } = prev ; const { angle : endAngle , color : endColor } = current ; const point1 = this . getPoint ( startAngle , this . radius ) ; const point2 = this . getPoint ( endAngle + 0.005 , this . radius ) ; prev = current ; return { path : this . getPath ( point1 , point2 ) , from : point1 , to : point2 , startColor , endColor , } ; } ) ; } handleSize ( outerRadius , innerRadius , offsetAngle ) { this . size = outerRadius * 2 ; this . radius = ( outerRadius + innerRadius ) / 2 ; this . outerRadius = outerRadius ; this . innerRadius = innerRadius ; this . offsetAngle = offsetAngle ; } render ( ) { const { size , outerRadius , innerRadius } = this ; return ( < Svg width = { size } height = { size } viewBox = { `0 0 ${ size } ${ size }` } > < Defs > { this . colors . map ( ( { from , to , startColor , endColor } , index ) => ( < LinearGradient key = { index } id = { `linear${ index }` } x1 = { from . x . toFixed ( 2 ) } y1 = { from . y . toFixed ( 2 ) } x2 = { to . x . toFixed ( 2 ) } y2 = { to . y . toFixed ( 2 ) } > < Stop offset = "0" stopColor = { startColor } / > < Stop offset = "1" stopColor = { endColor } / > < /LinearGradient>))} < /Defs> < G > { this . colors . map ( ( { path } , index ) => { return ( < Path key = { index } d = { path } fill = "transparent" stroke = { `url(#linear${ index })` } strokeWidth = { `${ outerRadius - innerRadius }` } / > ) ; } ) } < /G> < /Svg>); } }