Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
133 views
in Technique[技术] by (71.8m points)

javascript - React Native d3 azimuthal equal-area rotate not smooth

I am doing the d3 azimuthal equal-area projection in react-native, i used this example 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.

this the repo of this.

  1. this is the code where i update rotate values:

    const countryPaths = useMemo(() => {
    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]);
    

enter image description here

here is my complete code

  1. App.js
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(() => {
    const countriesAsArray = Object.keys(covidData_raw).map((key) => ({
      name: key,
      data: covidData_raw[key],
    }));

    const windowSize = 7;

    const countriesWithAvg = countriesAsArray.map((country) => ({
      name: country.name,
      data: [...movingAverage(country.data, windowSize)],
    }));

    const onlyCountriesWithData = countriesWithAvg.filter(
      (country) => country.data.findIndex((d, _) => d[stat] >= 10) != -1,
    );

    return onlyCountriesWithData;
  }, []);

  const maxY = useMemo(() => {
    return d3.max(covidData, (country) => d3.max(country.data, (d) => d[stat]));
  }, [stat]);

  const colorize = useMemo(() => {
    const colorScale = d3
      .scaleSequentialSymlog(d3.interpolateReds)
      .domain([0, maxY]);

    return colorScale;
  });

  return (
    <SafeAreaView>
      <View>
        <Map
          dimensions={dimensions}
          data={covidData}
          date={date}
          colorize={colorize}
          stat={stat}
        />
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: COLORS.primary,
    alignItems: 'center',
    justifyContent: 'center',
  },
  rotateView: {
    width: 300,
    height: 300,
    backgroundColor: 'black',
    shadowOpacity: 0.2,
  },
});

  1. map.js
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) => {
  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) => {
    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) => {
    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) => {
    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) => {
    if (
      event.nativeEvent.scale + lastScaleOffset >= 1 &&
      event.nativeEvent.scale + lastScaleOffset <= 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 = () => {
    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(() => {
    return dimensions.width > dimensions.height / 2
      ? dimensions.height / 2
      : dimensions.width;
  }, [dimensions]);

    const countryPaths = useMemo(() => {
    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(() => {
    setCountryList(
      countryPaths.map((path, i) => {
        const curCountry = COUNTRIES[i].properties.name;

        const isCountryNameInData = data.some(
          (country) => country.name === curCountry,
        );

        const curCountryData = isCountryNameInData
          ? data.find((country) => country.name === curCountry)['data']
          : null;

        const isDataAvailable = isCountryNameInData
          ? curCountryData.some((data) => data.date === date)
          : false;

        const dateIndex = isDataAvailable
          ? curCountryData.findIndex((x) => x.date === date)
          : null;

        return (
          <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}
          />
        );
      }),
    );
  }, [rotateX, rotateY]);

  return (
    <View>
      <PanGestureHandler
        onGestureEvent={(e) => panGestureHandler(e)}
        onHandlerStateChange={(e) => panStateHandler(e)}>
        <PinchGestureHandler
          onGestureEvent={(e) => pinchGestureHandler(e)}
          onHandlerStateChange={(e) => pinchStateHandler(e)}>
          <Svg
            width={dimensions.width}
            height={dimensions.height / 2}
            style={styles.svg}>
            <G
            // transform={`scale(${scale}) translate(${-translateX},${-translateY})`}
            >
              <Circle
                cx={dimensions.width / 2}
                cy={mapExtent / 2}
                r={mapExtent / 2}
                fill={COLORS.lightPrimary}
              />
              {countryList.map((x) => x)}
            </G>
          </Svg>
        </PinchGestureHandler>
      </PanGestureHandler>
    </View>
  );
};

const styles = StyleSheet.create({
  svg: {},
  rotateView: {
    width: 100,
    height: 400,
    backgroundColor: 'black',
    shadowOffset: {height: 1, width: 1},
    shadowOpacity: 0.2,
  },
});

export default Map;


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

how i fixed this issue is :

  1. I cange countries json to countries-110m.json';
  2. delete the rotateX, rotateY and replace by translateX translateY
  3. new rotate code is: .rotate([-translateX, translateY])

if any doubts please check my complete source code from image


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...