ivanross
ivanross

Reputation: 103

Why <marker> doesn't orientate as the <path>

I'm trying to create a curved arrow with svg. I'm using d3.line() to generate the path.

let points = [
    [400,100],
    [450,200],
    [350,200],
    [385,275]
] 
let path = d3.line().curve(d3.curveCardinal)(points)

console.log(path)
// -> M400,100C400,100,458.3333333333333,183.33333333333334,450,200C441.6666666666667,216.66666666666666,360.8333333333333,187.5,350,200C339.1666666666667,212.5,385,275,385,275

But when I try to use this result in a svg:

<svg width="1200" height="1200" viewBox="0 0 1200 1200" xmlns="http://www.w3.org/2000/svg" version="1.1">
        <defs>
            <marker id="Triangle" viewBox="0 0 10 10" refX="1" refY="5" markerWidth="6" markerHeight="6" orient="auto">
                <path d="M 0 0 L 10 5 L 0 10 z" />
            </marker>
        </defs>
        <path d="M400,100C400,100,458.3333333333333,183.33333333333334,450,200C441.6666666666667,216.66666666666666,360.8333333333333,187.5,350,200C339.1666666666667,212.5,385,275,385,275"
            stroke-width="2" stroke="lightblue" fill="none" style="marker-end: url(#Triangle);"></path>
    </svg>

And here is the SVG result

.

I can't figure out why the marker doesn't orientate. Is there a better library to generate path to resolve this?

Upvotes: 4

Views: 164

Answers (3)

Gerardo Furtado
Gerardo Furtado

Reputation: 102198

That's the expected behaviour. The issue is that in a cardinal spline...

Two additional points are required on either end of the curve.

And those points seem to interfere with the marker orientation (which is indeed the case, see LeBeau's answer).

You can easily see this if you change the curve. For instance, using curveBasis:

let points = [
    [400,100],
    [450,200],
    [350,200],
    [385,275]
] 
let path = d3.line().curve(d3.curveBasis)(points)

d3.select("#myPath").attr("d", path);
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="1200" height="1200" viewBox="0 0 1200 1200" xmlns="http://www.w3.org/2000/svg" version="1.1">
    <defs>
        <marker id="Triangle" viewBox="0 0 10 10" refX="1" refY="5" markerWidth="6" markerHeight="6" orient="auto">
            <path d="M 0 0 L 10 5 L 0 10 z" />
        </marker>
    </defs>
    <path id="myPath" stroke-width="2" stroke="lightblue" fill="none" style="marker-end: url(#Triangle);"></path>
</svg>

In your case, a solution (arguably a hack) may be adding a final line to the path, just 1px away from the final point:

path = path + "L387,277";

Here is the demo:

let points = [
    [400,100],
    [450,200],
    [350,200],
    [385,275]
] 
let path = d3.line().curve(d3.curveCardinal)(points)

path = path + "L387,277";

d3.select("#myPath").attr("d", path);
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="1200" height="1200" viewBox="0 0 1200 1200" xmlns="http://www.w3.org/2000/svg" version="1.1">
    <defs>
        <marker id="Triangle" viewBox="0 0 10 10" refX="1" refY="5" markerWidth="6" markerHeight="6" orient="auto">
            <path d="M 0 0 L 10 5 L 0 10 z" />
        </marker>
    </defs>
    <path id="myPath" stroke-width="2" stroke="lightblue" fill="none" style="marker-end: url(#Triangle);"></path>
</svg>

Upvotes: 4

ibrahim tanyalcin
ibrahim tanyalcin

Reputation: 6501

  • First, the ref attributes are sort of correct but can be better I think, make the refX 0 since you using the full viewBox.

  • I think the marker's orientation is correct and updated. But based on the ending of the path, the interpolation of the orientation might look incorrect. So you can verify this behavior by cutting your pathstring from the last C... curve and will see that the orientation is correct.

  • I further tested it to see if it is correct, at least for line segments, here is a fiddle and i didn't even use d3:

https://jsfiddle.net/ibowankenobi/L8x19rco/2/

var path = document.querySelector("path[stroke]");
var arr = Array.apply(null,Array(path.getTotalLength()/4 << 0)).map(function(d,i){
    var p = this.getPointAtLength(i*4);
  return [p.x,p.y];
},path);
var length = arr.length;
animate();

function animate(index){
    if(index >= length){
    return;
  }
    var index = index || 0;
  path.setAttribute("d","M"+arr.slice(1,Math.min(++index+1,length)).join("L"));
    window.requestAnimationFrame(function(){animate(index);});
}

Upvotes: 0

Paul LeBeau
Paul LeBeau

Reputation: 101868

This is because the last control point and the end-point of your path have the same coordinates: (385,275).

SVG uses the control point vector to work out what the curve direction is at that point. If your control point vector is from (385,275) to (385,275), then it can't determine the angle. So it defaults to an angle of 0 degrees.

Upvotes: 3

Related Questions