Search code examples
mathd3.jscoordinatescoordinate-systems

D3: Translating two identical paths so that they run parallel


My question is as follows:

(a) I am plotting two paths in D3 that have exactly the same coordinates. The same paths.

(b) I would now like to transform one of the paths so that they run parallel and that the 'width' between the paths is always equal to x pixels. My code is as follows:

d3.select("#path2")
.attr("transform", "translate(15,0)");

Unfortunately, the code yields the following:

enter image description here

As you can see the lines are not parallel and are intersecting. In fact it's a bit of a dog's breakfast. I have a feeling this could be alot more complicated than it sounds to achieve. Or is it? Any ideas?

Thank you all


Solution

  • I have a different approach to propose: create two identical paths, like you're doing right now. Then, if you want a distance of, for instance, 16px, set the stroke-width of one of the paths bigger than that and use the other path as an SVG mask, with a stroke-width of 16, at the very same position of the other path.

    That way, one path will make a "hollow space" in the other one, without any complicated math.

    Here is a demo. I put a circle just for showing that the path is hollow:

    var svg = d3.select("svg")
    
    var dataset = [
        [0, 30],
        [20, 30],
        [50, 55],
        [60, 70],
        [100, 120],
        [110, 90],
        [135, 121],
        [200, 70],
        [300, 130]
    ];
    
    var line = d3.line()
        .x(function(d) {
            return d[0];
        })
        .y(function(d) {
            return d[1];
        });
    
    svg.append("circle")
        .attr("cx", 200)
        .attr("cy", 70)
        .attr("r", 30)
        .attr("fill", "teal")
    
    var defs = svg.append("defs");
    
    var mask = defs.append("mask")
        .attr("id", "pathMask");
    
    mask.append("rect")
        .attr("width", 300)
        .attr("height", 150)
        .attr("fill", "white");
    
    mask.append("path")
        .attr("d", line(dataset))
        .attr("stroke", "black")
        .attr("fill", "none")
        .attr("stroke-width", 16);
    
    var path = svg.append("path")
        .attr("d", line(dataset))
        .attr("mask", "url(#pathMask)")
        .attr("stroke", "red")
        .attr("stroke-width", 20)
        .attr("fill", "none");
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <svg>
    </svg>

    If you don't need a transparent fill, the solution is even easier: draw two paths, one over the other, with different stroke-width, and fill the upper one (with the smaller stroke width) with the same colour of the background.