Skip to content

Instantly share code, notes, and snippets.

@ZoeLeBlanc
Last active March 1, 2018 19:36
Show Gist options
  • Save ZoeLeBlanc/797e49620b10320868add2d52e41f8d5 to your computer and use it in GitHub Desktop.
Save ZoeLeBlanc/797e49620b10320868add2d52e41f8d5 to your computer and use it in GitHub Desktop.
Adjacency Matrix Layout
license: mit

A simple D3 layout that creates an adjacency matrix. In an adjacency matrix, unlike an arc diagram or a force-directed layout, the links are not lines and the nodes are not circles (or other icons). Instead, all nodes are shown across the x and y axes, and a link is indicated by a filled grid cell where the connected nodes meet.

The layout also includes helper functions to draw the x and y axes to label the nodes.

The most current version of the layout is here.

forked from emeeks's block: Adjacency Matrix Layout

(function() {
d3.layout.adjacencyMatrix = function() {
var directed = true,
size = [1,1],
nodes = [],
edges = [],
edgeWeight = function (d) {return 1},
nodeID = function (d) {return d.id};
function matrix() {
var width = size[0],
height = size[1],
nodeWidth = width / nodes.length,
nodeHeight = height / nodes.length,
constructedMatrix = [],
matrix = [],
edgeHash = {},
xScale = d3.scale.linear().domain([0,nodes.length]).range([0,width]),
yScale = d3.scale.linear().domain([0,nodes.length]).range([0,height]);
nodes.forEach(function(node, i) {
node.sortedIndex = i;
})
edges.forEach(function(edge) {
var constructedEdge = {source: edge.source, target: edge.target, weight: edgeWeight(edge)};
if (typeof edge.source == "number") {
constructedEdge.source = nodes[edge.source];
}
if (typeof edge.target == "number") {
constructedEdge.target = nodes[edge.target];
}
var id = nodeID(constructedEdge.source) + "-" + nodeID(constructedEdge.target);
if (directed === false && constructedEdge.source.sortedIndex < constructedEdge.target.sortedIndex) {
id = nodeID(constructedEdge.target) + "-" + nodeID(constructedEdge.source);
}
console.log('eh',edgeHash[id][0]);
if (!edgeHash[id]) {
edgeHash[id] = constructedEdge;
}
else {
edgeHash[id].weight = edgeHash[id].weight + constructedEdge.weight;
}
});
console.log("nodes", edges, nodes.length)
nodes.forEach(function (sourceNode, a) {
nodes.forEach(function (targetNode, b) {
var grid = {id: nodeID(sourceNode) + "-" + nodeID(targetNode), source: sourceNode, target: targetNode, x: xScale(b), y: yScale(a), weight: 0, height: nodeHeight, width: nodeWidth};
var edgeWeight = 0;
console.log(edgeHash[grid.id]);
if (edgeHash[grid.id]) {
edgeWeight = edgeHash[grid.id].weight;
grid.weight = edgeWeight;
};
if (directed === true || b < a) {
matrix.push(grid);
if (directed === false) {
var mirrorGrid = {id: nodeID(sourceNode) + "-" + nodeID(targetNode), source: sourceNode, target: targetNode, x: xScale(a), y: yScale(b), weight: 0, height: nodeHeight, width: nodeWidth};
mirrorGrid.weight = edgeWeight;
matrix.push(mirrorGrid);
}
}
});
});
console.log("matrix", matrix, matrix.length)
return matrix;
}
matrix.directed = function(x) {
if (!arguments.length) return directed;
directed = x;
return matrix;
}
matrix.size = function(x) {
if (!arguments.length) return size;
size = x;
return matrix;
}
matrix.nodes = function(x) {
if (!arguments.length) return nodes;
nodes = x;
return matrix;
}
matrix.links = function(x) {
if (!arguments.length) return edges;
edges = x;
return matrix;
}
matrix.edgeWeight = function(x) {
if (!arguments.length) return edgeWeight;
if (typeof x === "function") {
edgeWeight = x;
}
else {
edgeWeight = function () {return x};
}
return matrix;
}
matrix.nodeID = function(x) {
if (!arguments.length) return nodeID;
if (typeof x === "function") {
nodeID = x;
}
return matrix;
}
matrix.xAxis = function(calledG) {
var nameScale = d3.scale.ordinal()
.domain(nodes.map(nodeID))
.rangePoints([0,size[0]],1);
var xAxis = d3.svg.axis().scale(nameScale).orient("top").tickSize(4);
calledG
.append("g")
.attr("class", "am-xAxis am-axis")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("transform", "translate(-10,-10) rotate(90)");
}
matrix.yAxis = function(calledG) {
var nameScale = d3.scale.ordinal()
.domain(nodes.map(nodeID))
.rangePoints([0,size[1]],1);
yAxis = d3.svg.axis().scale(nameScale)
.orient("left")
.tickSize(4);
calledG.append("g")
.attr("class", "am-yAxis am-axis")
.call(yAxis);
}
return matrix;
}
})();
<html>
<head>
<title>D3 in Action Chapter 6 - Example 1</title>
<meta charset="utf-8" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="d3.layout.adjacencyMatrix.js" type="text/JavaScript"></script>
</head>
<style>
svg {
height: 1400px;
width: 1400px;
border: 1px solid gray;
}
g.am-axis text {
font-size: 8px;
}
.domain {
fill: none;
}
.tick > line{
stroke: black;
stroke-width: 1px;
stroke-opacity: .25;
}
</style>
<body>
<div id="viz">
<svg>
</svg>
</div>
<div id="controls" />
</body>
<footer>
<script>
d3.json("miserables.json", createAdjacencyMatrix);
function createAdjacencyMatrix(data) {
console.log(data)
var adjacencyMatrix = d3.layout.adjacencyMatrix()
.size([800,800])
.nodes(data.nodes)
.links(data.links)
.directed(false)
.nodeID(function (d) {return d.name});
var matrixData = adjacencyMatrix();
console.log(matrixData)
var someColors = d3.scale.category20b();
d3.select("svg")
.append("g")
.attr("transform", "translate(50,50)")
.attr("id", "adjacencyG")
.selectAll("rect")
.data(matrixData)
.enter()
.append("rect")
.attr("width", function (d) {return d.width})
.attr("height", function (d) {return d.height})
.attr("x", function (d) {return d.x})
.attr("y", function (d) {return d.y})
.style("stroke", "black")
.style("stroke-width", "1px")
.style("stroke-opacity", .1)
.style("fill", 'blue')
.style("fill-opacity", function (d) {return d.weight * .8});
d3.select("#adjacencyG")
.call(adjacencyMatrix.xAxis);
d3.select("#adjacencyG")
.call(adjacencyMatrix.yAxis);
}
</script>
</footer>
</html>
{
"nodes": [{
"name": "avril"
},
{
"name": "booboo"
},
{
"name": "hal"
},
{
"name": "hallie"
},
{
"name": "himself"
},
{
"name": "incandenza"
},
{
"name": "jamesorin"
},
{
"name": "joellevandyne"
},
{
"name": "joi"
}, {
"name": "luriap"
}, {
"name": "madame"
}, {
"name": "madamepsychosis"
}, {
"name": "mario"
}, {
"name": "mondragon"
}, {
"name": "orin"
}, {
"name": "pokie"
}, {
"name": "prettiestgirlofalltime"
}, {
"name": "themoms"
}
],
"links": [{
"weight": 1,
"source": "avril",
"target": "luriap"
}, {
"weight": 1,
"source": "avril",
"target": "pokie"
}, {
"weight": 6,
"source": "avril",
"target": "booboo"
}, {
"weight": 2,
"source": "avril",
"target": "hallie"
}, {
"weight": 8,
"source": "avril",
"target": "joi"
}, {
"weight": 3,
"source": "avril",
"target": "joellevandyne"
}, {
"weight": 5,
"source": "avril",
"target": "madamepsychosis"
}, {
"weight": 5,
"source": "avril",
"target": "madame"
}, {
"weight": 3,
"source": "avril",
"target": "mondragon"
}, {
"weight": 40,
"source": "avril",
"target": "mario"
}, {
"weight": 43,
"source": "avril",
"target": "incandenza"
}, {
"weight": 55,
"source": "avril",
"target": "hal"
}, {
"weight": 37,
"source": "avril",
"target": "himself"
}, {
"weight": 36,
"source": "avril",
"target": "orin"
}, {
"weight": 26,
"source": "avril",
"target": "themoms"
}, {
"weight": 0,
"source": "avril",
"target": "avril"
}, {
"weight": 1,
"source": "booboo",
"target": "mondragon"
}, {
"weight": 1,
"source": "booboo",
"target": "madamepsychosis"
}, {
"weight": 1,
"source": "booboo",
"target": "madame"
}, {
"weight": 4,
"source": "booboo",
"target": "hallie"
}, {
"weight": 1,
"source": "booboo",
"target": "joi"
}, {
"weight": 3,
"source": "booboo",
"target": "incandenza"
}, {
"weight": 9,
"source": "booboo",
"target": "orin"
}, {
"weight": 11,
"source": "booboo",
"target": "himself"
}, {
"weight": 12,
"source": "booboo",
"target": "themoms"
}, {
"weight": 13,
"source": "booboo",
"target": "mario"
}, {
"weight": 16,
"source": "booboo",
"target": "hal"
}, {
"weight": 0,
"source": "booboo",
"target": "booboo"
}, {
"weight": 2,
"source": "hal",
"target": "pokie"
}, {
"weight": 1,
"source": "hal",
"target": "prettiestgirlofalltime"
}, {
"weight": 17,
"source": "hal",
"target": "joellevandyne"
}, {
"weight": 20,
"source": "hal",
"target": "madamepsychosis"
}, {
"weight": 24,
"source": "hal",
"target": "madame"
}, {
"weight": 41,
"source": "hal",
"target": "joi"
}, {
"weight": 1,
"source": "hal",
"target": "jamesorin"
}, {
"weight": 19,
"source": "hal",
"target": "hallie"
}, {
"weight": 111,
"source": "hal",
"target": "mario"
}, {
"weight": 4,
"source": "hal",
"target": "mondragon"
}, {
"weight": 10,
"source": "hal",
"target": "luriap"
}, {
"weight": 123,
"source": "hal",
"target": "orin"
}, {
"weight": 54,
"source": "hal",
"target": "themoms"
}, {
"weight": 128,
"source": "hal",
"target": "incandenza"
}, {
"weight": 0,
"source": "hal",
"target": "hal"
}, {
"weight": 222,
"source": "hal",
"target": "himself"
}, {
"weight": 1,
"source": "hallie",
"target": "madamepsychosis"
}, {
"weight": 2,
"source": "hallie",
"target": "madame"
}, {
"weight": 3,
"source": "hallie",
"target": "joi"
}, {
"weight": 10,
"source": "hallie",
"target": "themoms"
}, {
"weight": 8,
"source": "hallie",
"target": "mario"
}, {
"weight": 8,
"source": "hallie",
"target": "incandenza"
}, {
"weight": 12,
"source": "hallie",
"target": "himself"
}, {
"weight": 10,
"source": "hallie",
"target": "orin"
}, {
"weight": 0,
"source": "hallie",
"target": "hallie"
}, {
"weight": 2,
"source": "himself",
"target": "pokie"
}, {
"weight": 1,
"source": "himself",
"target": "prettiestgirlofalltime"
}, {
"weight": 12,
"source": "himself",
"target": "joellevandyne"
}, {
"weight": 9,
"source": "himself",
"target": "madamepsychosis"
}, {
"weight": 11,
"source": "himself",
"target": "madame"
}, {
"weight": 25,
"source": "himself",
"target": "joi"
}, {
"weight": 76,
"source": "himself",
"target": "mario"
}, {
"weight": 4,
"source": "himself",
"target": "mondragon"
}, {
"weight": 7,
"source": "himself",
"target": "luriap"
}, {
"weight": 86,
"source": "himself",
"target": "orin"
}, {
"weight": 44,
"source": "himself",
"target": "themoms"
}, {
"weight": 85,
"source": "himself",
"target": "incandenza"
}, {
"weight": 0,
"source": "himself",
"target": "himself"
}, {
"weight": 2,
"source": "incandenza",
"target": "pokie"
}, {
"weight": 3,
"source": "incandenza",
"target": "luriap"
}, {
"weight": 8,
"source": "incandenza",
"target": "joellevandyne"
}, {
"weight": 15,
"source": "incandenza",
"target": "joi"
}, {
"weight": 12,
"source": "incandenza",
"target": "madamepsychosis"
}, {
"weight": 14,
"source": "incandenza",
"target": "madame"
}, {
"weight": 3,
"source": "incandenza",
"target": "mondragon"
}, {
"weight": 1,
"source": "incandenza",
"target": "jamesorin"
}, {
"weight": 65,
"source": "incandenza",
"target": "orin"
}, {
"weight": 26,
"source": "incandenza",
"target": "themoms"
}, {
"weight": 68,
"source": "incandenza",
"target": "mario"
}, {
"weight": 0,
"source": "incandenza",
"target": "incandenza"
}, {
"weight": 1,
"source": "jamesorin",
"target": "orin"
}, {
"weight": 0,
"source": "jamesorin",
"target": "jamesorin"
}, {
"weight": 2,
"source": "joellevandyne",
"target": "joi"
}, {
"weight": 1,
"source": "joellevandyne",
"target": "luriap"
}, {
"weight": 1,
"source": "joellevandyne",
"target": "themoms"
}, {
"weight": 2,
"source": "joellevandyne",
"target": "madame"
}, {
"weight": 4,
"source": "joellevandyne",
"target": "mario"
}, {
"weight": 10,
"source": "joellevandyne",
"target": "orin"
}, {
"weight": 0,
"source": "joellevandyne",
"target": "joellevandyne"
}, {
"weight": 1,
"source": "joi",
"target": "luriap"
}, {
"weight": 16,
"source": "joi",
"target": "orin"
}, {
"weight": 3,
"source": "joi",
"target": "madamepsychosis"
}, {
"weight": 4,
"source": "joi",
"target": "madame"
}, {
"weight": 11,
"source": "joi",
"target": "mario"
}, {
"weight": 7,
"source": "joi",
"target": "themoms"
}, {
"weight": 0,
"source": "joi",
"target": "joi"
}, {
"weight": 1,
"source": "luriap",
"target": "themoms"
}, {
"weight": 3,
"source": "luriap",
"target": "orin"
}, {
"weight": 5,
"source": "luriap",
"target": "mario"
}, {
"weight": 2,
"source": "luriap",
"target": "mondragon"
}, {
"weight": 0,
"source": "luriap",
"target": "luriap"
}, {
"weight": 7,
"source": "madame",
"target": "themoms"
}, {
"weight": 9,
"source": "madame",
"target": "orin"
}, {
"weight": 10,
"source": "madame",
"target": "mario"
}, {
"weight": 22,
"source": "madame",
"target": "madamepsychosis"
}, {
"weight": 0,
"source": "madame",
"target": "madame"
}, {
"weight": 7,
"source": "madamepsychosis",
"target": "themoms"
}, {
"weight": 8,
"source": "madamepsychosis",
"target": "orin"
}, {
"weight": 10,
"source": "madamepsychosis",
"target": "mario"
}, {
"weight": 0,
"source": "madamepsychosis",
"target": "madamepsychosis"
}, {
"weight": 40,
"source": "mario",
"target": "themoms"
}, {
"weight": 60,
"source": "mario",
"target": "orin"
}, {
"weight": 4,
"source": "mario",
"target": "mondragon"
}, {
"weight": 0,
"source": "mario",
"target": "mario"
}, {
"weight": 3,
"source": "mondragon",
"target": "themoms"
}, {
"weight": 3,
"source": "mondragon",
"target": "orin"
}, {
"weight": 0,
"source": "mondragon",
"target": "mondragon"
}, {
"weight": 2,
"source": "orin",
"target": "pokie"
}, {
"weight": 1,
"source": "orin",
"target": "prettiestgirlofalltime"
}, {
"weight": 0,
"source": "orin",
"target": "orin"
}, {
"weight": 38,
"source": "orin",
"target": "themoms"
}, {
"weight": 1,
"source": "pokie",
"target": "themoms"
}, {
"weight": 0,
"source": "pokie",
"target": "pokie"
}, {
"weight": 0,
"source": "prettiestgirlofalltime",
"target": "prettiestgirlofalltime"
}, {
"weight": 0,
"source": "themoms",
"target": "themoms"
}]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment