React Native d3 азимутальный поворот равной площади не плавный

#javascript #reactjs #react-native #d3.js #react-native-svg

#javascript #reactjs #react-native #d3.js #react-native-svg

Вопрос:

Я делаю проекцию d3 азимутальной равной площади в react-native, для этого я использовал этот пример. он работает нормально, но я обновляю значения поворота с помощью panGestureHandler это тоже работает, но не плавно, и для обновления карты требуется время.

это репозиторий этого.

  1. это код, в котором я обновляю значения поворота:
     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]);
     

введите описание изображения здесь

вот мой полный код

  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 amp;amp;
      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;

 

Комментарии:

1. Нужен ли вам такой уровень детализации в вашем topojson? В нем есть Питкэрн — он не будет отображаться на карте, а с населением в пару десятков человек, вероятно, в этом нет необходимости. Упрощение вашей геометрии, даже за счет увеличения площадей (например, острова Родос) или областей детализации (например, Аляскинского попрошайничества), не изменит внешний вид карты, но, вероятно, будет самым простым способом избежать задержек при перепроектировании каждой точки в вашей геометрии. Попробуйте этот файл

2. @yathavan можете ли вы создать мини-воспроизводимый пример на stackblitz или где-либо еще?

3. @JoeSorocin я это исправил, я скоро опубликую свой ответ, спасибо за ваш ответ

Ответ №1:

как я исправил эту проблему, так это :

  1. Я могу изменить страны json на страны-110m.json ‘;
  2. удалите rotateX, rotateY и замените на translateX translateY
  3. новый код поворота — это: .rotate([-translateX, translateY])

если у вас есть какие-либо сомнения, пожалуйста, ознакомьтесь с моим полным исходным кодом на Github