Search code examples
javascriptd3.jscurve

Curve tension is weird in d3 v4


When creating a cardinal curve in d3 v3, I would use tension(.0) to create a certain curve. However, in d3 v4, the tension seems to have been changed. To get the same curve, I need to use tension(-1.3). This shouldn't even work, since the tension should be between 0 and 1.

Fiddle for the v3: https://jsfiddle.net/diogoscf/5st4wk7c/

Fiddle for the v4 that should work but doesn't: https://jsfiddle.net/diogoscf/3xma5wxu/

Fiddle for the v4 that shouldn't work but does: https://jsfiddle.net/diogoscf/3xma5wxu/2/

Is this a bug in d3 v4? I don't want to exploit bugs since they could get patched and break my code, but this is the only way it seems to work. If there's another way, please inform.


Solution

  • It seems to me that the buggy version is the v3 one, not v4.

    If you read the changelog, you'll see that Bostock says:

    4.0 fixes the interpretation of the cardinal spline tension parameter, which is now specified as cardinal.tension and defaults to zero for a uniform Catmull–Rom spline. (emphasis mine)

    We can easily see it in this demo. I'm using your code, drawing two paths. One of them, in yellow, uses d3.curveCardinal.tension(.0):

    var line = d3.line()
      .curve(d3.curveCardinal.tension(0));
    

    In front of it I'm drawing other one, in blue, that uses d3.curveCatmullRom:

    var line2 = d3.line()
      .curve(d3.curveCatmullRom.alpha(0));
    

    As you can see, they are the same (I put red circles on your points):

    var line = d3.line()
      .curve(d3.curveCardinal.tension(0));
    
    var line2 = d3.line()
      .curve(d3.curveCatmullRom.alpha(0));
    
    var svg = d3.select("svg")
      .append("g")
      .attr("transform", "translate(20,0)");
    
    var points = [
      [0, 80],
      [100, 100],
      [200, 30],
    ];
    
    svg.append("path")
      .datum(points)
      .attr("d", line)
      .style("stroke", "yellow")
      .style("stroke-width", "6")
      .style("fill", "none");
    
    svg.append("path")
      .datum(points)
      .attr("d", line2)
      .style("stroke", "blue")
      .style("stroke-width", "2")
      .style("fill", "none");
    
    svg.selectAll(null)
      .data(points)
      .enter()
      .append("circle")
      .attr("r", 5)
      .attr("fill", "red")
      .attr("cx", d => d[0])
      .attr("cy", d => d[1]);
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <svg></svg>

    Therefore, @Marcell's explanation is correct.

    PS: do not use transparent for the fill, use none instead.