-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Updating the appearance of individual features #6020
Comments
@ansis with the above and Option 2C, does exposing the feature object also allow updating the geometry? |
I'm not sure. The mockups are just rough sketches but I think it might make sense to allow geometry updates for GeoJSON sources using a similar approach. This could be a nice way to implement things like point dragging?
Good question. I'm not sure. One option would be to allow it but to have different performance expectations for changing layout properties vs paint properties. |
I pushed a proof of concept Option 2: Making data updates cheap to data-update-proof-of-concept with an example http://localhost:9966/debug/highlight.html |
I like Option 2, it's provides a very clear separation of Style and Data. As features are hovered over the Style remains constant from Studio and the client side app just updates each features data with its state. Agreed, 2A is simpler but ultimately something generic allowing user defined states would be better. To retain the simplicity of 2A when implementing 2B/2C we could use convention in Studio and examples to set things like "hover", "active" etc. |
awesome write-up @ansis! I agree that option 2 seems like a logical extension of paradigms we've already introduced with expressions. The POC demo is 🔥 ⚡️ |
/cc @mapbox/studio |
Last we talked about this and settled on option 2. There was some concern that fast geometry updates would make the POC approach unnecessary but I think we still think that even if the geometry updates are fast for a small number of features this POC approach could be faster for more and still useful. Also, this provides a clear path to make property updates fast now. @asheemmamoowala will be working on implementing this |
What does it mean if a user updates properties on a vector tile feature? Do those updates persist across zoom levels? Will we require all custom source types to allow feature property updates? |
Yep, it would be updated for all tiles across all zoom levels and would persist even if the tile was unloaded and reloaded. In order to do this we need some way of telling what feature is the same feature: #6019
Not sure, good question |
The ongoing discussion in #6021 is converging on the idea of separating state from feature properties. Setting stateState should be tracked independently of feature data and assigned per source. This would look similar to 2B //Set a feature_id to a named state
map.setState("source", "state", feature_id);
//Clear state
map.setState("source", "state");
//Set one or more feature Ids to a named state
map.setState("source", "state", [feature_id]); States are tracked per source in the Question: Is there a need for an API to un-set a named state on a single feature or set of features , while preserving other features in that state? Using stateState can be referenced in expressions as part of layer paint properties // Increase opacity for features in the 'highlight' state
"fill-opacity": [ "case", ["state", "highlight"], ["number", 0.9], ["number", 0.5]] or queried through an API: map.getState("state");
// Where the returned object looks like:
{
"source_A" : { "highlight": [1234, ...], "dragging" : [...], ... } ,
"source_B": { "visited" : ["foo", "bar"] }
} Reacting to changed stateFor expressions, To quickly update a features paint properties, the implementation needs to:
@ansis' proof of concept branch shows most of the the plumbing needed to update paint arrays. |
I very much like the direction of this API 😄 One could imagine allowing other animation parameters to be stored in state as well for smoother animations. One consideration: If I were coming to Mapbox with fresh eyes and saw an API called |
Really glad to see this happening. Several times I've implemented a "highlight polygon on mouseover" in a map, only to remove it because it wasn't responsive enough. Couple of questions:
What is In any case, 2B looks right to me. In my most recent app, there is a "multi select" for polygons, which would be easy to implement with 2B, and probably not catered for with 2A.
Like, user selects 5 polygons, then unselects 1? Absolutely. Or do you mean, a polygon is |
@stevage Thanks for the feedback! Setting a state value to
|
Implemented by #6263 |
Motivation
What problem are we trying to solve?
Interaction often triggers changes to appearance of features. For example:
These changes are currently both slow and hard to implement. We need to make it possible to update the paint properties of of a reasonable number of features instantly.
This issue focuses on how to respond to these interaction events, not how to detect them.
Implementation Design Alternatives
Do nothing is not an option.
Option 1: Making style updates cheap
When you want to change an appearance of a feature, change the style:
These examples are close to the current approach to highlighting features. If we can make paint property changes fast enough we can use this approach to respond to interaction.
Changing these properties currently involves recreating the entire bucket in the worker. To make it fast enough we'd need to a) only recreate the relevant paint buffers, and b) do it on the main thread. Since we can't tell which features were affected by the change we have to recalculate the paint values for all of them. There might also be challenges with transitioning changes and with switching between data-driven and literal expressions.
setFilter
would be harder to optimize. We would need to either:Since neither of these are great options, we would encourage users use data-driven opacity instead.
Option 2: Making data updates cheap
Instead of constantly updating the style, specify the possible style variations as a data-driven expression that is set once and then update the data:
When creating the buckets keep track of which paint array vertices correspond to which feature ids:
There would be some kind of way to update the data of individual features. See the mockups for possible external APIs.
When the data of a feature changes, recalculate any paint properties that depend on the data that changed and write the values to the buffer using the previously stored indices. Do this within a render frame.
Upload any buffers that changed.
Implementation Design
I think we should use Option 2.
The advantages are:
The main disadvantage is:
Mockups
Option 2 would require some kind of API for updating data. Here are some rough possibilities. I could see a combination of them being useful.
Option 2A: Setting data updates internally
We could keep track of hover state, activation and selection internally. These states would be exposed within expressions using something like
["state", "hover"]
. This could be very useful for basic cases but not complex enough to handle all the possible approaches to selection, etc.Option 2B: setters
map.setDataProperty("source", feature, "hover", true);
Option 2C: feature objects
Queries and events could return feature objects that could be manipulated directly.
Concepts
What existing precedents support the new concepts?
Where do the concepts set new precedents?
This would introduce the concept of updating feature appearance by updating feature data. This builds on the concept of data-driven styling (as used in Mapbox and in d3) but takes it a step further with live updates.
How will we teach this design?
With examples showing how to use this approach to interactivity.
What terminology will work best for the new concepts introduced by this design?
I'm not sure. "feature data updating"?
Next steps
The main question here is:
@kkaefer @asheemmamoowala @mollymerp @anandthakker @mourner @lucaswoj
The text was updated successfully, but these errors were encountered: