React Native Mapbox custom cluster pie chart - react-native

I'm using the package react-native-mapbox-gl/maps to generate a map with clustering. I need to be able to analyze each cluster and based on its contents generate a pie chart representing the different types of points within the cluster. Either it isn't possible or I can't figure it out given the different types of Layers and Sources. I'm honestly not even sure where to begin. Any help or pointing in the right direction is much appreciated!
I've been able to create my map using the react-native-maps package (Google Maps) and have custom clusters, but I find the memory usage of the Mapbox package to be so much better.
There is nothing special about how I'm generating my map but here is the code:
const mapStyles = {
icon: {
iconImage: ['get', 'icon'],
iconSize: [
'match',
['get', 'icon'],
'park', 0.9,
'parkLarge', 1.6,
'school', 0.9,
'schoolLarge', 1.6,
1, /* default */
],
iconAllowOverlap: true
},
clusteredPoints: {
circlePitchAlignment: 'map',
circleColor: [
'step',
['get', 'point_count'],
'#2A2E43',
100,
'#2A2E43',
750,
'#2A2E43',
],
circleRadius: ['step', ['get', 'point_count'], 20, 100, 30, 750, 40],
circleOpacity: 1,
circleStrokeWidth: 4,
circleStrokeColor: 'white',
},
clusterCount: {
textColor: 'white',
textField: '{point_count}',
textSize: 12,
textPitchAlignment: 'map',
},
};
<MapboxGL.MapView
ref={ref => (this.map = ref)}
style={{ flex: 1, zIndex: 100 }}
styleURL="mapbox://[hidden]"
onPress={() => this.props.onPressMap()}
onRegionDidChange={(region) => this.onRegionDidChange(region)}
onRegionWillChange={() => this.props.onRegionWillChange()}
pitchEnabled={false}
rotateEnabled={false}
localizeLabels={true}
>
<MapboxGL.UserLocation visible={true} />
<MapboxGL.Camera
ref={(c) => this.camera = c}
zoomLevel={this.props.zoomLevel}
centerCoordinate={this.props.location}
animationMode={'flyTo'}
animationDuration={200}
style={{ paddingBottom: 300 }}
/>
<MapboxGL.Images images={{ park: parkIcon, parkLarge: parkIcon, school: schoolIcon, schoolLarge: schoolIcon }} />
{
this.props.featureCollection && this.props.featureCollection.features && this.props.featureCollection.features.length > 0 ? (
<View>
<MapboxGL.ShapeSource
id="pointsSource"
shape={this.props.featureCollection}
onPress={(event) => this.props.onPressMarker(event)}
cluster
clusterRadius={80}
clusterMaxZoomLevel={14}
>
<MapboxGL.SymbolLayer
id="pointCount"
style={mapStyles.clusterCount}
/>
<MapboxGL.CircleLayer
id="clusteredPoints"
belowLayerID="pointCount"
filter={['has', 'point_count']}
// filter={['>', 'point_count', 1]}
style={mapStyles.clusteredPoints}
/>
<MapboxGL.SymbolLayer
id="favoritesIcons"
filter={['!', ['has', 'point_count']]}
// filter={['==', 'point_count', 1]}
style={mapStyles.icon}
/>
</MapboxGL.ShapeSource>
</View>
) : null
}
</MapboxGL.MapView>

Though the relevant example in our documentation is built with Mapbox GL JS rather than React Native, you might find this display HTML clusters with custom properties example to be a good starting point. It demonstrates how to make use of expressions to create pie chart-like SVGs for each cluster, depending on the properties of the data in the particular cluster.
A similar approach (manually synchronizing the clustered source with a pool of marker objects that updates continuously while the map view changes, rather than using a Mapbox GL layer to display the clusters) will likely be necessary in a React Native implementation as well.

Related

How can I use react-native-redash to translateZ with react-native-reanimated v2?

I have a list of cards. I am selecting a sublist from the cards and transforming their x and y location to another point in the screen. I am trying to update their zIndex using a shared variable that updates based upon the order I select them. However, this works fine on Android but not on iOS. (Please look at the image for visual clarification).
Visual representation of ideal scenario
This is a simplified code of what I have.
This is the render component.
<View>
<TapGestureHandler onGestureEvent={tapGestureEvent}>
<Animated.View style={[cardStyle]}>
<Animated.View style={[frontImageStyle]}>
<Image
source={require('../../../assets/frontImage.png')}
style={styles.card}
/>
</Animated.View>
<Animated.View style={[backImageStyle]}>
<Image
source={require('../../../assets/backImage.png')}
style={styles.card}
/>
</Animated.View>
</Animated.View>
</TapGestureHandler>
</View>
This is my gestureTapHander where I am updating the x and y locations and also updating the index for each card based upon the total cards drawn so far, this total cards drawn is a shared variable and defined in my parent component.
const tapGestureEvent = useAnimatedGestureHandler({
onStart: (event, context) => {
context.y = yPosition.value; // It seems I am not even using this.
},
onActive: (event, context) => {},
onEnd: (event, context) => {
newYPosition.value = someValue
cardDrawnIndex.value = cardsDrawn.value;
cardsDrawn.value += 1;
},
});
This is the style that I have on the outerComponent.
cardDrawnIndex = useSharedVariable(-1);
const cardStyle = useAnimatedStyle(() => {
return {
transform: [
{translateX: someXValue},
{translateY: someYValue},
{rotateZ: `${someVariable.value}deg`},
{rotateY: `${someVariable.value}deg`},
{translateX: wasToChangeAnchorPointForMyZRotation}, // I don't understand why there is a need to do it and why isn't it as simple as saying, rotate with this anchor point.
{translateY: wasToChangeAnchorPointForMyZRotation},
],
zIndex: cardDrawnIndex.value,
// elevation: drawnCardZPosition.value, // Also tried this, it works only for Android, not for iOS.
};
});
I see there is a transformZ utility in react-native-redash but I am unable to figure out how to use it correctly for my use case. Or alternatively, if I can fix this zIndex issue on iOS, (maybe it has to do something with creating new instances of my card.)

how to use array from axios post in react native

I'm fairly new to React Native. I have successfully fetched the required array through axios.post, in the following format:
"hospitals": [
{
"title": "Holy Family Hospital Rawalpindi",
"distance": "71.45372179866516"
},
{
"title": "Fauji Foundation Hospital Islamabad",
"distance": "62.242918533343705"
},
{
"title": "PIMS Hospital Islamabad",
"distance": "80.0576175928936"
}
I'm trying to display only the titles in a dropdown list and continuously failing. I just dont know the correct format/protocol for this (ie how to use individual values). Any help would be appreciated.
https://reactnative.dev/docs/picker
I think u wanna use that.
just map the items. like that:
<Picker
selectedValue={selectedValue}
style={{ height: 50, width: 150 }}
onValueChange={(itemValue, itemIndex) => setSelectedValue(itemValue)}
>
data.map((data)=>{
return( <Picker.Item label=data.title value=data.title />)
})
</Picker>

React Native conditionally setting state

I have an array of "favorite" listings per user. I want to set a state variable depending on if it is rendering a favorite item or a regular (non-favorite) item. This is a little heart icon that is either filled or unfilled. It is incorrectly setting the state on first render. Correctly sets it after refresh.. I'm doing something wrong and have tried many things to no avail so if someone could just look at this code and tell me if you see any glaring flaws. If not then I will look elsewhere for the problem.
Behavior when app loads: I am doing console.log just after the state is set to show whether it's a favorite or not, and the contents of the favorite state variable (an image url, but in the console it shows it as either 21 for non-fav, or 22 for a fav). I can see that it is correctly pulling the array of favorites, and correctly identifying those that are and are not favorited (1 means its a favorite). It is however not setting the state variable correctly. Furthermore, it IS setting it correctly for only some of the listings. Currently I have all listings marked as a favorite, and it only messes up the first 10. After that they all set right. MORE bizarre, is upon refreshing the screen, it correctly sets all of them.
MainHeader.js (pulling data from db, setting initial array of favorite listings, and passing it to the messagesScreen component)
const [favsArray, setFavsArray] = useState("");
useEffect(() => {
lookupUser()
.then((snapshot) => {
if (snapshot.hasChildren()) {
snapshot.forEach(function(childSnapshot) {
let favs = childSnapshot.child("favorites").val();
setFavsArray(favs);
})
}
})
.catch((error) => {console.error('Error:', error)});
}, []);
return (
<NavigationContainer>
<View style={styles.headerContainer}>
<Image
style={styles.image}
source={require("../assets/newheader4.png")}
/>
</View>
<Tab.Navigator
tabBarOptions={{
activeTintColor: "blue",
inactiveTintColor: "black",
style: {},
tabStyle: {
width: "auto",
backgroundColor: "#e0d5f3",
borderTopWidth: 3,
borderBottomWidth: 3,
borderRightColor: "gray",
},
labelStyle: {
fontSize: 14,
fontWeight: "bold",
},
scrollEnabled: true,
}}
>
<Tab.Screen name="All Deals" children={()=><MessagesScreen favsArray={favsArray} setFavsArray={setFavsArray}/>} />
</Tab.Navigator>
</NavigationContainer>
MessagesScreen, receives favsArray and renders a FlatList with component Card which it feeds favsArray to.
<FlatList
data={messagesShow}
keyExtractor={(messagesShow) => messagesShow.id.toString()}
renderItem={({ item }) => (
<Card
price={item.currentPrice}
title={item.title}
image={item.image}
posted={item.postedDate}
discAmount={item.discountAmount}
discType={item.discType}
expiration={item.expiration}
promoCode={item.promoCode}
affLink={item.amzLink}
indexStore={item.indexStore}
store={item.store}
favsArray = {favsArray}
/>
)}
ItemSeparatorComponent={ListItemSeparator}
contentContainerStyle={styles.messagesList}
refreshing={refreshing}
onRefresh={() =>
db.ref('deals').once('value', (snapshot) =>{
let testData = [];
snapshot.forEach((child)=>{
// if (child.val().hasOwnProperty('title')){
testData.push({
id: child.key,
title: child.val().hasOwnProperty('title') ? child.val().title : 'NA',
currentPrice: child.val().price,
discountAmount: child.val().discAmt,
discType: child.val().discType,
promoCode: child.val().promoCode,
expiration: child.val().expDate,
postedDate: child.val().postDate,
image: { uri: child.val().imageLink},
amzLink: child.val().affLink,
category: child.val().category,
indexStore: child.val().indexStore,
store: child.val().store
})
// }
checkMessages(testData);
})
})
.then()
.catch((error) => {console.error('Error:', error)})
}
/>
Card component, this is in a FlatList where favsArray is passed as a prop (correctly verified by console), along with the individual listing data. If it finds the listing in the fav array, it should set to HeartFilled (1), if not set to HeartEmpty (0).
let test = [];
test = favsArray.split(',');
let isFav = 0;
let found = test.find(function (element) {
return element == indexStore;
});
if (found != undefined){
isFav = 1;
}
const [heartFilled, setHeartFilled] = useState( isFav == 1 ? require('../assets/heartFilled.png') : require('../assets/heartEmpty.png'));
console.log(isFav + ' ' + heartFilled);
Looking at my console, you can see it correctly shows each listing as a favorite, but for the first 10 listings it sets the state to the wrong image (21, shown in red). These should all be 22.

how to order inputs horizontally within ArrayImput and SimpleFormIterator

Is it possible to order inputs horizontally within ArrayImput and SimpleFormIterator? I know that the default is vertical. Thanks
See image of vertical inputs inside ArrayInputs here. Can the inputs be arranged horizontally? Thanks
Yes you can, the ArrayInput is made using material-ui which comes with its style way. For instance, you can play with elements root, form etc...
const useIteratorStyle = makeStyles(() => ({
root: {
display: 'flex',
flexDirection: 'row',
},
form: {
width: '100%',
},
line: {
border: 0,
},
}));
const iteratorClasses = useIteratorStyle();
<ArrayInput {...props}>
<SimpleFormIterator classes={iteratorWithIndexClasses}>
...
</SimpleFormIterator>
</ArrayInput>
Gives me something like
You can override each property of the useStyles object defined in the SimpleFormIterator: https://github.com/marmelab/react-admin/blob/master/packages/ra-ui-materialui/src/form/SimpleFormIterator.tsx
Inline prop has been introduced in version 4.3. It should work like that
<ArrayInput {...props}>
<SimpleFormIterator inline>
...
</SimpleFormIterator>
</ArrayInput>

How do I start a `spring` animation with react-native-reanimated in a declarative way?

I have a simple Animated.View that I move based on the user's finger, my component looks like this:
return (
<View
style={styles.container}
>
<PanGestureHandler
ref={this.panRef}
onGestureEvent={this.handlePan}
onHandlerStateChange={this.handlePan}
>
<Animated.View
style={[
styles.parent,
{
transform: [
{ translateX: this.X },
],
},
]}
>
<MyAwesomeComponent />
</Animated.View>
</PanGestureHandler>
</View>
The problem is, I want to create a declarative method called scrollToPosition(position, animated)
It now looks like this:
scrollToPosition = (position, animated = false) => {
if (animated) {
// TODO: What to do now?
} else {
this.X.setValue(position);
}
}
, it works without an animation, but my goal now is to add a way to animate the value with an Ease too.
If we were on the react-native Animated world, we'd do something like this:
Animated.spring(this.X, {
toValue: position,
}).start();
but how do I do the same thing with react-native-reanimated.
Please don't waste time explaining about the UI vs JS realm, I know about it, and I do want to take advantage of running animations in the UI thread, I just don't know how to run this animation declaratively after the user i.e taps a button.
Thanks
In using Animated.spring in reanimated, you should define the config as without that, it won't work
const config = {
damping: 15,
mass: 1,
stiffness: 150,
overshootClamping: false,
restSpeedThreshold: 0.001,
restDisplacementThreshold: 0.001,
}
Animated.spring(this.X, {
toValue: position,
...config
}).start();