title: React Native Animated Rotating Circles with scale dependency
body: <p>I have an animated component where you can select one of seventeen circles. It looks like this so far: </p>
  
  <p><a href="https://i.stack.imgur.com/lQsSP.gif" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/lQsSP.gif" alt="App"></a></p>
  
  <p><strong>I would like to add an animation that scales the circle as it gets closer to the center. How do I do that?</strong></p>
  
  <p>Until now I tried to calculate the x value of the circle as <code>Math.sin(index*deltaTheta*Math.PI/180 + Math.PI)*Radius</code> and use this value in a functions which maps to a scaling factor (e.g. a gaussian). This fails because the x value does not change, because I am using CSS transform rotate.</p>
  
  <p>Then I tried to use a different <code>interpolating</code> range for every single circle, but did not achieved a satisfying result.</p>
  
  <hr>
  
  <p>My code:</p>
  
  <pre><code>import React, { Component } from 'react'
  import { Text, View, PanResponder, Animated, Dimensions } from 'react-native'
  import styled from 'styled-components'
  import Circle from './Circle'
  
  const SCREEN_WIDTH = Dimensions.get('window').width
  
  const Container = styled(Animated.View)`
    margin: auto;
    width: 200px;
    height: 200px;
    position: relative;
    top: 100px;
  `
  
  const gaussFunc = (x, sigma, mu) =&gt; {
    return 1/sigma/Math.sqrt(2.0*Math.PI)*Math.exp(-1.0/2.0*Math.pow((x-mu)/sigma,2))
  }
  const myGaussFunc = (x) =&gt; gaussFunc(x, 1/2/Math.sqrt(2*Math.PI), 0)
  
  const circles = [{
    color: 'red'
  }, {
    color: 'blue'
  }, {
    color: 'green'
  }, {
    color: 'yellow'
  }, {
    color: 'purple'
  }, {
    color: 'black'
  }, {
    color: 'gray'
  }, {
    color: 'pink'
  }, {
    color: 'lime'
  }, {
    color: 'darkgreen'
  }, {
    color: 'crimson'
  }, {
    color: 'orange'
  }, {
    color: 'cyan'
  }, {
    color: 'navy'
  }, {
    color: 'indigo'
  }, {
    color: 'brown'
  }, {
    color: 'peru'
  }
                  ]
  
  function withFunction(callback) {
    let inputRange = [], outputRange = [], steps = 50;
    /// input range 0-1
    for (let i=0; i&lt;=steps; ++i) {
      let key = i/steps;
      inputRange.push(key);
      outputRange.push(callback(key));
    }
    return { inputRange, outputRange };
  }
  
  export default class SDGCircle extends Component {
    state = {
      deltaTheta: 360/circles.length,
      Radius: 0, // radius of center circle (contaienr)
      radius: 25, // radius of orbiting circles
      container: { height: 0, width: 0 },
      deltaAnim: new Animated.Value(0),
    }
  
    offset = () =&gt; parseInt(this.state.container.width/2)-this.state.radius
  
    _panResponder = PanResponder.create({
      nMoveShouldSetPanResponderCapture: (evt, gestureState) =&gt; true,
      onMoveShouldSetPanResponder: (event, gestureState) =&gt; true,
      onPanResponderGrant: () =&gt; {
        const { deltaAnim } = this.state
        deltaAnim.setOffset(deltaAnim._value)
        deltaAnim.setValue(0)
      },
      onPanResponderMove: (event, gestureState) =&gt; {
        const { deltaAnim, scaleAnim, deltaTheta, Radius } = this.state
        deltaAnim.setValue(gestureState.dx)
        console.log(deltaAnim)
      },
      onPanResponderRelease: (event, gestureState) =&gt; {
  
        const {dx, vx} = gestureState
        const {deltaAnim} = this.state
  
        deltaAnim.flattenOffset()
        Animated.spring(deltaAnim, {
          toValue: this.getIthCircleValue(dx, deltaAnim),
          friction: 5,
          tension: 10,
        }).start(() =&gt; this.simplifyOffset(deltaAnim._value));
      }
    })
  
    getIthCircleValue = (dx, deltaAnim) =&gt; {
      const selectedCircle = Math.round(deltaAnim._value/(600/circles.length))
      return (selectedCircle)*600/circles.length
    }
    getAmountForNextSlice = (dx, offset) =&gt; {
      // This just rounds to the nearest 200 to snap the circle to the correct thirds
      const snappedOffset = this.snapOffset(offset);
      // Depending on the direction, we either add 200 or subtract 200 to calculate new offset position. (200 are equal to 120deg!)
      // const newOffset = dx &gt; 0 ? snappedOffset + 200 : snappedOffset - 200; // fixed for 3 circles
      const newOffset = dx &gt; 0 ? snappedOffset + 600/circles.length : snappedOffset - 600/circles.length;
      return newOffset;
    }
    snapOffset = (offset) =&gt; { return Math.round(offset / (600/circles.length)) * 600/circles.length; }
    simplifyOffset = (val) =&gt; {
      const { deltaAnim } = this.state
      if(deltaAnim._offset &gt; 600) deltaAnim.setOffset(deltaAnim._offset - 600)
      if(deltaAnim._offset &lt; -600) deltaAnim.setOffset(deltaAnim._offset + 600)
    }
  
    handleLayout = ({ nativeEvent }) =&gt; {
      this.setState({
        Radius: nativeEvent.layout.width,
        container: {
          height: nativeEvent.layout.height,
          width: nativeEvent.layout.width
        }
      })
    }
  
    render() {
      const {deltaAnim, radius} = this.state
  
      return (
        &lt;Container
          onLayout={this.handleLayout}
          {...this._panResponder.panHandlers}
          style={{
            transform: [{
              rotate: deltaAnim.interpolate({
                inputRange: [-200, 0, 200],
                outputRange: ['-120deg', '0deg', '120deg']
              })
            }]
          }}
        &gt;
          {circles.map((circle, index) =&gt; {
            const {deltaTheta, Radius} = this.state
  
            return (
              &lt;Circle
                key={index}
                color={circle.color}
                radius={radius}
                style={{
                  left: Math.sin(index*deltaTheta*Math.PI/180 + Math.PI)*Radius+this.offset(),
                  top: Math.cos(index*deltaTheta*Math.PI/180 + Math.PI)*Radius+this.offset(),
                }}
              &gt;
                &lt;Text style={{color: 'white'}}&gt;{index}&lt;/Text&gt;
              &lt;/Circle&gt;
            )
          })}
        &lt;/Container&gt;
      )
    }
  }
  </code></pre>