Search code examples
reactjsdeck.gl

deck.gl GeoJsonLayer: update feature color on click


I'm using the deck.gl GeoJsonLayer to display regions on a map. through an event, I was able to make these regions selectable. what I am struggling with, is to change the color of a feature in the feature selection after clicking on it.

this is the render layer function (adapted from here)

_renderLayers(props) {
  const { geofeats } = props;

return [
  new GeoJsonLayer({
    id: 'geojson',
    data: geofeats,
    opacity: 0.8,
    stroked: true,
    filled: true,
    extruded: true,
    wireframe: true,
    getElevation: f => 10000, 
    getFillColor: f =>
    {
        if(f.properties.selected)
        {
            return  [200, 200, 100];
        } else return  [200, 100, 150];

    },
    getLineColor: [255, 255, 255],
    pickable: true,
    onHover: this._onHover, 
    onClick: this._onClick
  })
];
}

the problem is, when I update the selection state of a feature in the feature collection via setState(), the rendering is not updated even though the state change is represented in the data..

this is how I relay the 'geofeats' object:

  render() {
  const {features} = this.state;

  const {mapStyle = 'mapbox://styles/mapbox/light-v9'} = this.props;

return (
  <DeckGL
    layers={this._renderLayers({geofeats: features})}
    effects={this._effects}
    initialViewState={INITIAL_VIEW_STATE}
    controller={true}
  >
    <StaticMap
      reuseMaps
      mapStyle={mapStyle}
      preventStyleDiffing={true}
      mapboxApiAccessToken={MAPBOX_TOKEN}
    />

    {this._renderTooltip}
  </DeckGL>
);
}

I tried it via setState instead of via props - but the result is the same. the feature collection is handed over to the GeoJsonLayer but never updated.

could someone tell me, what I'm doing wrong?

Update: gist example with error reproduction: https://gist.github.com/jaronimoe/efdbb58b3f52c2aac63362a921802cfe


Solution

  • Your issue is two fold.

    First you never assign a unique ID to each feature, which ends up meaning that every feature would be assigned to selected when you hover one item. this is because in setFeatureSelected you are comparing undefined == undefined aka (currentID == selectedId). Just as a side note you should default to triple equal

    Second, you need to tell your GeoJsonLayer how to update the colors. These are the changes you'll need to make:

    function setFeatureSelected(features, selfeat) {
      let selectedID = selfeat.properties.id;
      for (let i = 0; i < features.features.length; i++) {
        let currentID = features.features[i].properties.id;
    
        if (selectedID === currentID) {
          features.features[i].properties.selected = true;
        } else {
          // Make sure to update the others to be false, so that way only one is ever selected
          features.features[i].properties.selected = false;
        }
      }
    
      return features;
    }
    

    Then you want to update initFeatureSelected to actually inject ID's. For now I'm just using the index in the array, but you can use something more explicit if you'd like

    function initFeatureSelected(features) {
      for (let i = 0; i < features.features.length; i++) {
        features.features[i].properties.selected = false;
    
        // Track each feature individually with a unique ID.
        features.features[i].properties.id = i;
      }
    
      return features;
    }
    

    The last part that you need is to tell the GeoJsonLayer that you have new values to re-calculate the getFillColor function. This is done with updateTriggers where you pass the value of the thing that determines if the color should change. Which, in your case is the selected id of your feature

    new GeoJsonLayer({
      id: 'geojson',
      data,
      opacity: 0.8,
      stroked: false,
      filled: true,
      extruded: true,
      wireframe: true,
      getElevation: 50, //Math.sqrt(f.properties.valuePerSqm) * 10,
      getFillColor: d => d.properties.selected ? [100, 105, 155] : [55, 205, 155], //COLOR_SCALE(f.properties.growth),
      getLineColor: [255, 255, 255],
      updateTriggers: {
        getFillColor: [
          this.state.hoveredObject
            ? this.state.hoveredObject.properties.id
            : null
        ]
      },
      pickable: true,
      onHover: this._onHover
    })