score:2

Accepted answer

As you already know, the use of d3.easeElasticIn in your specific code will create negative values for the rectangles' width, which is not allowed.

This basic demo reproduces the issue, the console (your browser's console, not the snippet's console) is populated with error messages, like this:

Error: Invalid negative value for attribute width="-85.90933910798789"

Have a look:

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

const margin = 50;

const line = svg.append("line")
  .attr("x1", margin)
  .attr("x2", margin)
  .attr("y1", 0)
  .attr("y2", 150)
  .style("stroke", "black")

const data = d3.range(10).map(function(d) {
  return {
    y: "bar" + d,
    x: Math.random()
  }
});

const yScale = d3.scaleBand()
  .domain(data.map(function(d) {
    return d.y
  }))
  .range([0, 150])
  .padding(0.2);

const xScale = d3.scaleLinear()
  .range([margin, 300]);

const bars = svg.selectAll(null)
  .data(data)
  .enter()
  .append("rect")
  .attr("x", margin)
  .attr("width", 0)
  .style("fill", "steelblue")
  .attr("y", function(d) {
    return yScale(d.y)
  })
  .attr("height", yScale.bandwidth())
  .transition()
  .duration(2000)
  .ease(d3.easeElasticIn)
  .attr("width", function(d) {
    return xScale(d.x) - margin
  })
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>

So, what's the solution?

One of them is catching those negative values as they are generated and, then, moving the rectangle to the left (using the x attribute) and converting those negative numbers to positive ones.

For that to work, we'll have to use attrTween instead of attr in the transition selection.

Like this:

.attrTween("width", function(d) {
    return function(t){
        return Math.abs(xScale(d.x) * t);
    };
})
.attrTween("x", function(d) {
    return function(t){
        return xScale(d.x) * t < 0 ? margin + xScale(d.x) * t : margin;
    };
})

In the snippet above, margin is just a margin that I created so you can see the bars going to the left of the axis.

And here is the demo:

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

const margin = 100;

const line = svg.append("line")
  .attr("x1", margin)
  .attr("x2", margin)
  .attr("y1", 0)
  .attr("y2", 150)
  .style("stroke", "black")

const data = d3.range(10).map(function(d) {
  return {
    y: "bar" + d,
    x: Math.random()
  }
});

const yScale = d3.scaleBand()
  .domain(data.map(function(d) {
    return d.y
  }))
  .range([0, 150])
  .padding(0.2);

const xScale = d3.scaleLinear()
  .range([0, 300 - margin]);

const bars = svg.selectAll(null)
  .data(data)
  .enter()
  .append("rect")
  .attr("x", margin)
  .attr("width", 0)
  .style("fill", "steelblue")
  .attr("y", function(d) {
    return yScale(d.y)
  })
  .attr("height", yScale.bandwidth())
  .transition()
  .duration(2000)
  .ease(d3.easeElasticIn)
  .attrTween("width", function(d) {
    return function(t) {
      return Math.abs(xScale(d.x) * t);
    };
  })
  .attrTween("x", function(d) {
    return function(t) {
      return xScale(d.x) * t < 0 ? margin + xScale(d.x) * t : margin;
    };
  })
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>


Related Query

More Query from same tag