mapbox-gl-js icon indicating copy to clipboard operation
mapbox-gl-js copied to clipboard

FeatureState support for Layout

Open cammanderson opened this issue 4 years ago • 29 comments

Motivation

Currently, the only "feature-state" properties are Paint. When working with interactivity on symbols, it doesn't give enough flexibility to work with complex sets of data and interactions without reverting to modifying the data source data. Currently the feature-state supports things such as halo and opacity for symbol.

Examples of wider feature state interactions and state include: hover, select, filter, relate, default icons, apply a collection of icons based on an application selection criteria. This ideally requires that the icon and visibility are dynamic based on application state.

I wish to propose consideration/discussion around the addition of "icon-image" (a layout property) to be able to access feature state, and then potentially expanding to "visibility".

Symbols are a core part of the application stylising. When we create interactions with the data, we use feature-state in order to be able to capture interaction with symbols. If we wish to create categorisation (such as assigning different symbols different icons given a different view of the data), or be able to toggle on groups of features that match criteria, the feature-state would be the place for us to be able to do this. We can quickly create properties in the feature-state (e.g. { selected: true }, { related: true } etc).

Currently, without access to the feature-state to modify visibility or icons, we must devise filter expressions that can eliminate the data (or show only what we need) into specific layers, which requires all filter criteria to be within the properties of the feature. If we need to interact to assign a different symbol, we either have to create a number of layers to match all the possible symbols we want to assign, or again modify the underlying data properties in order to read back.

Unfortunately, modifying the source data comes with a cost, and writing numerous layers also has a cost, and updating the filtering can be limiting if we have the complexity of the object in our application. Ideally, Feature State existed for faster state management of particular features.

We have worked around this in the past, but we are getting to a point where we are having to hide properties through opacity (not ideal) or have resorted to updating underlying data (to modify a symbol), but as you are filtering and moving through large collections of data we are getting slight unresponsive steps when we swap the data.

If we could control the icon-image and visibility with Feature State, we could quickly set symbol collections or change icons with ease.

Implementation

Update the style specification to support feature-state.

If the feature state updates for a feature, it would cause a re-render/paint of that feature. We could limit the layout props so that we don't modify other behaviours, such as collision, spacing, etc on a feature by feature basis.

cammanderson avatar Feb 17 '20 03:02 cammanderson

Agree that being able to use feature-state for icon-image would be handy. It has come up for me in the past - using feature state on hover to highlight an icon, but being unable to change the icon to an alternative version.

Re: visibility, does using icon-opacity help as a workaround? AFAIK, the performance cost of a layer with *-opacity: 0 is basically zero.

stevage avatar Feb 18 '20 21:02 stevage

I am under the impression that feature state also does not work for hatches, i.e. fill-pattern paint styles. It would be nice if this could also be supported.

songololo avatar Feb 19 '20 21:02 songololo

Hi! Thanks for this thoughtful proposal. We have discussions about issues like this pretty frequently.

There are Paint properties that can't work with feature state - some for performance reasons and some for reasons we forgot or that no longer apply. Layout properties are more difficult to support with feature state, as I understand it because of the placement/collision system (I think you mentioned this). Your icon-image example is interesting because it's kind of like a Paint property that has been categorized as Layout. There are other examples of this, and we have been talking about how we might add flexibility to these properties, or maybe in a future Style Spec re-categorize them. Visibility unfortunately has major layout implications otherwise it's the same as opacity: 0.

So maybe for icon-image we could do this without too much difficulty. I'll bring this up again and see what people think, like I said there are a few things that are Layout but seem more like Paint in terms of cost.

A little more explanation if it's useful (though maybe you already get this), one feature updating its position or size may need an entire re-layout of all other features which is one of the most expensive (and sometimes CPU bound) operations we perform. I'm not sure that we could only target a small set of features as layout is a whole-layer concern. We'd love to be able to support this kind of thing but we'd need a lot of work (and potentially API changes) to get enough performance budget to make re-layout interactive.

A stubborn, related question is whether feature state is the best model to support interactive updates to layout. With a lot of work there may be ways to support fine-grained updates directly through source layers that don't require a complete rebuilding and layout of all the dependent style layers. This would be a better design right?

If you have any thoughts on how we might implement new mechanisms for performant re-layout, we'd love to hear them.

ahk avatar Feb 25 '20 23:02 ahk

A stubborn, related question is whether feature state is the best model to support interactive updates to layout. With a lot of work there may be ways to support fine-grained updates directly through source layers that don't require a complete rebuilding and layout of all the dependent style layers. This would be a better design right?

Thanks so much for the response here. I'm having a little trouble grasping what you mean in this paragraph, particularly "to support interactive updates to layout".

Are you talking about a possible solution with an alternative mechanism where, say, an icon-image can be updated for one feature without triggering a refresh of the layout for the whole layer? I guess my thought is there are already quite a few mechanisms for limiting the number of features affected by something (eg, filters, data-driven expressions, feature state, setData on geojson sources...), so adding another might be increasing complexity?

Or maybe I'm not quite getting what you're hinting at.

stevage avatar Feb 25 '20 23:02 stevage

So maybe for icon-image we could do this without too much difficulty. I'll bring this up again and see what people think, like I said there are a few things that are Layout but seem more like Paint in terms of cost.

Hello! Just wanted to chime in that we would also greatly benefit from this feature! Are there any plans for adding this?

daschi avatar May 29 '20 22:05 daschi

Any update on this?

Really need this one.....

dollysingh3192 avatar Jul 30 '20 10:07 dollysingh3192

Still actual...

azinit avatar Sep 09 '20 18:09 azinit

Yeah. Would love to see support for this. Just tried to do

"text-offset": [
  ["number", ["feature-state", "posX"], 0],
  ["number", ["feature-state", "posY"], 0],
],

And got Error: layers.ports.layout.text-offset[0]: number expected, array found, so I suspect this is not supported for layout properties.

eivindml avatar Sep 24 '20 10:09 eivindml

I would love to use text-field with feature states, that would be awesome.

magnolo avatar Oct 06 '20 07:10 magnolo

Still here :)

dimagimburg avatar Nov 17 '20 16:11 dimagimburg

Would be very nice

BoJIbFbI4 avatar Jan 07 '21 12:01 BoJIbFbI4

Thanks for all the interest! This would be very useful to have. We have not yet started working on this.

ansis avatar Jan 08 '21 16:01 ansis

Would love to see this implemented show how, we currently have a very large set of data that we have prerendered in a Vector layer on our tile server to performance reasons (it is basically all the roads of The Netherlands and pure GeoJSON didn't perform at all). Until now we have only had the need to set the colors on all the individuel segments, how ever we would also like to update the icon and place a text-field on the map.

If there is any other way to get this done with using the vector layers that would also be a fine solution, going with a GeoJSON file however is not a option for us.

bvanleeuwen1995 avatar Feb 03 '21 10:02 bvanleeuwen1995

i would really like feature-states for icon-image, then maybe it would be possible to do image animations like gifs without high gpu loads like it was for me by using setLayoutProperty

enersis-pst avatar Aug 06 '21 09:08 enersis-pst

My use case for this requirement is a user selecting features from a set contiguous polygons and the resulting styling being a bit inconsistent, so I'd like to use feature-state to populate line-sort-order of the selected features

  map.setLayoutProperty('boundary-outlines', 'line-sort-key', [
    'case',
    ['==', ['feature-state', 'isSelected'], null], 1,
    2
  ])

In the picture below the user starts with the orange polygon boundaries, and then selects some of them with mouse click, once selected the boundaries turn yellow. At the moment there is inconsistent stacking of the orange and yellow.

line-sort-key

Thanks mapbox-gl team :)

rowanwins avatar Sep 17 '21 05:09 rowanwins

@rowanwins Probably obvious, but an easy workaround is a separate layer from the same source.

stevage avatar Sep 20 '21 06:09 stevage

Thanks for the suggestion @stevage - that had crossed my mind and I'm thinking that's what I'll resort to. Until this selection/editing workflow I'd never reached the limits of style expressions with a single layer, but I think I'm now at that point :)

rowanwins avatar Sep 20 '21 07:09 rowanwins

This would be very helpful if implemented. Any idea if it's being considered?

abaza738 avatar Mar 08 '22 14:03 abaza738

Agreed, this would be very helpful for me as well.

lmeromy avatar Jul 05 '22 19:07 lmeromy

Bump. Still need this functionality to change icon on hover or click.

wtravO avatar Jan 12 '23 20:01 wtravO

Another use-case would be showing labels (symbol text) from feature-state. we use this very often with fixed tiles where the data is only added to feature-state for styling. but we can't show the data in labels yet.

the workaround is to create an extra source dynamically which is tricky (getting unique features..)

oberhamsi avatar Feb 22 '23 10:02 oberhamsi

Bump, would be really helpful.

sienki-jenki avatar May 24 '23 08:05 sienki-jenki

+1

Being able to change the icon when selected (when the feature-state has an active property to 'true')

Workaround :

Instead of having 1 unique MapLayer, dynamic regarding the feature-state You get 2 MapLayers, on the same source Each MapLayers will load different icons 1 will display everything except the feature-state if the feature-state is set 1 will display nothing except the feature-state if the feature-state is set To perform, you play with the opacity, it looks like this :

let layer = {
    'icon-opacity': ['case', ['boolean', ['feature-state', 'active'], false], 0, 1]
};

let layerActive = {
    'icon-opacity': ['case', ['boolean', ['feature-state', 'active'], false], 1, 0]
}

fpassaniti avatar May 26 '23 15:05 fpassaniti

For those who want to change the icon-image on hover

Here's my workaround (this assumes you have a unique 'id' property in your .properties object, but you can adapt it to your use case):

  // map.on hover, change the cursor to a pointer and log the feature hovered over
    map.on('mouseenter', 'my-layer', e => {
        map.setLayoutProperty('my-layer', 'icon-image',
        [
        'match',
        ['get', 'id'],
        e.features[0].properties.id, 'hovered-marker', //image when id is the hovered feature id
        'default-marker' // default
        ]
        )
    });

    // on mouse leave, reset the image
    map.on('mouseleave', 'my-layer', e => {
        map.setLayoutProperty('my-layer', 'icon-image', 'default-marker')
    });

This essentially just dynamically changes the icon-image for the whole layer but only for the feature hovered by using a match condition using the hovered feature's properties. This strikes me as a cleaner, faster alternative to having to create a whole new layer as mentioned above.

louisondumont avatar Aug 01 '23 12:08 louisondumont

+1

I have a use case similar to that of @fpassaniti, where I need to set the symbol-sort-key conditionally based on the feature state. This essentially ensures that the active symbol is brought to the top compared to others.

3zzy avatar Dec 13 '23 01:12 3zzy

@3zzy In your case, you should probably just create a separate layer for the active feature. Show it only in that layer, don't show it in the other layer.

stevage avatar Dec 13 '23 02:12 stevage

@stevage The layer could potentially have thousands of features, so are you saying maintaining two layers is the solution?

3zzy avatar Dec 13 '23 07:12 3zzy