Home > Software engineering >  D3 path fill is giving weird result
D3 path fill is giving weird result

Time:11-27

Tried to draw a sinusoid with positive and negative parts in different color using path element and fill stroke but got a weird/confusing output.

  • With 99 points enter image description here
  • With 399 points enter image description here

Here's the D3 code:

window.onload = function(){

    let xy_values =[];
    let xy_values2 =[];
    
    let len = 99; //Number of points
    let dt = 8*Math.PI/len; //X-Distance between points
    
    for (var i =0; i<len; i  ){
        
        let valx = i*dt;
        let valy = Math.sin(valx);
        
        if(valy > 0){ 
            // Positive sine
            xy_values.push( {key: valx, value: valy} );
            xy_values2.push( {key: valx, value: 0.0} );
        }else{
            //Negative sine
            xy_values.push( {key: valx, value: 0.0} );
            xy_values2.push( {key: valx, value: valy} );
        }
        
    }
    
    draw(xy_values, xy_values2);
    
    }
    
function draw(cdata, ddata){
    
    var height=200;
    var width=800;
    
    let svg=d3.select("#container").append("svg").attr("width", width).attr("height", height);
    
    walkX = d3.scaleLinear()
        .domain([0, 30])
        .range([40, width ])
    
    walkY = d3.scaleLinear()
        .domain([-1, 1])
        .range([height-10, 10])
    

    
    // Add the green regions
    svg.append("path")
        .datum(cdata)
        .attr("fill", "#BFB")
        .attr("stroke", "none")
        .attr("stroke-width", 1.5)
        .attr("d", d3.line()
            .x(function(d) { return walkX(d.key) })
            .y(function(d) { return walkY(d.value) })
        );
    
    // Add the red regions    
    svg.append("path")
        .datum(ddata)
        .attr("fill", "#FBB")
        .attr("stroke", "none")
        .attr("stroke-width", 1.5)
        .attr("d", d3.line()
            .x(function(d) { return walkX(d.key) })
            .y(function(d) { return walkY(d.value) })
        );
        
        
    // Add scatter points
    svg.append('g')
    .selectAll("dot")
    .data(ddata)
    .enter()
    .append("circle")
      .attr("cx", function (d) { return walkX(d.key); } )
      .attr("cy", function (d) { return walkY(d.value); } )
      .attr("r", 1.5)
      .style("fill", "#69b3a2")
        
        
    svg.append("g")
        .attr("transform", "translate("  0   ","   10   ")")
        .call(d3.axisBottom(walkX));
        
    svg.append("g")
        .attr("transform", "translate("  0   ","   (height-10)   ")")
        .call(d3.axisBottom(walkX));
    
    svg.append("g")
        .attr("transform", "translate("  0   ","   (height/2)   ")")
        .call(d3.axisBottom(walkX));
    
    svg.append("g")
        .attr("transform", "translate("   35  ","  0  ")")
        .call(d3.axisLeft(walkY));
    
}

Questions

What's causing that incorrect filling/ Tilted filling ?
Is this because of some precision issue ? or is it a glitch in the way fill is implemented in D3 ?

CodePudding user response:

Is this because of some precision issue ? or is it a glitch in the way fill is implemented in D3 ?

No, D3 is only used to create your SVG elements in the DOM not render them, the issue is how SVG fills are applied. When applying a fill to a path element, the fill connects the last point of the path with the first, which is why you see that effect. You'll see that the effect you are trying to remove forms line between these two points. The browser in rendering the SVG does not have any instruction to give the fill a different shape.

One option to fix this is to use an area generator rather than a line generator which allows the option to set a baseline.

The D3 area generator creates a path with two y values for every x value (specifying an area to be filled, rather than a path), in this case one at a baseline (y0) and one at a data value (y1):

        d3.area()
        .x(function(d) { return walkX(d.key) })
        .y1(function(d) { return walkY(d.value) })
        .y0(function() { return walkY(0) })

Show code snippet

window.onload = function(){

    let xy_values =[];
    let xy_values2 =[];
    
    let len = 99; //Number of points
    let dt = 8*Math.PI/len; //X-Distance between points
    
    for (var i =0; i<len; i  ){
        
        let valx = i*dt;
        let valy = Math.sin(valx);
        
        if(valy > 0){ 
            // Positive sine
            xy_values.push( {key: valx, value: valy} );
            xy_values2.push( {key: valx, value: 0.0} );
        }else{
            //Negative sine
            xy_values.push( {key: valx, value: 0.0} );
            xy_values2.push( {key: valx, value: valy} );
        }
        
    }
    
    draw(xy_values, xy_values2);
    
    }
    
function draw(cdata, ddata){
    
    var height=200;
    var width=800;
    
    let svg=d3.select("#container").append("svg").attr("width", width).attr("height", height);
    
    walkX = d3.scaleLinear()
        .domain([0, 30])
        .range([40, width ])
    
    walkY = d3.scaleLinear()
        .domain([-1, 1])
        .range([height-10, 10])
    

    
    // Add the green regions
    svg.append("path")
        .datum(cdata)
        .attr("fill", "#BFB")
        .attr("stroke", "none")
        .attr("stroke-width", 1.5)
        .attr("d", d3.area()
            .x(function(d) { return walkX(d.key) })
            .y1(function(d) { return walkY(d.value) })
            .y0(function() { return walkY(0) })
        );
    
    // Add the red regions    
    svg.append("path")
        .datum(ddata)
        .attr("fill", "#FBB")
        .attr("stroke", "none")
        .attr("stroke-width", 1.5)
        .attr("d", d3.area()
            .x(function(d) { return walkX(d.key) })
            .y1(function(d) { return walkY(d.value) })
            .y0(function() { return walkY(0) })
        );
        
        
    // Add scatter points
    svg.append('g')
    .selectAll("dot")
    .data(ddata)
    .enter()
    .append("circle")
      .attr("cx", function (d) { return walkX(d.key); } )
      .attr("cy", function (d) { return walkY(d.value); } )
      .attr("r", 1.5)
      .style("fill", "#69b3a2")
        
        
    svg.append("g")
        .attr("transform", "translate("  0   ","   10   ")")
        .call(d3.axisBottom(walkX));
        
    svg.append("g")
        .attr("transform", "translate("  0   ","   (height-10)   ")")
        .call(d3.axisBottom(walkX));
    
    svg.append("g")
        .attr("transform", "translate("  0   ","   (height/2)   ")")
        .call(d3.axisBottom(walkX));
    
    svg.append("g")
        .attr("transform", "translate("   35  ","  0  ")")
        .call(d3.axisLeft(walkY));
    
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.1.0/d3.min.js"></script>
<div id="container"></div>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

Which gives us:

enter image description here

  • Related