Deck.gl: Transitions create significant lag on map pan/rotate

Created on 16 Dec 2019  路  4Comments  路  Source: visgl/deck.gl

Description

The presence of transitions props in layer creates ~0.5sec lag on rotate/pan, even if no transnasional state occurs. There is no lag on the transition itself (i.e, the color/scale of GeoJson objects), only on the pan/rotate of the map, even if Layer is hidden (visible:false) and even if the methods related to the transition animation (updateTriggers) are off.

Repro Steps


constructor(props) {
   ...
        this._onViewStateChange = this._onViewStateChange.bind(this);
    }

    _onViewStateChange({ viewState }) {
        this.setState({ viewState });
    }
new GeoJsonLayer({
                /*
                Edit geojson:
                https://codesandbox.io/s/7y3qk00o0q
                */

                id: "GEOJSON_GRID",
                data,
_______________________
// Even if onClick,  onHover are commented, transition will create lag 
_______________________
                onClick: (event, picked) => {
                    this._handlePicking(event, picked);
                },
                onHover: (event, hovered) => {
                    this._handleHovered(event, hovered);
                },
                visible: layersProps.includes("GEOJSON_GRID") ? true : false,
                pickable: true,
                extruded: true,
                getElevation: d =>
                    d.properties.land_use === "M1" ? d.properties.height : 1,
                getFillColor: d =>
                    d.properties.type !== undefined
                        ? this.colors.picked
                        : d.properties.color
                        ? d.properties.color
                        : d.properties.land_use === "M1"
                        ? this.colors.idle
                        : this.colors.water,
                updateTriggers: {
                    getElevation: d => d.properties.height,
                    getFillColor: d => d.properties.color
                },

_______________________
// commenting this solves the lag issue 

_______________________
                transitions: {
                    getElevation: 250,
                    getFillColor: 250
                }
            })

the render part:

 render() {
        return (
            <DeckGL
                ref={deck => {
                    this.deckGL = deck;
                }}
                viewState={this.state.viewState}
                onViewStateChange={this._onViewStateChange}
                className="map"
                layers={this._renderLayers()}
                effects={this._effects}
                controller={true}
            >
                <StaticMap

// ATTEMPTED TO SOLVE WITH `asyncRender` didn't work
                    asyncRender={true}

                    dragRotate={true}
                    reuseMaps={true}
                    mapboxApiAccessToken={process.env.REACT_APP_MAPBOX_TOKEN}
                    mapStyle={process.env.REACT_APP_MAPBOX_STYLE}
                    preventStyleDiffing={true}
                />
            </DeckGL>
        );
    }

Environment (please complete the following information):

  • Framework Version: [e.g. deck.gl 6.3.0]
    deck.[email protected]
  • Browser Version: [e.g. Chrome 71.0]
    Version 78.0.3904.108
  • OS: [e.g. Mac OS X 10.4]
    OS X 10.14.6

Logs

question

Most helpful comment

@tgorkin thanks, worked well. As you've suggested, collecting the hovered set in a different state object did the trick. The new hover method:

  _handleHovered = (hovered, event) => {

            const hoveredProps = hovered.object.properties;
            // color the hovered object and wait
            hoveredProps.color = this.colors.hovered;
            hoveredProps.oldColor = hoveredProps.picked
                ? this.colors.picked
                : this.colors.idle;
            setTimeout(() => {
                hoveredProps.color = hoveredProps.oldColor;
            }, 250);

        this.setState({
//---- 
//here we only change the state of the hovered batch, not the whole Geojson 
//----
            hoveredCellsState: hovered
        });
    };

All 4 comments

You're using updateTriggers wrong. Triggers are not callbacks but objects: https://deck.gl/#/documentation/deckgl-api-reference/layers/layer?section=updatetriggers-object-optional-

Because your triggers change shallowly every time the map is panned/rotated, they trigger attribute update, and in your case, transitions as well.

@Pessimistress thanks for your prompt reply. I'm afraid I'm still unclear as to what it means that "Triggers are objects". This is similar to this question: https://github.com/uber/deck.gl/issues/2123#issuecomment-408661723

When trying to replace the shortened methods with the entire Geojson object (which is being updated in a different function, see below), no update is happening. Should I create a separate object that collect only heights/colors of changes geojson features and use it with tiggers?

As a side note, it might be useful to amend https://deck.gl/#/documentation/deckgl-api-reference/layers/layer?section=updatetriggers-object-optional- so that it would be clear how female/male are changing.

 new GeoJsonLayer({
                /*
                Edit geojson:
                https://codesandbox.io/s/7y3qk00o0q
                */

                id: "GEOJSON_GRID",
                data: this.state.geoJsonData,
                visible: layersProps.includes("GEOJSON_GRID") ? true : false,
                pickable: true,
                extruded: true,
                lineWidthScale: 1,
                lineWidthMinPixels: 2,
                getElevation: d =>
                    d.properties.land_use === "M1" ? d.properties.height : 1,
                getFillColor: d =>
                    d.properties.type !== undefined
                        ? this.colors.picked
                        : d.properties.color
                        ? d.properties.color
                        : d.properties.land_use === "M1"
                        ? this.colors.idle
                        : this.colors.water,
                onClick: (event, picked) => {
                    this._handlePicking(event, picked);
                },
                onHover: (event, hovered) => {
                    this._handleHovered(event, hovered);
                },

_____________________________
This did not work: 
                updateTriggers: {
                    getElevation: this.state.geoJsonData, // also tried [this.state.geoJsonData]
                    getFillColor: this.state.geoJsonData
----------------------------
                },
                transitions: {
                    getElevation: 250,
                    getFillColor: 250
                }
            })

And this is the geojson update function:

    _handleHovered = (hovered, event) => {
        if (
            hovered.object &&
            hovered.object.properties.land_use === "M1" &&
            !hovered.object.properties.interactive
        ) {
            const hoveredProps = this.state.geoJsonData.features[hovered.index]
                .properties;
            // color the hovered object and wait
            hoveredProps.color = this.colors.hovered;
            hoveredProps.oldColor = hoveredProps.picked
                ? this.colors.picked
                : this.colors.idle;
            setTimeout(() => {
                hoveredProps.color = hoveredProps.oldColor;
            }, 500);
        }

        this.setState({
            geoJsonData: this.state.geoJsonData
        });
    };

I think the challenge you are running into here is that the updateTriggers functionality should generally be used to trigger a layer to update if some value outside of your data object changes. However, in your case, the value changing is nested deeply inside of your data object this.state.geoJsonData. For performance reasons, deck.gl layers try to detect changes to the data object using shallow comparison (see Equality comparisons and sameness). Your logic in _handleHovered changes a deeply nested property of a geoJson feature at a particular hovered.index. However since that field is deeply nested inside of your data object, deck.gl does not detect that the object has changed through shallow comparison.

One suggestion would perhaps be to store the hovered.index as an additional field in your react component state, and update the index along with your geoJsonData state in your _handleHovered this.setState call. You could then set the updateTrigger to this this.state.hoveredIndex and that should cause the necessary update logic to trigger. Give this a try and let us know how it goes.

@tgorkin thanks, worked well. As you've suggested, collecting the hovered set in a different state object did the trick. The new hover method:

  _handleHovered = (hovered, event) => {

            const hoveredProps = hovered.object.properties;
            // color the hovered object and wait
            hoveredProps.color = this.colors.hovered;
            hoveredProps.oldColor = hoveredProps.picked
                ? this.colors.picked
                : this.colors.idle;
            setTimeout(() => {
                hoveredProps.color = hoveredProps.oldColor;
            }, 250);

        this.setState({
//---- 
//here we only change the state of the hovered batch, not the whole Geojson 
//----
            hoveredCellsState: hovered
        });
    };
Was this page helpful?
0 / 5 - 0 ratings