Search code examples
d3.jscurvesmoothing

How to smooth two curves at joint point in d3.js v4?


I solved my initial goal to have the last point to be joint by a dashed line, while the rest of the curve is joint with solid line (see the image).

But, by doing that as follows, I lost the smooth of the curve. How would you address this?enter image description here

In addition, what to do for having a style supersedes a class like it does in HTML5? (the circle has a red stroke as a style attribute but win the class it has a blue one).

    // curve
    self.svg.append("path")
    .datum(function() {return data.slice(0,data.length-1);})
        .attr("stroke", colors(i)) 
        .attr("class", "line ")
        .attr("d", d3.line()
            .curve(d3.curveCatmullRom)
            .x(function(d) { return self.xSales(d.period) + self.xSales.bandwidth()/2;})
            .y(function(d) { return self.ySales(v(d)); })
        );

    self.svg.append("path")
    .datum(function() {return data.slice(data.length-2, data.length);})
        .attr("stroke", colors(i)) 
        .attr("class", "line currentPeriod")
        .attr("d", d3.line()
            .curve(d3.curveCatmullRom)
            .x(function(d) { return self.xSales(d.period) + self.xSales.bandwidth()/2;})
            .y(function(d) { return self.ySales(v(d)); })
        );

Solution

  • With regards to my comment:

    For your first question, you'll need to generate a single path and use dash-offset and a gradient coloring.

    I provided a pretty good answer here about how to dash a section of single path but it doesn't provide the colors. So I've updated below for that:

    <!DOCTYPE html>
    <meta charset="utf-8">
    <style>
      body {
        font: 10px sans-serif;
      }
      
      .axis path,
      .axis line {
        fill: none;
        stroke: #000;
        shape-rendering: crispEdges;
      }
      
      .x.axis path {
        display: none;
      }
      
      .line {
        fill: none;
        stroke-width: 1.5px;
      }
    </style>
    
    <body>
      <script src="//d3js.org/d3.v3.min.js"></script>
      <script>
        var margin = {
            top: 20,
            right: 20,
            bottom: 30,
            left: 50
          },
          width = 960 - margin.left - margin.right,
          height = 500 - margin.top - margin.bottom;
    
        var data = d3.range(11).map(function(d, i) {
          return {
            x: i,
            y: Math.random() * 100
          };
        });
    
        var x = d3.scale.linear()
          .range([0, width])
          .domain([0, 10]);
    
        var y = d3.scale.linear()
          .range([height, 0])
          .domain([0, 100]);
    
        var xAxis = d3.svg.axis()
          .scale(x)
          .orient("bottom");
    
        var yAxis = d3.svg.axis()
          .scale(y)
          .orient("left");
    
        var line = d3.svg.line()
          .x(function(d) {
            return x(d.x);
          })
          .y(function(d) {
            return y(d.y);
          })
          .interpolate("basis");
    
        var svg = d3.select("body").append("svg")
          .attr("width", width + margin.left + margin.right)
          .attr("height", height + margin.top + margin.bottom)
          .append("g")
          .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
          
        var lG = svg.append("defs")
          .append("linearGradient")
          .attr("id", "lineColor")
          .attr("x1", "0%")
          .attr("x2", "100%")
          .attr("y1", "0%")
          .attr("y2", "0%");
        
        lG.append("stop")
          .attr("offset", "0")
          .attr("stop-color", "steelblue");
          
        svg.append("g")
          .attr("class", "x axis")
          .attr("transform", "translate(0," + height + ")")
          .call(xAxis);
    
        svg.append("g")
          .attr("class", "y axis")
          .call(yAxis)
    
        var p = svg.append("path")
          .datum(data)
          .attr("class", "line")
          .attr("stroke", "url(#lineColor)")
          .attr("d", line);
    
        // draw dashed from 2.7 to 7 in the X domain
        var dashBetweenX = [2.5, 7]
        path = p.node(),
          totalLen = path.getTotalLength();
    
        // find the corresponding line lengths
        var dashBetweenL = dashBetweenX.map(function(d, i) {
    
          var beginning = 0,
            end = totalLen,
            target = null,
            d = x(d);
    
          // find the line lengths the correspond to our X values
          // stolen from @duopixel from http://bl.ocks.org/duopixel/3824661
          while (true) {
            target = Math.floor((beginning + end) / 2);
            pos = path.getPointAtLength(target);
            if ((target === end || target === beginning) && pos.x !== d) {
              break;
            }
            if (pos.x > d) end = target;
            else if (pos.x < d) beginning = target;
            else break; //position found
          }
    
          return target;
        })
    
        var sd =  dashBetweenL[0],
            dp = dashBetweenL[0],
            count = 0;
        while (dp < dashBetweenL[1]) {
          count++;
          dp += 4;
          sd += ", 4";
        }
    
        if (count % 2 == 0) {
          sd += ", 4, " + (totalLen - dashBetweenL[1]);
        } else {
          sd += ", " + (totalLen - dashBetweenL[1]);
        }
        p.attr("stroke-dasharray", sd);
        
        lG.append("stop")
          .attr("offset", dashBetweenX[0] / x.domain()[1])
          .attr("stop-color", "steelblue");
        
        lG.append("stop")
          .attr("offset", dashBetweenX[0] / x.domain()[1] + 0.001)
          .attr("stop-color", "red");
          
        lG.append("stop")
          .attr("offset", dashBetweenX[1] / x.domain()[1])
          .attr("stop-color", "red");
          
        lG.append("stop")
          .attr("offset", dashBetweenX[1] / x.domain()[1] + 0.001)
          .attr("stop-color", "steelblue");
          
        lG.append("stop")
          .attr("offset", '1')
          .attr("stop-color", "steelblue");
        
      </script>
    </body>
    
    </html>