|
<!DOCTYPE html> |
|
<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="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> |
|
<script> |
|
|
|
// http://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> |