Support rgba interpolation #582

Closed
jasondavies opened this Issue Mar 5, 2012 · 10 comments

Comments

6 participants
@jasondavies
Collaborator

jasondavies commented Mar 5, 2012

The default string interpolation results in floating-point values; it might be simple enough to extend d3.rgb to handle an alpha component too.

The spec mentions that integers should be used.

See also: this thread.

@mbostock

This comment has been minimized.

Show comment Hide comment
@mbostock

mbostock Jul 31, 2012

Owner

Since RGBA isn't widely supported, I think it's probably not worth adding alpha channels to all colors in D3. Seems like it would add baggage (e.g., CIE Lab* would need to have an alpha channel, too).

Owner

mbostock commented Jul 31, 2012

Since RGBA isn't widely supported, I think it's probably not worth adding alpha channels to all colors in D3. Seems like it would add baggage (e.g., CIE Lab* would need to have an alpha channel, too).

@mbostock mbostock closed this Jul 31, 2012

@jasondavies

This comment has been minimized.

Show comment Hide comment
@jasondavies

jasondavies Jul 31, 2012

Collaborator

Makes sense.

Collaborator

jasondavies commented Jul 31, 2012

Makes sense.

@dcleao

This comment has been minimized.

Show comment Hide comment
@dcleao

dcleao Feb 13, 2014

I'm thinking about the way that protovis handled colors.
Because colors had an alpha channel, color scales made up of colors with transparencies were supported.

In a fork of protovis, we, at webdetails, also generalized the color concept to include gradient specifications. This allowed defining color scales made up of gradients. This later use case could also be achieved assuming some gradient template, applied to a base color at a later stage. The most important thing here is that the color concept was generalized and that a css gradient spec. string can always be passed to where a "color" is expected.

In my view, the fact that SVG does not support RGBA directly (and gradients) is not the issue here, but that of being able to use d3's color scales for these kinds of colors.

While rgba interpolation would be feasible in d3 (I think), I recognize that gradient interpolation is a hard to conceive thing. It wouldn't have to be supported though.

Do you recognize any value in this approach to color scales (and color concept)?
Do you see any way that the d3 color concept could be generalized, and implemented in some extensible way, without adding "baggage" to all color classes?

dcleao commented Feb 13, 2014

I'm thinking about the way that protovis handled colors.
Because colors had an alpha channel, color scales made up of colors with transparencies were supported.

In a fork of protovis, we, at webdetails, also generalized the color concept to include gradient specifications. This allowed defining color scales made up of gradients. This later use case could also be achieved assuming some gradient template, applied to a base color at a later stage. The most important thing here is that the color concept was generalized and that a css gradient spec. string can always be passed to where a "color" is expected.

In my view, the fact that SVG does not support RGBA directly (and gradients) is not the issue here, but that of being able to use d3's color scales for these kinds of colors.

While rgba interpolation would be feasible in d3 (I think), I recognize that gradient interpolation is a hard to conceive thing. It wouldn't have to be supported though.

Do you recognize any value in this approach to color scales (and color concept)?
Do you see any way that the d3 color concept could be generalized, and implemented in some extensible way, without adding "baggage" to all color classes?

@dcleao

This comment has been minimized.

Show comment Hide comment
@dcleao

dcleao Mar 3, 2014

@mbostock could you please comment on this?

dcleao commented Mar 3, 2014

@mbostock could you please comment on this?

@mbostock

This comment has been minimized.

Show comment Hide comment
@mbostock

mbostock Mar 3, 2014

Owner

It’s already the case that you can have scales that interpolate multiple things simultaneously by using the default interpolator, which can defer to one of interpolateObject or interpolateArray. For example:

var x = d3.scale.linear().range([{color: "red", opacity: .1}, {color: "green", opacity: 1}]);
x(.5); // {color: "#804000", opacity: 0.55}

You can interpolate directly, too, of course:

var i = d3.interpolate({color: "red", opacity: .1}, {color: "green", opacity: 1});
i(.5); // {color: "#804000", opacity: 0.55}

You can even almost use interpolateString, except for the fact that it returns floating point values that aren’t considered valid rgba colors:

var x = d3.scale.linear().range(["rgba(255,0,0,.1)", "rgba(0,255,0,1)"]);
x(.5); // "rgba(127.5,127.5,0,0.55)"

Likewise you can just interpolate arbitrary objects representing RGBA colors:

var i = d3.interpolate({r: 255, g: 0, b: 0, a: .1}, {r: 0, g: 0, b: 255, a: 1});
i(.5); // {r: 127.5, g: 0, b: 127.5, a: 0.55}

You could cast the object to d3.rgb to take care of the rounding:

var rgba = {r: 127.5, g: 0, b: 127.5, a: 0.55},
    rgb = d3.rgb(rgba.r, rgba.g, rgba.b),
    opacity = rgba.a; // "#7f007f"
rgb + "";

One downside of these approaches is that it’s less obvious how you control the interpolation color space, since d3.interpolate uses d3.interpolateRgb by default for color interpolation. However, you can add your own interpolator to d3.interpolators and change it to do whatever you want, say adding support for HCL color space interpolator.

For that matter you could implement your own rgba color interpolator and add that to d3.interpolators, too.

Anyway, my point is it feels like there are lots of ways to do this already, and so I don’t feel like there’s a need to muddle the concept of a color with opacity.

Owner

mbostock commented Mar 3, 2014

It’s already the case that you can have scales that interpolate multiple things simultaneously by using the default interpolator, which can defer to one of interpolateObject or interpolateArray. For example:

var x = d3.scale.linear().range([{color: "red", opacity: .1}, {color: "green", opacity: 1}]);
x(.5); // {color: "#804000", opacity: 0.55}

You can interpolate directly, too, of course:

var i = d3.interpolate({color: "red", opacity: .1}, {color: "green", opacity: 1});
i(.5); // {color: "#804000", opacity: 0.55}

You can even almost use interpolateString, except for the fact that it returns floating point values that aren’t considered valid rgba colors:

var x = d3.scale.linear().range(["rgba(255,0,0,.1)", "rgba(0,255,0,1)"]);
x(.5); // "rgba(127.5,127.5,0,0.55)"

Likewise you can just interpolate arbitrary objects representing RGBA colors:

var i = d3.interpolate({r: 255, g: 0, b: 0, a: .1}, {r: 0, g: 0, b: 255, a: 1});
i(.5); // {r: 127.5, g: 0, b: 127.5, a: 0.55}

You could cast the object to d3.rgb to take care of the rounding:

var rgba = {r: 127.5, g: 0, b: 127.5, a: 0.55},
    rgb = d3.rgb(rgba.r, rgba.g, rgba.b),
    opacity = rgba.a; // "#7f007f"
rgb + "";

One downside of these approaches is that it’s less obvious how you control the interpolation color space, since d3.interpolate uses d3.interpolateRgb by default for color interpolation. However, you can add your own interpolator to d3.interpolators and change it to do whatever you want, say adding support for HCL color space interpolator.

For that matter you could implement your own rgba color interpolator and add that to d3.interpolators, too.

Anyway, my point is it feels like there are lots of ways to do this already, and so I don’t feel like there’s a need to muddle the concept of a color with opacity.

@dcleao

This comment has been minimized.

Show comment Hide comment
@dcleao

dcleao Mar 4, 2014

Thanks for the pointers and clarification!

dcleao commented Mar 4, 2014

Thanks for the pointers and clarification!

@cool-Blue

This comment has been minimized.

Show comment Hide comment
@cool-Blue

cool-Blue Apr 22, 2015

You can easily build a composite interpolator combining d3.interpolateRgb and d3.interpolate and generalize it using d3.rgb().
For example, this will handle rgba and any other format supported by d3.rgb()...

function interpolateRgba(fromRgba, toRgba) {
    var fromRgb = rgb(fromRgba), toRgb = rgb(toRgba),
          rgbInterp = d3.interpolateRgb(fromRgb.rgb, toRgb.rgb),
          aInterp = d3.interpolate(fromRgb.a, toRgb.a)

    return function (t) {
        var c = d3.rgb(rgbInterp(t)), target = toRgba
        return 'rgba(' + c.r + ', ' + c.g + ', ' + c.b + ', ' + aInterp(t) + ')'
    }
    function rgba(colString) {
        if (colString.search(/rgba/) === 0) {
            return colString
        } else {
            if (colString.search(/rgb/) === 0) {
                return colString.replace(/rgb(.*)\)/, 'rgba$1, 1.0)')
            }
        }
    }
    function rgb(colString) {
        if (colString.search(/rgba/) === 0) {
            var regExp = /rgba(.*),\s?(.*)\)/,
                   results = regExp.exec(colString)
            return {
                rgba: results[0],
                rgb: 'rgb' + results[1] + ')',
                a: results[2]
            }
        } else {
            if (colString.search(/rgb/) === 0) {
                return {
                    rgba: null,
                    rgb: colString,
                    a: 1.0
                }
            } else {
                var c = d3.rgb(colString)

                return {
                    rgba: null,
                    rgb: 'rgb(' + c.r + ',' + c.g + ',' + c.b + ')',
                    a: 1.0
                }
            }
        }
    }
}
// example...
        selection.transition().duration(1000)
            .styleTween('background-color', function (d, i, a) {
                return interpolateRgba(a, 'rgba(255, 255, 0, 0.8)')
            })
            .styleTween('color', function (d, i, a) {
                return interpolateRgba(a, 'yellow')
            })
            .styleTween('outline-color', function (d, i, a) {
                return interpolateRgba(a, '#ccc')
            })

You can easily build a composite interpolator combining d3.interpolateRgb and d3.interpolate and generalize it using d3.rgb().
For example, this will handle rgba and any other format supported by d3.rgb()...

function interpolateRgba(fromRgba, toRgba) {
    var fromRgb = rgb(fromRgba), toRgb = rgb(toRgba),
          rgbInterp = d3.interpolateRgb(fromRgb.rgb, toRgb.rgb),
          aInterp = d3.interpolate(fromRgb.a, toRgb.a)

    return function (t) {
        var c = d3.rgb(rgbInterp(t)), target = toRgba
        return 'rgba(' + c.r + ', ' + c.g + ', ' + c.b + ', ' + aInterp(t) + ')'
    }
    function rgba(colString) {
        if (colString.search(/rgba/) === 0) {
            return colString
        } else {
            if (colString.search(/rgb/) === 0) {
                return colString.replace(/rgb(.*)\)/, 'rgba$1, 1.0)')
            }
        }
    }
    function rgb(colString) {
        if (colString.search(/rgba/) === 0) {
            var regExp = /rgba(.*),\s?(.*)\)/,
                   results = regExp.exec(colString)
            return {
                rgba: results[0],
                rgb: 'rgb' + results[1] + ')',
                a: results[2]
            }
        } else {
            if (colString.search(/rgb/) === 0) {
                return {
                    rgba: null,
                    rgb: colString,
                    a: 1.0
                }
            } else {
                var c = d3.rgb(colString)

                return {
                    rgba: null,
                    rgb: 'rgb(' + c.r + ',' + c.g + ',' + c.b + ')',
                    a: 1.0
                }
            }
        }
    }
}
// example...
        selection.transition().duration(1000)
            .styleTween('background-color', function (d, i, a) {
                return interpolateRgba(a, 'rgba(255, 255, 0, 0.8)')
            })
            .styleTween('color', function (d, i, a) {
                return interpolateRgba(a, 'yellow')
            })
            .styleTween('outline-color', function (d, i, a) {
                return interpolateRgba(a, '#ccc')
            })

saraquigley added a commit to saraquigley/badges that referenced this issue Nov 5, 2015

Create layoutB.html
Interpolates the shade behind the attendee name using a function described in this d3 issue discussion: d3/d3#582
@ericsoco

This comment has been minimized.

Show comment Hide comment
@ericsoco

ericsoco Nov 17, 2015

I just stumbled across this after a half hour of debugging -- my shapes were mysteriously disappearing as soon as they started their transition, and I eventually tracked it down to this.

Is rgba widely supported enough now that this should be reconsidered? (I couldn't find a definitive answer.) Or, at the very least, perhaps d3 can warn when a style or attr containing an rgba key is transition()ed...

I just stumbled across this after a half hour of debugging -- my shapes were mysteriously disappearing as soon as they started their transition, and I eventually tracked it down to this.

Is rgba widely supported enough now that this should be reconsidered? (I couldn't find a definitive answer.) Or, at the very least, perhaps d3 can warn when a style or attr containing an rgba key is transition()ed...

@PatrickKing

This comment has been minimized.

Show comment Hide comment
@PatrickKing

PatrickKing Apr 6, 2016

This just bit me today. caniuse shows RGBA as being supported in all modern browsers, with the only notable holdout being IE8.

This just bit me today. caniuse shows RGBA as being supported in all modern browsers, with the only notable holdout being IE8.

@mbostock mbostock modified the milestones: 4.0, Icebox Apr 6, 2016

@mbostock mbostock added the req label Apr 6, 2016

@mbostock

This comment has been minimized.

Show comment Hide comment
@mbostock

mbostock Apr 6, 2016

Owner

D3 4.0 supports RGBA interpolation. See d3-color and d3-interpolate.

Owner

mbostock commented Apr 6, 2016

D3 4.0 supports RGBA interpolation. See d3-color and d3-interpolate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment