Reputation: 103
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
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
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
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