3
votes

I'm creating a Sankey diagram in D3 using paths as chunky connectors to indicate flow. However, I'm finding the paths that are thicker than they are long start behaving really oddly. You can see an example here, where I'm splitting the path into sections:

 image description here

The blue and orange overlap, because the blue (and also the grey behind it) don't curve in the same way as the thinner paths, they've got a kind of "kink" in them.

All the line curves work well, except these large ones. I made a simple example with just SVG:

<SVG height=800 width=800>
<g transform="translate(40,400)">
    <Path class="link1" d="M29,-129C104.5,-129 104.5,-202.125 180,-202.125" />
    <Path class="link2" d="M29,-129C104.5,-129 104.5,-202.125 180,-202.125" />
    <Path class="link3" d="M29,-129C104.5,-129 104.5,-202.125 180,-202.125" />
    <Path class="normal" d="M29,-129 L104.5,-129" />
    <Path class="normal" d="M104.5,-202 L180,-202.125" />
</g>

You can see it here:

https://jsfiddle.net/hanvyj/t91pbp4w/

I couldn't find anything on google, I was hoping someone would have come across the issue and know a fix, or has more SVG experience and know a good alternative to 'SVG:Path' that wouldn't do this?

3
Take another look at your fiddle, it needs content.Persijn
Oops, didn't save it. Updated now. Thanks.Joe
instead of making different paths-strokes; can making one path with a fill property be an alternative?maioman
this is a issue related to stroke-linecap and you'd have to redraw the curve to fix itmaioman
It's not anything to do with the linecap. It is just simple geometry.Paul LeBeau

3 Answers

3
votes

SVG stroke-width behaving differently

Problem example:

enter image description here

<svg width="800px" height="600px" viewBox="0 0 100 100">
  <path stroke-width="15" stroke="rgba(0,0,0,0.5)" fill="none" d="m0,40 c 20,0 20,-10 40,-10" />
  <path stroke-width="3" stroke="rgba(0,0,0,0.5)" fill="none" D="m40,24 c 5,0 5,-5 10,-5" />
  <path stroke-width="3" stroke="rgba(0,0,0,0.5)" fill="none" D="m40,27 c 5,0 5,-2 10,-2" />
  <path stroke-width="3" stroke="rgba(0,0,0,0.5)" fill="none" D="m40,30 10,0" />
  <path stroke-width="3" stroke="rgba(0,0,0,0.5)" fill="none" D="m40,33 c 5,0 5,2 10,2" />
  <path stroke-width="3" stroke="rgba(0,0,0,0.5)" fill="none" D="m40,36 c 5,0 5,5 10,5" />

  <path stroke-width="3" stroke="rgba(255, 200, 50, 0.7)" fill="none" d="m0,46 c 20,0 20,-10 40,-10" />
</svg>

Solution:

<svg width="800px" height="600px" viewBox="0 0 100 100">
  <path stroke-width="15" stroke="rgba(0,0,0,0.5)" fill="none" d="m0,40 c 20,0 20,-10 40,-10" />
  <path stroke-width="3" stroke="rgba(0,0,0,0.5)" fill="none" D="m40,24 c 5,0 5,-5 10,-5" />
  <path stroke-width="3" stroke="rgba(0,0,0,0.5)" fill="none" D="m40,27 c 5,0 5,-2 10,-2" />
  <path stroke-width="3" stroke="rgba(0,0,0,0.5)" fill="none" D="m40,30 10,0" />
  <path stroke-width="3" stroke="rgba(0,0,0,0.5)" fill="none" D="m40,33 c 5,0 5,2 10,2" />
  <path stroke-width="3" stroke="rgba(0,0,0,0.5)" fill="none" D="m40,36 c 5,0 5,5 10,5" />

  <path stroke-width="3" stroke="rgba(255, 200, 50, 0.7)" fill="none" d="m0,46 c 22,0 22,-10 40,-10" />
</svg>

Wait what did you do to the path?

Lets take a look at the d attribute on those orange paths.
Problem path: m0,46 c 20,0 20,-10 40,-10
Solution path: m0,46 c 22,0 22,-10 40,-10

I simply adjusted the C curve command by 2 units.

I want pictures of the differences... ohhhh wel okay:

enter image description here

enter image description here

3
votes

While Persijn's solution was good, it wasn't quite perfect - it matched the curve of the 'overaying' thinner line to the underlying kinked line. Ideally, I wanted to remove the kink altogether.

As maioman suggested, for this kind of shape it is probably best I define the full path and use fill, rather than stroke width. This gives me a lot more control. The stroke-width keeps the thickness constant throughout the lines length, that's not what I actually wanted and as people pointed out, it's impossible when the width is larger than it's length.

I wanted the height in the x axis to be constant along it's length. I created a function that produces the shape I actually want, in case anyone comes across this:

var linkPath = function(sourceTop,sourceBottom,
                targetTop,targetBottom) {
    //middle
    var MiddleDelta =(targetTop.x - sourceTop.x)/2;
    console.log(MiddleDelta);
    //start in the top left
    var d = "M";
    //top left corner
    d += sourceTop.x + ", " + sourceTop.y + " ";
    d += "C" + (sourceTop.x+MiddleDelta) + "," + sourceTop.y + " ";
    d += (targetTop.x - MiddleDelta) + "," + targetTop.y + " ";
    //top right corner
    d += "" + targetTop.x + "," + targetTop.y + " ";
    //bottom right corner
    d += "L" + targetBottom.x + "," + targetBottom.y + " ";
    d += "C" + (targetBottom.x-MiddleDelta) + "," + targetBottom.y + " ";
    d += (sourceBottom.x + MiddleDelta) + "," + sourceBottom.y + " ";
    //bottom left corner
    d += "" + sourceBottom.x + "," + sourceBottom.y + " ";
    return d;
};

It produces this:

enter image description here

as oposed to this:

enter image description here

2
votes

Your line is so thick that when it follows the bezier curve, it passes outside the starting point of the other end of the line.

Almost any non-straight bezier curve will have a limit on how thick it can get before it starts self-intersecting. You are just exceeding that limit here. If the start and end points were further apart, you would be able to get a bit thicker.