This is an alternative to: Mobile Patent Suits
A simpler version is here
How? The width of the links and the length & width of arrow-heads are determined by parameters. These parameters are read from the link properties. Nodes must have a property named "r" which then determines the radius of representing circles. This property is used by the creation of the polygon to shorten the link preventing the arrow head disappearing behind the target node.
Explanation of the Code: Click here to see the picture
Why? svg markers don't seem to be easy to play with, particularly if node radii vary.
What more can be done? One may create curved links by using svg path instead of polygon.
xxxxxxxxxx
<meta charset="utf-8">
<style>
.link {
stroke: white;
stroke-width: 1px;
}
.licensing {
fill: #4DDE00;
}
.suit {
fill: #0772A1;
}
.resolved {
fill: #FF8700;
}
.node {
fill: lightgray;
stroke: black;
stroke-width: 1px;
}
text {
font: 10px sans-serif;
pointer-events: none;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
}
</style>
<body>
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script>
// https://blog.thomsonreuters.com/index.php/mobile-patent-suits-graphic-of-the-day/
var links = [
{source: "Microsoft", target: "Amazon", type: "licensing"},
{source: "Microsoft", target: "HTC", type: "licensing"},
{source: "Samsung", target: "Apple", type: "suit"},
{source: "Motorola", target: "Apple", type: "suit"},
{source: "Nokia", target: "Apple", type: "resolved"},
{source: "HTC", target: "Apple", type: "suit"},
{source: "Kodak", target: "Apple", type: "suit"},
{source: "Microsoft", target: "Barnes & Noble", type: "suit"},
{source: "Microsoft", target: "Foxconn", type: "suit"},
{source: "Oracle", target: "Google", type: "suit"},
{source: "Apple", target: "HTC", type: "suit"},
{source: "Microsoft", target: "Inventec", type: "suit"},
{source: "Samsung", target: "Kodak", type: "resolved"},
{source: "LG", target: "Kodak", type: "resolved"},
{source: "RIM", target: "Kodak", type: "suit"},
{source: "Sony", target: "LG", type: "suit"},
{source: "Kodak", target: "LG", type: "resolved"},
{source: "Apple", target: "Nokia", type: "resolved"},
{source: "Qualcomm", target: "Nokia", type: "resolved"},
{source: "Apple", target: "Motorola", type: "suit"},
{source: "Microsoft", target: "Motorola", type: "suit"},
{source: "Motorola", target: "Microsoft", type: "suit"},
{source: "Huawei", target: "ZTE", type: "suit"},
{source: "Ericsson", target: "ZTE", type: "suit"},
{source: "Kodak", target: "Samsung", type: "resolved"},
{source: "Apple", target: "Samsung", type: "suit"},
{source: "Kodak", target: "RIM", type: "suit"},
{source: "Nokia", target: "Qualcomm", type: "suit"}
];
var width = 960,
height = 500;
var nodes = {}; // Compute the distinct nodes from the links
links.forEach(function (link) {
link.source = nodes[link.source] || (nodes[link.source] = {name: link.source});
link.target = nodes[link.target] || (nodes[link.target] = {name: link.target});
});
nodes = d3.values(nodes)
links.forEach(function (link) {
link.linkWidth = 3;
link.headLength = 15;
link.headWidth = 5;
});
var force = d3.layout.force()
.size([width, height])
.nodes(nodes)
.links(links)
.charge(-3000)
.gravity(.8)
.on("tick", tick)
.start();
// Attach the r attribute looking at the number of neighbors.
// This is necessary for calculatePolygon)
nodes.forEach(function(node) {node.r = 3 + 1.1 * node.weight })
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var svgLinks = svg.selectAll(".link").data(links)
.enter().append("polygon")
.attr("class", function (d) { return "link " + d.type})
var svgNodes = svg.selectAll(".node").data(nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", function (d) {return d.r})
.call(force.drag);
var svgTexts = svg.selectAll("text").data(nodes)
.enter().append("text")
.attr("x", function (d) { return d.r + 6 })
.attr("y", ".31em")
.text(function(d) { return d.name; });
function tick() {
svgNodes.attr("transform", translate);
svgTexts.attr("transform", translate);
svgLinks.attr("points", calculatePolygon);
}
function translate (d) {
return "translate(" + d.x + "," + d.y + ")";
}
function calculatePolygon(d) {
var p2 = d.source,
w = diff(d.target, p2),
wl = length(w),
v1 = scale(w, (wl - d.target.r) / wl),
p1 = sum(p2, v1),
v2 = scale(rotate90(w), d.linkWidth / length(w)),
p3 = sum(p2, v2),
v1l = length(v1),
v3 = scale(v1, (v1l - d.headLength) / v1l),
p4 = sum(p3, v3),
v2l = length(v2),
v4 = scale(v2, d.headWidth / v2l),
p5 = sum(p4, v4);
return pr(p1) +" "+ pr(p2) +" "+ pr(p3) +" "+ pr(p4) +" "+ pr(p5);
function length(v) {return Math.sqrt(v.x * v.x + v.y * v.y)}
function diff(v, w) {return {x: v.x - w.x, y: v.y - w.y}}
function sum(v, w) {return {x: v.x + w.x, y: v.y + w.y}}
function scale(v, f) {return {x: f * v.x, y: f * v.y}}
function rotate90(v) {return {x: v.y, y: -v.x}} // clockwise
function pr(v) {return v.x +","+ v.y}
}
</script>
Modified http://d3js.org/d3.v3.min.js to a secure url
https://d3js.org/d3.v3.min.js