title: React Native d3 azimuthal equal-area rotate not smooth
body: <p>I am doing the d3 azimuthal equal-area projection in react-native, i used this <a href="https://medium.com/nightingale/creating-an-interactive-d3-choropleth-map-in-react-native-5e6f3d4208ca" rel="nofollow noreferrer">example</a> for this. its working fine but im updating rotate values using panGestureHandler this is also working but it's not smooth and it's take time to update map.</p>
  <p><a href="https://github.com/yathu/react-native-d3-globe" rel="nofollow noreferrer">this the repo of this.</a></p>
  <ol>
  <li><p>this is the code where i update rotate values:</p>
  <pre><code>const countryPaths = useMemo(() =&gt; {
  const clipAngle = 150;
  
  const projection = d3
    .geoAzimuthalEqualArea()
    // .rotate([0, -90])
    .rotate([rotateX, rotateY])
    .fitSize([mapExtent, mapExtent], {
      type: 'FeatureCollection',
      features: COUNTRIES,
    })
    .clipAngle(clipAngle)
    .translate([dimensions.width / 2, mapExtent / 2]);
  
  const geoPath = d3.geoPath().projection(projection);
  
  const windowPaths = COUNTRIES.map(geoPath);
  
  return windowPaths;
  }, [dimensions, rotateX, rotateY]);
  </code></pre>
  </li>
  </ol>
  <p><a href="https://i.stack.imgur.com/p2QJx.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/p2QJx.png" alt="enter image description here" /></a></p>
  <p>here is my complete code</p>
  <ol>
  <li>App.js</li>
  </ol>
  <pre><code>import React, {useState, useMemo, useEffect, useRef} from 'react';
  import {
    StyleSheet,
    View,
    Dimensions,
    Animated,
    PanResponder,
    Text,
    SafeAreaView,
  } from 'react-native';
  
  import Map from './components/Map';
  
  import COLORS from './constants/Colors';
  import movingAverage from './functions/movingAverage';
  import * as d3 from 'd3';
  import covidData_raw from './assets/data/who_data.json';
  
  export default function App(props) {
    const dimensions = Dimensions.get('window');
    const [stat, setStat] = useState('avg_confirmed');
    const [date, setDate] = useState('2020-04-24');
  
    //Data Manipulation
    const covidData = useMemo(() =&gt; {
      const countriesAsArray = Object.keys(covidData_raw).map((key) =&gt; ({
        name: key,
        data: covidData_raw[key],
      }));
  
      const windowSize = 7;
  
      const countriesWithAvg = countriesAsArray.map((country) =&gt; ({
        name: country.name,
        data: [...movingAverage(country.data, windowSize)],
      }));
  
      const onlyCountriesWithData = countriesWithAvg.filter(
        (country) =&gt; country.data.findIndex((d, _) =&gt; d[stat] &gt;= 10) != -1,
      );
  
      return onlyCountriesWithData;
    }, []);
  
    const maxY = useMemo(() =&gt; {
      return d3.max(covidData, (country) =&gt; d3.max(country.data, (d) =&gt; d[stat]));
    }, [stat]);
  
    const colorize = useMemo(() =&gt; {
      const colorScale = d3
        .scaleSequentialSymlog(d3.interpolateReds)
        .domain([0, maxY]);
  
      return colorScale;
    });
  
    return (
      &lt;SafeAreaView&gt;
        &lt;View&gt;
          &lt;Map
            dimensions={dimensions}
            data={covidData}
            date={date}
            colorize={colorize}
            stat={stat}
          /&gt;
        &lt;/View&gt;
      &lt;/SafeAreaView&gt;
    );
  }
  
  const styles = StyleSheet.create({
    container: {
      flex: 1,
      backgroundColor: COLORS.primary,
      alignItems: 'center',
      justifyContent: 'center',
    },
    rotateView: {
      width: 300,
      height: 300,
      backgroundColor: 'black',
      shadowOpacity: 0.2,
    },
  });
  
  </code></pre>
  <ol start="2">
  <li>map.js</li>
  </ol>
  <pre><code>import React, {useMemo, useState, useEffect} from 'react';
  import {StyleSheet, View, Animated, PanResponder} from 'react-native';
  
  //LIBRARIES
  import Svg, {G, Path, Circle} from 'react-native-svg';
  import * as d3 from 'd3';
  import {
    PanGestureHandler,
    PinchGestureHandler,
    State,
  } from 'react-native-gesture-handler';
  
  //CONSTANTS
  import {COUNTRIES} from '../constants/CountryShapes';
  import COLORS from '../constants/Colors';
  
  //COMPONENTS
  import Button from './Button';
  
  const Map = (props) =&gt; {
    const [countryList, setCountryList] = useState([]);
    const [translateX, setTranslateX] = useState(0);
    const [translateY, setTranslateY] = useState(0);
    const [lastTranslateX, setLastTranslateX] = useState(0);
    const [lastTranslateY, setLastTranslateY] = useState(0);
    const [buttonOpacity, _] = useState(new Animated.Value(0));
    const [scale, setScale] = useState(1);
    const [prevScale, setPrevScale] = useState(1);
    const [lastScaleOffset, setLastScaleOffset] = useState(0);
  
    const [rotateX, setrotateX] = useState();
    const [rotateY, setrotateY] = useState();
  
    const {dimensions, data, date, colorize, stat} = props;
  
    //Gesture Handlers
    const panStateHandler = (event) =&gt; {
      if (event.nativeEvent.oldState === State.UNDETERMINED) {
        setLastTranslateX(translateX);
        setLastTranslateY(translateY);
      }
  
      if (event.nativeEvent.oldState === State.ACTIVE) {
        Animated.timing(buttonOpacity, {
          toValue: 1,
          duration: 1000,
          useNativeDriver: true,
        }).start();
      }
    };
  
    const panGestureHandler = (event) =&gt; {
      console.log('event', event.nativeEvent);
      setrotateX(event.nativeEvent.x);
      setrotateX(event.nativeEvent.y);
      setTranslateX(-event.nativeEvent.translationX / scale + lastTranslateX);
      setTranslateY(-event.nativeEvent.translationY / scale + lastTranslateY);
    };
  
    const pinchStateHandler = (event) =&gt; {
      if (event.nativeEvent.oldState === State.UNDETERMINED) {
        setLastScaleOffset(-1 + scale);
      }
  
      if (event.nativeEvent.oldState === State.ACTIVE) {
        Animated.timing(buttonOpacity, {
          toValue: 1,
          duration: 1000,
          useNativeDriver: true,
        }).start();
      }
    };
  
    const pinchGestureHandler = (event) =&gt; {
      if (
        event.nativeEvent.scale + lastScaleOffset &gt;= 1 &amp;&amp;
        event.nativeEvent.scale + lastScaleOffset &lt;= 5
      ) {
        setPrevScale(scale);
        setScale(event.nativeEvent.scale + lastScaleOffset);
        setTranslateX(
          translateX -
            (event.nativeEvent.focalX / scale -
              event.nativeEvent.focalX / prevScale),
        );
        setTranslateY(
          translateY -
            (event.nativeEvent.focalY / scale -
              event.nativeEvent.focalY / prevScale),
        );
      }
    };
  
    //Initialize Map Transforms
    const initializeMap = () =&gt; {
      setTranslateX(0);
      setTranslateY(0);
      setScale(1);
      setPrevScale(1);
      setLastScaleOffset(0);
      Animated.timing(buttonOpacity, {
        toValue: 0,
        duration: 1000,
        useNativeDriver: true,
      }).start();
    };
  
    //Create Map Paths
    const mapExtent = useMemo(() =&gt; {
      return dimensions.width &gt; dimensions.height / 2
        ? dimensions.height / 2
        : dimensions.width;
    }, [dimensions]);
  
      const countryPaths = useMemo(() =&gt; {
      const clipAngle = 150;
  
      const projection = d3
        .geoAzimuthalEqualArea()
        // .rotate([0, -90])
        .rotate([rotateX, rotateY])
        .fitSize([mapExtent, mapExtent], {
          type: 'FeatureCollection',
          features: COUNTRIES,
        })
        .clipAngle(clipAngle)
        .translate([dimensions.width / 2, mapExtent / 2]);
  
      const geoPath = d3.geoPath().projection(projection);
  
      const windowPaths = COUNTRIES.map(geoPath);
  
      return windowPaths;
      }, [dimensions, rotateX, rotateY]);
  
  
  
    useEffect(() =&gt; {
      setCountryList(
        countryPaths.map((path, i) =&gt; {
          const curCountry = COUNTRIES[i].properties.name;
  
          const isCountryNameInData = data.some(
            (country) =&gt; country.name === curCountry,
          );
  
          const curCountryData = isCountryNameInData
            ? data.find((country) =&gt; country.name === curCountry)['data']
            : null;
  
          const isDataAvailable = isCountryNameInData
            ? curCountryData.some((data) =&gt; data.date === date)
            : false;
  
          const dateIndex = isDataAvailable
            ? curCountryData.findIndex((x) =&gt; x.date === date)
            : null;
  
          return (
            &lt;Path
              key={COUNTRIES[i].properties.name}
              d={path}
              stroke={COLORS.greyLight}
              strokeOpacity={0.3}
              strokeWidth={0.6}
              fill={
                isDataAvailable
                  ? colorize(curCountryData[dateIndex][stat])
                  : COLORS.greyLight
              }
              opacity={isDataAvailable ? 1 : 0.4}
            /&gt;
          );
        }),
      );
    }, [rotateX, rotateY]);
  
    return (
      &lt;View&gt;
        &lt;PanGestureHandler
          onGestureEvent={(e) =&gt; panGestureHandler(e)}
          onHandlerStateChange={(e) =&gt; panStateHandler(e)}&gt;
          &lt;PinchGestureHandler
            onGestureEvent={(e) =&gt; pinchGestureHandler(e)}
            onHandlerStateChange={(e) =&gt; pinchStateHandler(e)}&gt;
            &lt;Svg
              width={dimensions.width}
              height={dimensions.height / 2}
              style={styles.svg}&gt;
              &lt;G
              // transform={`scale(${scale}) translate(${-translateX},${-translateY})`}
              &gt;
                &lt;Circle
                  cx={dimensions.width / 2}
                  cy={mapExtent / 2}
                  r={mapExtent / 2}
                  fill={COLORS.lightPrimary}
                /&gt;
                {countryList.map((x) =&gt; x)}
              &lt;/G&gt;
            &lt;/Svg&gt;
          &lt;/PinchGestureHandler&gt;
        &lt;/PanGestureHandler&gt;
      &lt;/View&gt;
    );
  };
  
  const styles = StyleSheet.create({
    svg: {},
    rotateView: {
      width: 100,
      height: 400,
      backgroundColor: 'black',
      shadowOffset: {height: 1, width: 1},
      shadowOpacity: 0.2,
    },
  });
  
  export default Map;
  
  </code></pre>