d3 / d3-transition

Animated transitions for D3 selections.
https://d3js.org/d3-transition
ISC License
224 stars 64 forks source link

Interpolation issue when transitioning style with HEX colors #82

Closed antoinetissier closed 5 years ago

antoinetissier commented 6 years ago

Hi,

Consider the following snippet:

const svg = d3.select('svg');

const dataSet = [10, 20, 30, 40];

const circle = svg.selectAll('circle')
    .data(dataSet)
    .enter()
    .append('svg:circle')
    .attr({
        r:function(d){ return d },
        cx:function(d, i){ return i * 100 + 50 },
        cy:50,
    })
    .attr('style', d => 'fill:#00aeef;opacity:0.5')
    .transition()
    .attr('style', d => 'fill:#61c1e6;opacity:0.5');

Instead of having circles of the blue color defined by HEX string #61c1e6, I get black circles. I look at the HTML of a circle, and in the style I see that there is an unrecognized entry 'fill:#61c1000000' which is obviously not recognized.

It took me some time to figure out that actually substring 1e6 in #61c1e6 is interpreted as a number in scientific notation. This occurs because of the interpolator created during the call to .transition():

const interpolator = d3.interpolateString('fill:#00AEEF;opacity:0.5', 'fill:#61c1e6;opacity:0.5');
console.log(interpolator(1));
> fill:#61c1000000;opacity:0.5

In my use case I need to use .attr('style', ...) as the css is passed programatically (I do not know the style attributes beforehand). This transition issue makes it impossible to use. Is there any way I can work around this issue ?

Many thanks, Antoine

mbostock commented 5 years ago

This is the expected behavior of d3.interpolateString, which finds numbers embedded in strings. You’ll need a different interpolator if you want to apply a different strategy for parsing strings. For example:

circle.transition().attrTween("style", () => {
  const i = d3.interpolate(
    {fill: "#00aeef", opacity: 0.5},
    {fill: "#61c1e6", opacity: 0.5}
  );
  return t => {
    const styles = Object.entries(i(t));
    return styles.map(([name, value]) => `${name}: ${value};`).join("");
  };
});