d3-path-arrows
Version:
Add arrows to your paths using CSS dash-array and SVG triangle shapes
110 lines (89 loc) • 3.57 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = pathArrows;
var _d3Selection = require("d3-selection");
var _d3Array = require("d3-array");
// Function that appends a path to selection that has sankey path data attached
// The path is formatted as dash array, and triangle paths to create arrows along the path
function pathArrows() {
var arrowLength = 10;
var gapLength = 50;
var arrowHeadSize = 4;
var path = null;
function appendArrows(selection) {
let totalDashArrayLength = arrowLength + gapLength;
let arrows = selection.append('path').attr('d', path).style('stroke-width', 1).style('stroke', 'black').style('stroke-dasharray', arrowLength + ',' + gapLength);
arrows.each(function (arrow) {
let thisPath = (0, _d3Selection.select)(this).node();
let parentG = (0, _d3Selection.select)(this.parentNode);
let pathLength = thisPath.getTotalLength();
let numberOfArrows = Math.ceil(pathLength / totalDashArrayLength);
// remove the last arrow head if it will overlap the target node
if ((numberOfArrows - 1) * totalDashArrayLength + (arrowLength + (arrowHeadSize + 1)) > pathLength) {
numberOfArrows = numberOfArrows - 1;
}
let arrowHeadData = (0, _d3Array.range)(numberOfArrows).map(function (d, i) {
let length = i * totalDashArrayLength + arrowLength;
let point = thisPath.getPointAtLength(length);
let previousPoint = thisPath.getPointAtLength(length - 2);
let rotation = 0;
if (point.y == previousPoint.y) {
rotation = point.x < previousPoint.x ? 180 : 0;
} else if (point.x == previousPoint.x) {
rotation = point.y < previousPoint.y ? -90 : 90;
} else {
let adj = Math.abs(point.x - previousPoint.x);
let opp = Math.abs(point.y - previousPoint.y);
let angle = Math.atan(opp / adj) * (180 / Math.PI);
if (point.x < previousPoint.x) {
angle = angle + (90 - angle) * 2;
}
if (point.y < previousPoint.y) {
rotation = -angle;
} else {
rotation = angle;
}
}
return { x: point.x, y: point.y, rotation: rotation };
});
let arrowHeads = parentG.selectAll('.arrow-heads').data(arrowHeadData).enter().append('path').attr('d', function (d) {
return 'M' + d.x + ',' + (d.y - arrowHeadSize / 2) + ' ' + 'L' + (d.x + arrowHeadSize) + ',' + d.y + ' ' + 'L' + d.x + ',' + (d.y + arrowHeadSize / 2);
}).attr('class', 'arrow-head').attr('transform', function (d) {
return 'rotate(' + d.rotation + ',' + d.x + ',' + d.y + ')';
}).style('fill', 'black');
});
}
appendArrows.arrowLength = function (value) {
if (!arguments.length) return arrowLength;
arrowLength = value;
return appendArrows;
};
appendArrows.gapLength = function (value) {
if (!arguments.length) return gapLength;
gapLength = value;
return appendArrows;
};
appendArrows.arrowHeadSize = function (value) {
if (!arguments.length) return arrowHeadSize;
arrowHeadSize = value;
return appendArrows;
};
appendArrows.path = function (pathFunction) {
if (!arguments.length) {
return path;
} else {
if (typeof pathFunction === "function") {
path = pathFunction;
return appendArrows;
} else {
path = function () {
return pathFunction;
};
return appendArrows;
}
}
};
return appendArrows;
}