mapbox-gl-js
mapbox-gl-js copied to clipboard
Support for animation/transitions when setting data-driven paint properties
Up until support arrived for data driven fill-color I generated multiple layers and styled them separately to achieve color reliefs. The data driven approach has allowed me to go from hundreds of layers to one, which is great!
One visual effect that I lost, however, is that when I used to change color on my previous individual layers with discrete fill-color values I got a subtle fading effect, which was quite nice.
Now, when updating the data-driven property with new stops/colors, the change is much more disruptive and some polygons are even non-visible for a short instant.
I understand that transitioning from one data-driven setting to another is a bit more complicated than doing so between two discrete values. I believe, however, that this should be possible in principle.
Have you discussed having data-driven properties transition smoothly as well?
@averas I had the same question a few days back-- turns out we don't support transitions in DDS properties yet. Good feature enhancement down the road, though
cc/ @lucaswoj
How can one implement this feature?
I understand that the transition happen smoothly because updating the layer color happens directly on main thread, the source doesn't get reloaded (the feature constant stuff in the update method).
Changing the fill-color stops is expensive because the whole source gets reloaded. The change of color is actually happening on worker and not main thread. Can anybody give some guidelines to implement this, I might make PR if I come up with something?
@mourner
@sanjin-saric To implement this feature one would need to modify GL JS such that
- it retains old vertex buffers during a paint transition
- it binds both the new and old vertex buffers while rendering the paint transition
- it interpolates between the new and old vertex buffer values while rendering the paint transition
We would be very grateful for a PR that implements this feature. I suggest starting with an issue labeled starter task to get the hang of the codebase and PR workflow before diving in.
@lucaswoj I am quite familiar with the codebase. Thats not a problem.
I have an issue with these steps. Have you considered performance side of things?
Imagine a scenario:
- I change the colors -> 2. the request goes to workers so the new bucket is calculated (while old one is retained) -> 3. old and new vertex buffers return to main thread -> 4. drawFill uses those buckets to transition between colors.
If there are a lot of features, there could be a considerable amount of time until step 4. happens. Wouldn't that result in laggy transition?
Correct. This approach necessitates delay while buffers are populated before the transition can begin.
Well isn't this an issue, wouldn't this be bad for user experience? Should't we consider another approach?
Do you have another approach in mind @sanjin-saric?
It may be possible to do faster data-driven paint property changes if we switch our buffers from containing presentational data to containing feature property data. (This is implicitly being considered in https://github.com/mapbox/mapbox-gl-style-spec/issues/47). This is will be a big project.
If I were looking to implement this functionality, I would use the approach outlined in https://github.com/mapbox/mapbox-gl-js/issues/3170#issuecomment-270465042 and then invest some energy in optimizing the buffer creation codepth.
@lucaswoj I've been digging around this:
populatePaintArray(layer, paintArray, length, globalProperties, featureProperties) {
const start = paintArray.length;
paintArray.resize(length);
for (const attribute of this.attributes) {
const value = getPaintAttributeValue(attribute, layer, globalProperties, featureProperties);
for (let i = start; i < length; i++) {
const vertex = paintArray.get(i);
if (attribute.components === 4) {
for (let c = 0; c < 4; c++) {
vertex[attribute.name + c] = value[c] * attribute.multiplier;
}
} else {
vertex[attribute.name] = value * attribute.multiplier;
}
}
}
}
Figured out this is where the magic happens.
I have a feeling that the "buffer containing feature data" could solve our problems.
Lets take a common situation. Lets say we have data-driven map like this one:

And we want smooth transitions to say some blue shade. The key thing is that the map data stays the same. This fact we can use. Since the features don't change, there is no logical reason why the order of colors in paintArray shouldn't be the same.
I am thinking that if we store the info how we added color to this paintArray, we might be able to make transitions between one color stop definition to another.
Maybe I am misunderstanding the code, correct my logic if I'm wrong.
Your understanding of the code is correct!
I am thinking that if we store the info how we added color to this paintArray, we might be able to make transitions between one color stop definition to another.
Doing this elegantly and performantly is the crux of the problem. I encourage you to take a swing at it.
Adding additional 👍 to this ticket with the JSON client data-join example now live. Users exploiting DDS for data-joins for data visualization will want smooth transitions to show change over time (or other dimensions).
cc @kronick
I started doing this task. I will have to make a PR here as well as mapbox-gl-style-spec repository. I am experimenting with the categorical function for now, it will have to return both color and the value of the stop. Currently, I am trying to find a way to store stop value in buffers.
@sanjin-saric did you make any progress on this DDS-transition feature? Please link to your PR if so.
Hey, so two years later transitions still don't work on data-driven properties?
@mayteio hey, if you think this is so easy to implement, please welcome to do so and submit a PR.
Did anybody consider merging mapbox-animate back into the core? The animation performance is superior!
In the absence of a fix here, some documentation would be great - spent a long time trying to debug why my DDS weren't transitioning.
Hi, I'm new to mapbox and I'm trying to animate the opacities of a cloropeth. I use a match expression that make each polygon to have a given opacity. But depending on user navigation and data change, opacities change, and want smooth transitions for each polygon. So I was trying to update the match expression each time the user changes the selection, but the changes is without transition.
I'm migrating from a d3 svg where I had this done easily. But for what I'm seeing here, in mapbox this is not easy or even impossible.
Is that right?
thanks in advance
@carlosrovira right. in my understanding, the challenges of transitioning expression updates are similar to those for DDS updates in this thread so far.
But as a user, I've found some handy workarounds for situations like yours, by finding ways to hardcode the style value so that they could be transitioned. Jotting them here for anyone struggling with transitions:
-
Separate fill layer for each polygon: compute the desired opacity for every polygon, and hardcode that for every layer, on every update. Works best if you have fewish (<50?) polygons.
-
Separate fill layer for each "opacity scenario": determine all possible permutations of your user navigation/data changes, and add a fill layer for each that contains all polygons, and style them with expressions. When updates are needed, fade in/out whole layers instead of updating any expression.
Many thanks @peterqliu, I was considering just that, to create a layer per polygon, but I have more around 60 polygons on screen in average, so maybe the performance would be not good. Also the implementation seems to me a bit complex too. Maybe second option could be better in my case, I must to evaluate...
I think this is something very common, so it seems to me strange that mapbox doesn't put some effort on this over the years. It's my first approach to the platform and I think smooth transitions is for me essential even more coming from d3 or similar frameworks.
There's no intention to solve this problem in the near future?
Thanks in advance
Looks like this is definitely a non-trivial problem 😆
Here is my use case which may be slightly different/much simpler than the OP problem but is similar in that I am changing the data on a tab-stopped fill layer and was wondering why the colour change didn't transition as per fill-color-transition property.
This thread shows why I guess...
Not sure what the current workaround is, but thought about setting a layer for the one poly (out of 470) that is transitioning
jQuery.ajax({
url: jsObject.mirror_url + 'tiles/world/flat_states/' + file,
dataType: 'json',
data: null,
cache: true,
beforeSend: function (xhr) {
if (xhr.overrideMimeType) {
xhr.overrideMimeType("application/json");
}
}
})
.done(function (geojson) {
jQuery.each(geojson.features, function (i, v) {
if (typeof jsObject.grid_data.data[v.id] !== 'undefined' ) {
geojson.features[i].properties.value = jsObject.grid_data.data[v.id]
} else {
geojson.features[i].properties.value = 0
}
})
window.map.getSource(i.toString()).setData(geojson);
})
where the geojson contains about 470 polys, and only one of these is expected to change it's property value
The paint property is
{
'fill-color': {
property: 'value',
stops: [[0, red], [1, green]]
},
'fill-opacity': 0.75,
'fill-outline-color': 'black'
}