mapbox-gl-js version: 0.42.0
Developers want to use GL expressions for common color operations. A common use case is to indicate feature selection on a hover or click event of a feature by changing the color saturation.
There are no color functions in Expressions that can calculate changes in colors without using a third-party library such as chroma.js or the built-in color-space interpolations in GL JS Style Spec today.
Add two new color expressions:
cc @anandthakker
Let's broaden this to cover other color operations we may want -- I think it makes sense to consider the whole set together as we decide on how to add these.
As @davidtheclark mentioned previously, the CSS color modifying spec proposal might be good inspiration: https://drafts.csswg.org/css-color/#modifying-colors
While this is getting implemented, here is the workaround expression to implement desaturate with graying, i.e. it decreases the color value and makes the color more mid gray.
"text-color": [
"let",
"rgba", ["to-rgba", ["to-color", ["get", "color_property"]]],
["let",
"r", ["number", ["*", 255, ["at", 0, ["var", "rgba"]]]],
"g", ["number", ["*", 255, ["at", 1, ["var", "rgba"]]]],
"b", ["number", ["*", 255, ["at", 2, ["var", "rgba"]]]],
"a", ["number", ["at", 3, ["var", "rgba"]]],
["let",
"avg", ["+", ["*", 0.299, ["var", "r"]], ["*", 0.587, ["var", "g"]], ["*", 0.114, ["var", "b"]]],
["let",
"desat_r", ["+", ["*", 0.4 , ["var", "avg"]], ["*", 0.4 , 128], ["*", 0.2 , ["var", "r"]]],
"desat_g", ["+", ["*", 0.4 , ["var", "avg"]], ["*", 0.4 , 128], ["*", 0.2 , ["var", "g"]]],
"desat_b", ["+", ["*", 0.4 , ["var", "avg"]], ["*", 0.4 , 128], ["*", 0.2 , ["var", "b"]]],
["rgba", ["var", "desat_r"], ["var", "desat_g"], ["var", "desat_b"], ["var", "a"]]
]
]
]
]
Woah, thanks for sharing @mkv123 !
Looking at this, I'm seeing a bug in our to-rgba expression -- I think it should return the rgb components scaled to 0-255, since that's how the rgba expression consumes them. I'll file that as a separate ticket
Looking over the color-mod spec, one translation into expressions would be something like:
["color-{r,g,b,a,h,s,l}-=", color, number] -- produces a color with the given channel set to the given value["color-{r,g,b,a,h,s,l}-+", color, number] -- produces a color with the given channel adjusted by the given amount["color-{r,g,b,a,h,s,l}-*", color, number] -- produces a color with the given channel multiplied by the given amount["color-rgb{,a}-{+,*}", color, r, g, b] -- shorthand for adding/multiplying in all channels["color-{tint,shade}", color, percentage] -- implements tint and shade["color-blend-{rgb,hsl}{,a}", color, color, n] -- implements blend and blenda["color-contrast", color, percentage?] -- implements contrastSo increasing the saturation by 10 percentage points would be ["color-s-+", color, 10].
This translation:
color-mod as far as possible, including the treatment of achromatic colors.color-r--). Add a negative value instead.An alternative model is http://lesscss.org/functions/#color-operations.
The proposal in https://github.com/mapbox/mapbox-gl-js/issues/5676#issuecomment-356800938 adds 32 new operators with five distinct syntaxes. It feels like the operator name is being turned into a mini-language, whereas the structured expression syntax exists because we didn’t want to stuff a mini-language inside a string (https://github.com/mapbox/mapbox-gl-style-spec/issues/362#issuecomment-161857532).
What if we add a single new operator that applies a “color transform” or “color filter” to a color? A color transform could take the form of an object specifying various parameters for the transformation.
I have the opposite take, @1ec5 -- stuffing all of the potential color operations inside of a single operator with an object-based parameterization system feels like the sort of meta syntax we're aiming to avoid, while extending the language with additional operators is what we explicitly designed for. (I'm not sure what you mean by "five distinct syntaxes" -- as I see it, my proposal doesn't introduce any new syntax.)
I do think that the color-mod-spec-derived names are cryptic, and would support using more familiar operator names, like "color-saturate" for "color-s-+" and "color-lighten" for "color-l-+".
@jfirebaugh: Why not pre-parse the arguments, in a sense, by making them items in the array? Instead of ["color-r-+", ...] we'd have ["color", "+", "r", ...]. Or instead of ["color-blend-rgb", ...] we'd have ["color", "blend", "rgb", ...]? (Or even ["color", ["blend", "rgb"], ...]?)
I was looking through the spec for something similar that exists and the closest I found was interpolate. There, it seems like we opted for multiple arguments instead of operators like interpolate-linear, interpolate-exponential, interpolate-cubic-bezier. I'm curious if you think there are key features that differentiate this set of expressions from those?
The major difference is that the type signature of interpolate is the same whether interpolation is linear, exponential, or cubic-bezier. (And the fact that step has a different number of arguments is one reason we split curve into step and interpolate.)
In contrast, the number and types of arguments to color operators vary. This would make a single "uber-operator" difficult to implement and document.
In https://github.com/mapbox/mapbox-gl-js/issues/5676#issuecomment-357098168, I was thinking of interpolate, specifically the first argument. It may be that the style specification views linear, exponential, and cubic-bezier as context-specific operators in their own right, but to me (and to the NSExpression translation), it’s not much different than an options argument to a function.
I just wanted to see if there has been any movement on this. I'm currently working on a visualization where the missing piece seems to be the ability to subtract saturation from HSL colors from layers beneath, and I haven't had a lot of success finding what I'm looking for in the official documentation.
@audaciouscode This is not on our roadmap at this time. We have a general interest in extending the expressions system over time but balance that against other necessary work. I'd suggest reaching out to Mapbox Support if you have a specific example as they may be able to help provide an alternate implementation.
Most helpful comment
While this is getting implemented, here is the workaround expression to implement desaturate with graying, i.e. it decreases the color value and makes the color more mid gray.