|
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"> |
|
<html> |
|
<head> |
|
<meta name="generator" content= |
|
"HTML Tidy for Linux (vers 25 March 2009), see www.w3.org"> |
|
<meta http-equiv="Content-type" content= |
|
"text/html; charset=us-ascii"> |
|
<title>Graph from a matrix</title> |
|
|
|
<script type="text/javascript" src= |
|
"http://mbostock.github.com/d3/d3.js?1.29.1"> |
|
</script> |
|
<script type="text/javascript" src= |
|
"http://mbostock.github.com/d3/d3.geom.js?1.29.1"> |
|
</script> |
|
<script type="text/javascript" src= |
|
"http://mbostock.github.com/d3/d3.layout.js?1.29.1"> |
|
</script> |
|
<script type="text/javascript" src= |
|
"http://www.numericjs.com/lib/numeric-1.0.2.min.js"> |
|
</script> |
|
<style type="text/css"> |
|
|
|
path.link { |
|
fill: none; |
|
stroke: #111; |
|
stroke-width: 1.5px; |
|
} |
|
|
|
marker#licensing { |
|
fill: green; |
|
} |
|
|
|
path.link.licensing { |
|
stroke: green; |
|
} |
|
|
|
path.link.resolved { |
|
stroke-dasharray: 0,2 1; |
|
} |
|
|
|
circle { |
|
fill: #ccc; |
|
stroke: #333; |
|
stroke-width: 1.5px; |
|
} |
|
|
|
circle.self-loop { |
|
display: none; |
|
} |
|
|
|
text { |
|
font: 10px sans-serif; |
|
pointer-events: none; |
|
} |
|
|
|
text.self-loop { |
|
display: none; |
|
} |
|
|
|
text.shadow { |
|
stroke: #fff; |
|
stroke-width: 3px; |
|
stroke-opacity: .8; |
|
} |
|
|
|
</style> |
|
</head> |
|
<body> |
|
<form action=""><input id="expressionA" name="expression" onchange= |
|
"compute(this.value)" size="100%" value= |
|
"a=[[0,1,0,0,0],[0,0,1,0,0],[1,0,0,0,1],[0,1,0,0,0],[0,0,0,1,0]]; matrixPow(a,4)"></form> |
|
<script type="text/javascript"> |
|
|
|
function compute(expr){ |
|
matrix = eval(expr); |
|
draw(matrix); |
|
} |
|
|
|
function draw(matrix){ |
|
var links = []; |
|
|
|
for(m=0;m< matrix.length;m++) { |
|
a = matrix[m]; |
|
for(n=0;n< a.length;n++) { |
|
if (a[n]>0) { |
|
if (n!=m) { |
|
links.push( { |
|
source: String(m+1), |
|
target: String(n+1), |
|
type: String(a[n]) |
|
}); |
|
} else { |
|
// These are for rendering self reference. The 'null' vertex is a secret extra vertex. |
|
links.push( { |
|
source: String(m+1)+"-null", |
|
target: String(m+1), |
|
type: "self-loop" |
|
}); |
|
} |
|
} |
|
} |
|
} |
|
|
|
var nodes = {}; |
|
|
|
// Compute the distinct nodes from the links. |
|
links.forEach(function(link) { |
|
t = (link.type == "self-loop"?"self-loop": "basic"); |
|
link.source = nodes[link.source] || (nodes[link.source] = {name: link.source, type: t}); |
|
link.target = nodes[link.target] || (nodes[link.target] = {name: link.target}); |
|
}); |
|
|
|
var w = 960, |
|
h = 500; |
|
|
|
var force = d3.layout.force() |
|
.nodes(d3.values(nodes)) |
|
.links(links) |
|
.linkStrength(function(d) { return (d.type == "self-loop"? 1 : 0.5); }) |
|
.size([w, h]) |
|
.linkDistance(function(d) { return (d.type == "self-loop"? 20 : 150); }) |
|
.charge(-1200) |
|
.gravity(0.3) |
|
.on("tick", tick) |
|
.start(); |
|
|
|
oldsvgs = document.getElementsByTagName("svg"); |
|
for (i=0;i<oldsvgs.length;i++) { |
|
oldsvgs[i].parentNode.removeChild(oldsvgs[i]); |
|
} |
|
|
|
var svg = d3.select("body").append("svg:svg") |
|
.attr("width", w) |
|
.attr("height", h); |
|
|
|
// Per-type markers, as they don't inherit styles. |
|
svg.append("svg:defs").selectAll("marker") |
|
.data(["basic"]) |
|
.enter().append("svg:marker") |
|
.attr("id", String) |
|
.attr("viewBox", "0 -5 10 10") |
|
.attr("refX", 20) |
|
.attr("refY", -1.5) |
|
.attr("markerWidth", 6) |
|
.attr("markerHeight", 6) |
|
.attr("orient", "auto") |
|
.append("svg:path") |
|
.attr("d", "M0,-5L10,0L0,5"); |
|
|
|
var path = svg.append("svg:g").selectAll("path") |
|
.data(force.links()) |
|
.enter().append("svg:path") |
|
.attr("class", function(d) { return "link " + d.type; }) |
|
.attr("marker-end", "url(#basic)"); |
|
|
|
var circle = svg.append("svg:g").selectAll("circle") |
|
.data(force.nodes()) |
|
.enter().append("svg:circle") |
|
.attr("r", 10) |
|
.call(force.drag) |
|
.attr("class",function(d){ return d.type }); |
|
|
|
var text = svg.append("svg:g").selectAll("g") |
|
.data(force.nodes()) |
|
.enter().append("svg:g") |
|
.attr("class",function(d){ return d.type }); |
|
|
|
|
|
// A copy of the text with a thick white stroke for legitimacy. |
|
text.append("svg:text") |
|
.attr("x", 0) |
|
.attr("y", ".31em") |
|
.attr("class", "shadow") |
|
.attr("class",function(d){ return d.type }) |
|
.text(function(d) { return d.name; }); |
|
|
|
|
|
text.append("svg:text") |
|
.attr("x", 0) |
|
.attr("y", ".31em") |
|
.attr("class",function(d){ return d.type }) |
|
.text(function(d) { return d.name; }); |
|
|
|
function tick() { |
|
path.attr("d", function(d) { |
|
if (d.type != "self-loop") { |
|
// Use elliptical arc path segments to doubly-encode directionality. |
|
var dx = d.target.x - d.source.x, |
|
dy = d.target.y - d.source.y, |
|
dr = Math.sqrt(dx * dx + dy * dy); |
|
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y; |
|
} else if (d.type == "self-loop") { |
|
// draw a little loop back to the self, keeping in mind that all self loops from the dummy node toward the self. |
|
var dx = d.source.x - d.target.x, |
|
dy = d.source.y - d.target.y, |
|
dr = Math.sqrt(dx * dx + dy * dy); |
|
a = Math.atan2(dx,dy); |
|
da = 0.4; |
|
b = 1; |
|
return "M" + d.target.x + "," + d.target.y + "q" + |
|
b*dr*numeric.sin(a) + "," + b*dr*numeric.cos(a) + " " + |
|
b*dr*numeric.sin(a+da) + "," + b*dr*numeric.cos(a+da) + " " + " T " + d.target.x + "," + d.target.y; |
|
} |
|
}); |
|
|
|
circle.attr("transform", function(d) { |
|
return "translate(" + d.x + "," + d.y + ")"; |
|
}); |
|
|
|
text.attr("transform", function(d) { |
|
return "translate(" + d.x + "," + d.y + ")"; |
|
}); |
|
} |
|
} |
|
|
|
// extra functions |
|
|
|
function matrixPow(m,p) { |
|
if(p==0) return numeric.identity(m[0].length); |
|
a = m; |
|
for (i=0;i<p-1;i++) { |
|
a = numeric.dot(a,m); |
|
} |
|
return a; |
|
} |
|
|
|
function getParameterByName(name) |
|
{ |
|
// to get the parameters for the expression |
|
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]"); |
|
var regexS = "[\\?&]" + name + "=([^&#]*)"; |
|
var regex = new RegExp(regexS); |
|
var results = regex.exec(window.location.search); |
|
if(results == null) |
|
return ""; |
|
else |
|
return decodeURIComponent(results[1].replace(/\+/g, " ")); |
|
} |
|
|
|
input = document.getElementById("expressionA"); |
|
pExpression = getParameterByName("expression"); |
|
if (pExpression) input.value = pExpression; |
|
compute(document.getElementById("expressionA").value); |
|
|
|
</script> |
|
</body> |
|
</html> |