All you need is:
svg
.append('svg:defs')
.append('svg:marker')
.attr('id', 'triangle')
.attr('refX', 6)
.attr('refY', 6)
.attr('markerWidth', 30)
.attr('markerHeight', 30)
.attr('markerUnits', 'userSpaceOnUse')
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 0 0 12 6 0 12 3 6')
.style('fill', 'black');
Next:
const pathNodes = svg
.append('path')
.datum(path)
.attr('d', line)
.attr('fill', 'none')
.attr('stroke-width', '3')
.attr('stroke', (d) => d.color)
.attr('marker-end', 'url(#triangle)');
Or with animation in my case:
const arrow = svg
.append('svg:path')
.attr('d', 'M 0 0 12 6 0 12 3 6')
.attr('fill', 'black');
arrow
.transition()
.duration(2000)
.delay(1500)
.ease(d3.easeLinear)
.attrTween('transform', this.arrowAnimation(pathNodes.node()));
arrowAnimation(path) => {
const l = path.getTotalLength();
let prevX = 0;
let prevY = 0;
return (d, i, a) => {
return (t) => {
const p = path.getPointAtLength(t * l);
const deltaY = prevY - p.y;
const deltaX = prevX - p.x;
prevY = p.y;
prevX = p.x;
let rotTran;
if (deltaY === 0) {
if (deltaX > 0) {
rotTran = 'rotate(-180)';
} else {
rotTran = 'rotate(180)';
}
} else if (deltaX === 0) {
if (deltaY > 0) {
rotTran = 'rotate(-90)';
} else {
rotTran = 'rotate(90)';
}
} else {
rotTran = 'rotate(0)';
}
return 'translate(' + p.x + ',' + p.y + ') ' + rotTran;
};
};
}