0

I'm new to JS, React and Leaflet but I think I've managed okay so far. I have a choropleth displaying coffee information correctly. Unfortunately when I change the data within onEachCountry the changes are not reflected as this is not a state. Maybe I can somehow make each layer within the onEachCountry loop, a state? Or is there some easy refresh option I can call in the JSX maybe?

I've tried to use the useEffect for onEachCountry, I've tried console logs too and the data IS being updated, just not displayed. Other leaflet hooks seem to update more easily when changed.

Any help greatly appreciated.

import React, { useState, useEffect } from "react";
import { MapContainer, GeoJSON, TileLayer } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import "./ChoroMap.css";
import NavBarCountry from "../NavBarCountry";

// from Choro
const ChoroMap = ({ countries, coffees, onChangeLegend }) => {
  const [selectedStat, setSelectedStat] = useState("Producers");

  useEffect(() => {
    onEachCountry();
  }, [selectedStat]);

  const onProducersClick = function () {
    onChangeLegend("Producers");
    setSelectedStat("Producers");
  };

  const onExportersClick = function () {
    onChangeLegend("Exporters");
    setSelectedStat("Exporters");
  };

  const onFarmsClick = function () {
    onChangeLegend("Farms");
    setSelectedStat("Farms");
  };


  function legend(stat, comparisonArray) {
    if (stat >= comparisonArray[0]) {
      return "#741f1f";
    } else if (stat >= comparisonArray[1] && stat < comparisonArray[0]) {
      return "#9c2929";
    } else if (stat >= comparisonArray[2] && stat < comparisonArray[1]) {
      return "#d75e5e";
    } else if (stat >= comparisonArray[3] && stat < comparisonArray[2]) {
      return "#c57979";
    } else if (stat >= 0 && stat < comparisonArray[3]) {
      return "#f1b1b1";
    }
  }

  function stripNumber(numberString) {
    const number = Number(numberString.replace(/[^\d.-]/g, ""));
    return number;
  }

const onEachCountry = (country, layer) => {
if (country) {
  // defaults
  layer.options.fillOpacity = 1;
  layer.options.weight = 0.6;
  layer.options.color = "white";

      // country name from JSON
      const name = country.properties.ADMIN;

      const countryObj = coffees.find((coffee) => coffee.country === 
name);
      if (countryObj != null) {
        if ((selectedStat == "Producers")) {
          layer.bindPopup(`${name} 
${countryObj.production_volume}`);
          const productionVol = 
stripNumber(countryObj.production_volume);
          const foundColor = legend(
            productionVol,
            [10_000_000, 5_000_000, 2_000_000, 500_000]
          );
          layer.options.fillColor = foundColor;
        }
        else if ((selectedStat == "Exporters")) {
            layer.bindPopup(`${name} ${countryObj.export_volume}`);
            const exportVol = stripNumber(countryObj.export_volume);
            const foundColor = legend(
              exportVol,
              [10_000_000, 5_000_000, 2_000_000, 500_000]
            );
            layer.options.fillColor = foundColor;
          }
        else if ((selectedStat == "Farms")) {
        layer.bindPopup(`${name} ${countryObj.number_of_farms}`);
        const numberOfFarms = 
stripNumber(countryObj.number_of_farms);
        const foundColor = legend(
            numberOfFarms,
            [5, 10, 15, 20]
        );
        layer.options.fillColor = foundColor;
        }
        console.log('onEachCountry', layer.options.fillColor)
      }
    }
  };

  return (
    <div>
      <NavBarCountry
        onProducersClick={onProducersClick}
        onExportersClick={onExportersClick}
        onFarmsClick={onFarmsClick}
      />
      <MapContainer
        className="map"
        attributionControl={false}
        zoom={2.5}
        center={[10, 10]}
      >
        
        <GeoJSON data={countries} onEachFeature={onEachCountry}/>
      </MapContainer>
    </div>
  );
};

export default ChoroMap;
  • 1
    I'm confused as to where `layer` is decalred...are you missing some code here? What exactly is `onEachCountry`? It doesn't seem to actually be declared in this code example? – Seth Lutske May 25 '21 at 18:20
  • running `layer.options.fillColor = someColor` won't actually change the color. You need to use [`setStyle`](https://leafletjs.com/reference-1.7.1.html#path-setstyle) - see [this](https://stackoverflow.com/questions/15606027/dynamically-change-the-color-of-a-polygon-in-leaflet) – Seth Lutske May 25 '21 at 18:22
  • I'm sorry I accidentally deleted some code in my example. I actually have the const countryObj = coffees.find inside an onEachCountry, I'm not sure why this didn't display above. const onEachCountry = (country, layer) => { if (country) { etc. This is where the layer comes from. This does work I just can't refresh I have used setStyle before which does refresh, but don't know how to apply it to individual json countries – user2956284 May 25 '21 at 19:38
  • This setStyle post does seem helpful but it's JS instead of React-Leaflet. I could maybe adapt it by using useMap() hook. I'll investigate, thanks – user2956284 May 25 '21 at 19:40
  • Please edit your question to include those lines of code so that the codeblock has all details and is coherent. While `setStyle` is indeed vanilla js, so is layer.options.fillColor. Once you're inside the `onEachFeature` function, you've broken out of react back into vanilla leaflet code (which is fine). – Seth Lutske May 25 '21 at 21:15
  • Sorry, couldn't figure out how to edit. That's it now! I've noticed I can change the colour dynamically if I use layer.on( { mouseover: (event) => { event.target.setStyle({ color: "yellow"}); } }) So this refreshes everything, but unfortunately I need to use the mouse to do so – user2956284 May 26 '21 at 07:05
  • And using layer.setStyle directly doesn't do that for you? – Seth Lutske May 26 '21 at 14:30
  • 1
    I figured out a good method to get it to refresh (which I found elsewhere). You can add a key to the GeoJSON tag, I did key={key}. key is set as a state which defaults to 0. Every time I click on a button which should refresh the map I get it to add 1 to the key state which in turn forces a refresh of the GeoJSON tag by updating its key. – user2956284 Jun 03 '21 at 17:39
  • Not sure how to reply to and individual post, but layer.setStyle did not work as the onEachFeature does not update automatically. The easiest way is to set the key as listed above. – user2956284 Jun 03 '21 at 17:40

2 Answers2

0

I try to understand your problem, if you just want to refresh your geoJSON style in map, more than using choropleth, you better try this. It's more flexible and can be dynamic like choropleth

First, i recommended you to use class component than using function and change your react-leaflet version to 2.7.0. It's because i use that version and its' work very well. And as far as my experience in react-leaflet, there are no much discussion is talk about the new version, that's why i recommended you to use 2.7.0

Data

This one will help you to setting what geojson file that you'll show in your WebGIS

<GeoJSON
    data={this.state.isMyGeoJSON}
/>

onEachFeature

For setting all function that will called everytime you are doing something with your layer or feature. I recommended you to make it as arrow function in somewhere, because there are much function that can be called on onEachFeature later. Let me give example

<GeoJSON
    onEachFeature={this.onEachFeature}
/>

Place this onEachFeature function before render(){

onEachFeature = (feature, layer) => {
    console.log(fetaure) //array of feature
    console.log(layer) //array of layer
    console.log(layer.feature) //array of feature
    //you must remember, feature is part of layer, so it's up to you how to call it later

layer.on({
    mouseover : this.onMouseOver,
    mouseout : this.onMouseOut,
    mouseclick : this.onMouseClick
})

//i will show you example of onMouseOver
onMouseOver = (event) => {
    event.target.setStyle({
        color : "yellow"
    })

render(){
...

As you can see there, there are the one example how to change the color, but how if you want to click some button and change the color of your map. It's easy! First thing, again, i recommended you to place your default style inside onEachFeature. Why? because i usually using my GeoJSON style for my dynamic style, let me give example

 //add this inside your onEachFeature for example
 onEachFeature = (feature, layer) => {
     layer.options.color : "blue" //change line all layer line color to blue, this work beacuse onEachFeature is loop function that are calling all layer one by one, so this thing will help you to change all your layer line color options to blue
     //still confused? try to call console.log(layer.options) for experiment
 }

That layer options would be changable by your layer.onMouse. But how if we want to change it with button outside map? or any function? you can try this!

Style

Here should be the place for you to place your default style, but in this example let me tell you how to make your default style dynamic. If i remember, someone saying like this in some forum, "style inside GeoJSON just work with function call if you want to change it style". Then i create experiment like this.

<GeoJSON
    style={this.stateMapTheme}
/>

And inside my this.state i write like this

this.state = {
    mapTheme : this.firstStyle
}

Lets see here, we want to change this fillColor, because we already set our default color with blue inside onEachFeature, so lets make two function for example

//example of using data with if else
firstStyle(feature){
if (data < 20){
    return{
        fillColor : "red"
    }
} else {
    return{
        fillColor: "660000" //dark red
}


secondStyle(feature){
    return{
        fillColor : "green"
    }
}

create div somewhere outside your map

<div onClick={()=> {this.setState({mapTheme : this.secondStyle}) }}>green</div>
<div onClick={()=> {this.setState({mapTheme : this.firstStyle}) }}>red</div>

If you want to learn more about react-leaflet you can check this, this will help you to change the color depend on the data Medium React-Leaft by Deddy Setiawan

The result of react-leaflet example can be check here

Please let me help you from comment section if you still can't understand my solution

0

I did actually find a simple solution to this. I think it's potentially more of a hack than anything, but it works great and easier than refactoring code from scratch to use another method of colorization. I apologise for forgetting to add this earlier.

the tag can accept the argument index. I did <GeoJSON index={index} data={countries} onEachFeature={onEachCountry}/> I made index a STATE and set it to 1.

I then made a function so that every time a user clicks a tab to change the choropleth, it adds 1 to the index state, this in turn changes the index number of the GeoJSON tag and forces the map to refresh. Pretty nifty.