#javascript #reactjs #react-native #d3.js #react-native-svg
#javascript #reactjs #react-native #d3.js #react-native-svg
Вопрос:
Я делаю проекцию d3 азимутальной равной площади в react-native, для этого я использовал этот пример. он работает нормально, но я обновляю значения поворота с помощью panGestureHandler это тоже работает, но не плавно, и для обновления карты требуется время.
- это код, в котором я обновляю значения поворота:
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]);
вот мой полный код
- 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,
},
});
- 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:
как я исправил эту проблему, так это :
- Я могу изменить страны json на страны-110m.json ‘;
- удалите
rotateX, rotateY
и замените наtranslateX translateY
- новый код поворота — это:
.rotate([-translateX, translateY])
если у вас есть какие-либо сомнения, пожалуйста, ознакомьтесь с моим полным исходным кодом на Github