I have a fully functional forced directed graph. I am trying to use directional arrowheads.
The size of each node is proportional to it's indegree and outdegree and the thickness of links varies according the below link property:
.attr("stroke-width",function(d) {return d.total_amt/60;})
I am trying to have the arrowheads in such a way that they are in proportion to the stroke width as well as the node size.
Using multiple markers is not the option as the variation in stroke width is not know as it is dependent on d.total_amount which is one of the link attributes.
So I am trying to mathematically compute the x2 and y2 values for the line:
var nodeRadius = 20;
var lineX2 = function (d) {
var length = Math.sqrt(Math.pow(d.target.y - d.source.y, 2) + Math.pow(d.target.x - d.source.x, 2));
var scale = (length - nodeRadius) / length;
var offset = (d.target.x - d.source.x) - (d.target.x - d.source.x) * scale;
return d.target.x - offset;
};
var lineY2 = function (d) {
var length = Math.sqrt(Math.pow(d.target.y - d.source.y, 2) + Math.pow(d.target.x - d.source.x, 2));
var scale = (length - nodeRadius) / length;
var offset = (d.target.y - d.source.y) - (d.target.y - d.source.y) * scale;
return d.target.y - offset;
};
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(d3GraphData.links)
.enter().append("line")
.attr("stroke-width",function(d) {return d.total_amt/60;})
.attr("class", "link")
.attr("x1", function (d) {
return d.source.x;
})
.attr("y1", function (d) {
return d.source.y;
})
.attr("x2", lineX2)
.attr("y2", lineY2)
.attr("marker-end", "url(#end)")
Below is the tick function for the nodes that uses lineX2 and lineY2
function ticked() {
link
.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", lineX2)
.attr("y2", lineY2)
node
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
}
Below is the marker definition:
svg.append("svg:defs").selectAll("marker")
.data(["end"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 0 10 10")
.attr("refX", "4")
.attr("refY", "3")
.attr("markerUnits", "strokeWidth")
.attr("markerWidth", "7")
.attr("markerHeight", "2")
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M 0 0 L 10 5 L 0 10 z")
Right now this seems to partially work where the arrow heads do seem to be in proportion to the edge thickness.
But when the node sizes are small, the arrows don't seem to touch the outer edge of the node and terminate much earlier.
I did try using d3.scaleLinear() instead of nodeRadius in the computation of lineX2 and lineY2 but that made the whole graph very weird.
var minRadius = 5
var maxRadius = 20
var scale = (length - d3.scaleLinear().range([minRadius,maxRadius])) / length
Also, even though the arrowheads seem to be proportional to the edge thickness, the console still throws the below error:
Error: <line> attribute x2: Expected length, "NaN".
(anonymous) @ d3.v4.min.js:2
797d3.v4.min.js:2 Error: <line> attribute y2: Expected length, "NaN".
Below is the fiddle that demonstrates the problem