React-native-maps fill regions with different colors - reactjs

I'm using the react-native-maps library to render GeoJSON data to a map. Is there a way to fill regions with different colors? I have a GeoJSON file of countries, and I'm passing it into the <Geojson> component.
<MapView
style={styles.map}
zoomEnabled
provider={PROVIDER_GOOGLE}
>
<Geojson
fillColor="red"
geojson={geojson}
/>
</MapView>
This is the result:
Similar question: react-native-maps fill color to each region.

The simplest way to do this is to add fill property to the features in your GeoJSON object before passing it to the <Geojson /> component.
const colorizedGeojson = {
...geojson,
features: geojson.features.map(feature => ({
...feature,
properties: {
...feature.properties,
fill: getColor(feature.properties.values.population)
}
})),
};
You'll have to implement your own getColor function, depending on the data you want to represent. The population value used here is just an example. Using d3 interpolation to get the color for the value you want to represent is a common choice.
Once you've added the fill property to your GeoJSON data, you can pass that object to the component instead of the raw GeoJSON. Make sure you don't pass the fillColor property to the component, as that would override the feature colors.
<Geojson geojson={colorizedGeojson} />
Et voila! The react-native-maps component will render each region/country with the color you choose:

It is possible to use multiple <Geojson /> component and set different fillColor prop to them. Something like that.
{geojson.features.map(feature => {
const insertedObject = {
features: [feature]
};
if (feature.id === 'AGO' ||feature.id === 'AFG' || feature.id === 'RUS') {
return <Geojson
geojson={insertedObject}
fillColor={'orange'}
/>
}
return <Geojson
geojson={insertedObject}
fillColor={'red'}
/>
})}

Related

How to identify, inside an onClick event handler, which out of many polygons plotted on the same map (using react-leaflet) was clicked?

Situation:
I am plotting a country and all its state boundaries on a map using the react-leaflet. I am plotting each state boundary as a polygon (definitions are passed in a JSON file.) using the function . All the state boundary definition is passed in a single JSON file as different JSON objects. Each object has a unique id.
My Code:
import React from 'react'
import { MapContainer, TileLayer, GeoJSON } from 'react-leaflet'
import * as L from "leaflet";
const Map = (props) => {
let cordinates = [14.716, -14.467] // Dispaly coordinates
return (
// Cordinates of map
<MapContainer center={cordinates} zoom={7} scrollWheelZoom={false}>
{/* Display map */}
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{/* <GeoJSON id={Math.random()} data={country} /> */}
{/* Highlited area in map */}
<GeoJSON data={props.state} onEachFeature={props.handleEachFeature} />
</MapContainer>
)
}
export default Map
I am passing the JSON file and handleEachFeature function(returning the whole JSON file in the console) as props.
What I want to do:
When a user clicks on the map, I want to clear up the entire map and only plot the state within which the click was. Basically, the state will be zoomed and I also want to plot its district boundaries (I have definitions for the district boundaries for each state as well).
Approach I am taking:
I am trying to capture the id associated with the polygon (corresponding to the state) that was clicked inside the onClick event. I can then erase the existing map and using the captured id I can plot the state (and its districts) clicked. However, no matter which state is clicked, the onClick event is returning me the whole data of all the polygons. Following is my code:
On click handleEachFeature function:
function handleEachFeature(feature, layer) {
layer.on("click", L.bind(handleClick, null, layer));
}
// This is returning whole json file in console. But, I want only Polygon id on which user clicked.
function handleClick(e) {
console.log(e);
}
Things I already tried:
I used a single JSON file that contains multiple polygons. However, onClick event I get the whole JSON file, not any unique value to identify the polygon.
I also tried using different JSON files for each polygon (state) and add them to the map one by one but got the same result.
Please suggest any approach using react-leaflet or some other library.
You can do this by storing the unique identifier (cartodb_id in the provided example) in a variable and then use it to change the style of the geojson and render the clicked district with a specific style.
Using onEachFeature function you can derive the unique id and zoom to the clicked district. Once you store it in a var you can then filter the geojson by showing only this object that contains the unique id. Since react-leaflet's geojson comp data property is immutable you have to play with the reference (ref). You can use leaflet's eachLayer to attach specific style to all objects apart from the clicked. The latter will be achieved by setting the clicked layer style once you filter the geojson via a useeffect (see code below). Then using leaflet's addData you can readd the filtered geojson on the map.
export default function Map() {
const [map, setMap] = useState(null);
const geojsonRef = useRef();
const [featureId, setFeatureId] = useState(null);
const handleEachFeature = (feature, layer) => {
layer.on({
click: (e) => {
setFeatureId(feature.properties.cartodb_id);
map.fitBounds(e.target.getBounds());
}
});
};
useEffect(() => {
if (!featureId || !geojsonRef.current) return;
geojsonRef.current.eachLayer((layer) => {
layer.setStyle({
opacity: 0.5,
weight: 0
});
}); // inherited from LayerGroup
const newDistricts = districts.features.filter((district) => {
return district.properties.cartodb_id === featureId;
});
geojsonRef.current.addData(newDistricts);
}, [featureId]);
return (
<MapContainer
center={position}
zoom={9}
style={{ height: "100vh" }}
ref={setMap}
>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{map && (
<GeoJSON
ref={geojsonRef}
data={districts}
onEachFeature={handleEachFeature}
/>
)}
</MapContainer>
);
}
You could erase it entirely using clearLayers method but that makes no sense for me because you will end up with showing only the clicked district once you click it. I tried another approach by changing the style of all other districts apart from the clicked one. This way you can click a new one and revert the style of the previous one.
A simple free geojson is used to present the result.
Demo

React - Alternatives to triggering rerender of specific components after child mounting finishes

This is more of a conceptual question than anything else.
I'm trying to draw a SVG line between two elements in my application. The way I'm currently doing this is by having a top level ref, inside of which I store a ref for each of the child elements indexed by an arbitrary key. I then draw the arrows, deduced from a 2 dimensional array of pairs of these keys, look up the ref, and use the positioning of those elements to create the SVG's coordinates.
The problem with this method is, besides initial render, every render update afterwards uses outdated ref data as the children get rendered (and therefore positioned) after the parent, inside which the SVG layer is contained.
I've already thought of the obvious answers, useEffects, setStates, etc., but there doesn't seem to be a good solution here that I can think of. A lot of the obvious answers don't necessarily work in this case since they cause render loops. Another solution that I initially thought of was breaking down the arrow rendering to within each of the children, but this had problems of its own that initially caused the transition to having all of the refs live at the parent level.
My current solution is to key the SVG Layer component to a state variable, and then change the value of this variable in a useEffect once the final item renders, but this solution feels messy and improper.
Not exact but here's some code of the issue
Parent Component:
export default ({ items }) => {
const [svgKey, setSvgKey] = useState(0);
const pairs = items.reduce((arr, item) => {
for (const subItem of item.subItems) {
arr.push([subItem, item]);
}
}, [])
const itemRefs = useRef({});
return (
<>
{items.map(item => <Row items={items} setSvgKey={setSvgKey} item={item} refs={refs} key={item.key} />}
<SVGLayer key={svgKey} pairs={pairs} refs={refs} />
</>
);
}
SVG Layer
export default ({ pairs, refs }) => (
<svg>
{pairs.map(([a, b], i) => (
<Arrow key={i} a={refs.current[a.key]} b={refs.current[b.key]} />
)}
</svg>
);
Arrow
export default ({ a, b }) => <path d={[math for line coords here]} />;
Row
export default ({ refs, item, items, setSvgKey }) => {
useEffect(() => {
if (item.key === items[items.length - 1].key) {
setSvgKey(key => ++key);
}
});
return (
<div ref={el => (refs.current[item.key] = el)} />
);
}

Recharts: LabelList content prop not working

I am trying to do a custom label for my barchart using <LabelList> component.
This is my LabelList component and the corresponding render content function:
<LabelList
dataKey='pv'
position='insideRight'
fill='#f5f5f5'
className={'chart-label'}
offset={15}
content={renderLabel}
/>
const renderLabel = function(props: LabelProps){
return `${props.value}%`;
};
I just need to append a % symbol at the end of the chart value. but this is not working.
I used formatter prop for <LabelList> component instead of content prop.
<LabelList
dataKey='pv'
position='insideRight'
fill='#f5f5f5'
className={'chart-label'}
offset={15}
formatter={renderLabel}
/>
const renderLabel = (props: any) => {
return `${props}%`;
};
Yes using formatter for this is correct when you just want to affect the raw label text. The content property/api should be used if you want to provide the full markup for the label itself with full freedom to do what ever you like, and can be set to another react component, or to a function which must return the result from JSX rendering
Heres an example for other who stumbles across this blog and might need to use the 'content' freedom.
Use LabelList with content set to function which returns rendered JSX
<LabelList
content={renderLabelContent}
/>
The function:
const renderLabelContent = (props: object) => {
const { x: number, y, width, height, value } = props;
return (
<div>
{// All kinds of custom JSX/html here}
{value}
</div>
);
};
Using it with another selfmade react component (CustomizedLabel), you can pass in properties and values;
<LabelList
content={<CustomizedLabel external={external} />}
/>

how can I change react-google-maps <DrawingManager> component prop drawingMode props in runtime without change component state

I'm using react-google-maps lib in my React project. I want that, after complete to draw a shape, the user can't draw anymore and the shape stays on the screen.
I have de flowing snippet:
<GoogleMap
zoom={16}
center={initialPosition}
mapTypeId="satellite"
>
<DrawingManager
drawingMode={"polygon"}
onPolygonComplete={onDrawComplete}
/>
</GoogleMap>
With this code, the use can draw any shape that he and how many shapes he want.
I saw that I can change drawingMode of componente to null.
I've tried to store this value in a component state, but when I tried to change this inside onDrawComplete() callback, the map re-renders and I don't want that behavior.
Question: How can I change this prop to null without use component state?
You can achieve this by the following steps:
Import the objects from the library:
import {
withGoogleMap,
GoogleMap,
Marker,
Polyline,
Circle,
Rectangle,
Polygon
} from "react-google-maps";
import { DrawingManager } from "react-google-maps/lib/components/drawing/DrawingManager";
Declare different state variables for each of the overlay. These will be the variables that you will need in creating these different overlay objects. You should also have a variable for drawingControlEnabled to set the value of options>drawingControl parameter of Drawing Manager.
this.state = {
drawingControlEnabled: true,
marker: null,
polyline: null,
circleRadius: null,
circleCenter: null,
rectangle: null,
polygon: null,
visible: true
};
Use drawing manager with onOverlayComplete parameter where it will go to a function that will listen everytime you are done drawing the overlay.
<DrawingManager
onOverlayComplete={this.overlay}
defaultOptions={{
drawingControlOptions: {
position: google.maps.ControlPosition.TOP_CENTER,
drawingModes: [
google.maps.drawing.OverlayType.CIRCLE,
google.maps.drawing.OverlayType.POLYGON,
google.maps.drawing.OverlayType.POLYLINE,
google.maps.drawing.OverlayType.RECTANGLE
]
}
}}
options={{
drawingControl: this.state.drawingControlEnabled
}}
/>
In the onOverlayComplete function, you should set the drawingControlEnabled state to false. This will remove the Drawing Controls after the overlays are drawn. You should also have a switch case that will check the condition as to which overlay you had made. For each condition, get the value from the event.overlay for each overlay type and pass the value of the needed parameters for each overlay type in the state variables.
overlay = e => {
this.setState({
drawingControlEnabled: false
});
console.log(e);
switch (e.type) {
case "marker":
this.setState({
marker: {
lat: e.overlay.getPosition().lat(),
lng: e.overlay.getPosition().lng()
}
});
break;
case "polyline":
this.setState({
polyline: e.overlay.getPath()
});
break;
case "circle":
this.setState({
circleRadius: e.overlay.getRadius(),
circleCenter: e.overlay.getCenter()
});
break;
case "rectangle":
this.setState({
rectangle: e.overlay.getBounds()
});
break;
case "polygon":
this.setState({
polygon: e.overlay.getPaths()
});
break;
}
};
In the render() Area inside the object, add the different overlay object with the condition that check if the state variables for that object is not null. Doing this will only show the object once the state variables have values from the switch case above.
{this.state.marker !== null && <Marker position={this.state.marker} />}
{this.state.polyline !== null && (
<Polyline path={this.state.polyline} />
)}
{this.state.circleRadius !== null && (
<Circle
radius={this.state.circleRadius}
center={this.state.circleCenter}
visible={this.state.visible}
/>
)}
{this.state.rectangle !== null && (
<Rectangle bounds={this.state.rectangle} />
)}
{this.state.polygon !== null && <Polygon paths={this.state.polygon} />}
Here is a working code sample. note: Put your API key in the index.js for it to work properly.
TLDR: You need to catch drawingManager reference and do EVERYTHING using this.
Google MAPS and other Google things are like a paralell world (if i can say this). You need do everything using their instance directly.
google.maps.events.addEventListener
google.maps.events.addDOMListener
... and so on...
In React I couldn't find a way to doit diferent as this:
Use onLoad event to reach the drawingManager instance
<DrawingManager onLoad={handlerLoadDrawingManager} ...other />
Create a reference and asign the drawingManagerInstance recibed in handlerLoadDrawingManager
const drawingManager = useRef()
const handlerLoadDrawingManager = (drawingManagerInstance) => {
drawingManager.current = drawingManagerInstance
}
Use drawingManager.current.setDrawingMode(<some mode>)
const someFunction = () => drawingManager.current.setDrawingMode(google.maps.drawing.OverlayType.POLYGON)
See drawing manager overlay types for more modes.
PDT: Do the same with map instance, and polygons, and other map object that tyou need interact to

How to pass parameters to another screen without navigating to it

Right now to pass parameters to another screen I'm using the following:
this.props.navigation.navigate('MyWatchList', {watchLists: response});
This automatically navigates to the other screen. I'm trying to pass parameters to another screen without navigating away from the screen I'm on but I'm having trouble figuring out how to do it. I have been trying to use setParams but I have been having no success. Any help or a point in the right direction would be appreciated.
You can consider to use the asyncStorage in order to share data between your screens.
e.g
AsyncStorage.setItem('MyWatchList',JSON.stringify({watchLists: response}));
and retrieve by using
AsyncStorage.getItem('MyWatchList').then(item => {
const list = JSON.parse(item);
...
});
You can also use EventRegister
import { EventRegister } from "react-native-event-listeners";
send value like this:
<TouchableOpacity
onPress={() => {this.setState({ visibleModal: false });
EventRegister.emit("changeModalEvent", this.state.visibleModal);
}}
>
{this.state.theme == true ? (
<Icon3 name="cancel" color={Colors.night_color_text} size={30} />
) : (
<Image
source={require("../assets/images/Auto_Layout_Horizontal.png")}
style={{ height: 24, width: 24 }}
/>
)}
</TouchableOpacity>
read the value in any screen with respect to the key like this:
useEffect(() => {
let eventListner = EventRegister.addEventListener(
"changeModalEvent",
(data) => {
// console.log({ data });
setVisibleModal(data);
}
);
return () => {
EventRegister.removeEventListener(eventListner)
};
});
what i think you trying to achieve is persistent state across screen you can do that by multiple of ways
you can use global variable you will be able to read and write to them from all of your classes/functions read more here
How to use global variables in React Native?
But the most effective way is to use redux to mange your state
Redux is an open-source JavaScript library for managing and
centralizing application state.
You can Store Your Data in Array and when you navigate to next screen , Pass that Array with navigation

Resources