├── .gitignore ├── .babelrc ├── index.js ├── rollup.config.js ├── .eslintrc ├── package.json ├── README.md └── src └── d3AdjacencyMatrixLayout.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015-rollup"] 3 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | export { default as adjacencyMatrixLayout } from './src/d3AdjacencyMatrixLayout'; 2 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import json from 'rollup-plugin-json'; 2 | import babel from 'rollup-plugin-babel'; 3 | 4 | export default { 5 | entry: 'index.js', 6 | format: 'umd', 7 | globals: { 8 | d3: 'd3' 9 | }, 10 | moduleName: 'd3', 11 | plugins: [ json(), babel() ], 12 | dest: 'build/d3-adjacency-matrix-layout.js' 13 | }; -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "rules": { 4 | "comma-dangle": "off", 5 | "no-param-reassign": "off", 6 | "no-restricted-syntax": "off", 7 | "newline-per-chained-call": "off", 8 | "no-console": "off", 9 | "func-names": "off", 10 | "no-unused-vars": "off", 11 | "no-shadow": "off", 12 | "prefer-rest-params": "off" 13 | } 14 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "d3-ajacency-matrix-layout", 3 | "version": "1.0.0", 4 | "description": "a d3 ajacency matrix layout", 5 | "main": "build/d3-adjacency-matrix-layout.js", 6 | "scripts": { 7 | "prepublish": "rm -rf build && mkdir build && rollup -c --banner \"$(preamble)\" -- index.js", 8 | "test": "rm -rf build && mkdir build && rollup -c --banner \"$(preamble)\" -- index.js && cp build/d3-adjacency-matrix-layout.js ~/workspace/d3-adjacency-layout-example-project/04/", 9 | "lint": "eslint index.js src --fix" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/micahstubbs/d3-adjacency-matrix-layout" 14 | }, 15 | "keywords": [ 16 | "d3", 17 | "boxplot" 18 | ], 19 | "homepage": "https://github.com/micahstubbs/d3-adjacency-matrix-layout", 20 | "author": { 21 | "name":"contributors", 22 | "email":"micah.stubbs@gmail.com" 23 | }, 24 | "contributors": [ 25 | { 26 | "name":"Elijah Meeks", 27 | "email":"elijahmeeks@gmail.com" 28 | }, 29 | { 30 | "name": "@micahstubbs", 31 | "email": "micah.stubbs@gmail.com" 32 | } 33 | ], 34 | "devDependencies": { 35 | "babel-preset-es2015-rollup": "^1.1.1", 36 | "cli": "^0.11.2", 37 | "eslint": "^2.13.1", 38 | "eslint-config-airbnb": "^9.0.1", 39 | "eslint-plugin-import": "^1.10.2", 40 | "eslint-plugin-jsx-a11y": "^1.5.5", 41 | "eslint-plugin-react": "^5.2.2", 42 | "package-preamble": "0.0", 43 | "rollup": "^0.33.2", 44 | "rollup-plugin-babel": "^2.6.1", 45 | "rollup-plugin-json": "^2.0.1" 46 | }, 47 | "dependencies": { 48 | "d3": "^4.2.0" 49 | }, 50 | "license": "MIT", 51 | "bugs": { 52 | "url": "https://github.com/micahstubbs/d3-adjacency-matrix-layout/issues" 53 | } 54 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # d3-adjacency-matrix-layout 2 | 3 | a [d3 version 4](https://github.com/d3/d3/blob/master/API.md) port of the [adjacencyMatrix](https://github.com/emeeks/d3-plugins/tree/master/adjacencyMatrix) d3-plugin from [@Elijah_Meeks](https://twitter.com/elijah_meeks) 4 | 5 | see the layout in use at [http://bl.ocks.org/micahstubbs/7f360cc66abfa28b400b96bc75b8984e](http://bl.ocks.org/micahstubbs/7f360cc66abfa28b400b96bc75b8984e) 6 | 7 | develop with [es2015](https://babeljs.io/docs/learn-es2015/) then bundle and transpile to todays-browsers-friendly [es5](https://es5.github.io/) [Javascript](https://en.wikipedia.org/wiki/JavaScript) with the command 8 | `npm run prepublish` 9 | 10 | ### Download 11 | 12 | [build/d3-adjacency-matrix-layout.js](build/d3-adjacency-matrix-layout.js) 13 | 14 | ### API Reference 15 | 16 | **matrix.size** An array of [width, height] that is used to calculate grid x, y, height and width. 17 | 18 | **matrix.nodes** An array of the nodes of your network. 19 | 20 | **matrix.links** An array of the edges of your network. As with other D3 network layouts, if the source and target properties are numbers, they are assumed to be array position within the nodes array, otherwise objects are assumed. 21 | 22 | **matrix.edgeWeight** The function to return the weight of the edges of your links, if any. Defaults to returning 1. 23 | 24 | **matrix.nodeID** The function to return the id value of a node, defaults to returning #node.id. The id is used to build the matrix and drive the axes. 25 | 26 | **matrix.xAxis** Cannot be set. Call this from the same place where you've put your matrix cells and it will build a simple horizontal axis with labels from your node id. 27 | 28 | **matrix.yAxis** Cannot be set. Call this from the same place where you've put your matrix cells and it will build a simple vertical axis with labels from your node id. 29 | 30 | **matrix.directed** Set to false if you want to mirror undirected networks. 31 | 32 | [![d3-adjacency-matrix example](http://i.imgur.com/FSzNmgd.png)](http://bl.ocks.org/micahstubbs/7f360cc66abfa28b400b96bc75b8984e) -------------------------------------------------------------------------------- /src/d3AdjacencyMatrixLayout.js: -------------------------------------------------------------------------------- 1 | import * as d3 from 'd3'; 2 | 3 | export default function () { 4 | let directed = true; 5 | let size = [1, 1]; 6 | let nodes = []; 7 | let edges = []; 8 | let edgeWeight = d => 1; 9 | let nodeID = d => d.id; 10 | 11 | function matrix() { 12 | const width = size[0]; 13 | const height = size[1]; 14 | const nodeWidth = width / nodes.length; 15 | const nodeHeight = height / nodes.length; 16 | // const constructedMatrix = []; 17 | const matrix = []; 18 | const edgeHash = {}; 19 | const xScale = d3.scaleLinear() 20 | .domain([0, nodes.length]) 21 | .range([0, width]); 22 | 23 | const yScale = d3.scaleLinear() 24 | .domain([0, nodes.length]) 25 | .range([0, height]); 26 | 27 | nodes.forEach((node, i) => { 28 | node.sortedIndex = i; 29 | }); 30 | 31 | edges.forEach(edge => { 32 | const constructedEdge = { 33 | source: edge.source, 34 | target: edge.target, 35 | weight: edgeWeight(edge) 36 | }; 37 | if (typeof edge.source === 'number') { 38 | constructedEdge.source = nodes[edge.source]; 39 | } 40 | if (typeof edge.target === 'number') { 41 | constructedEdge.target = nodes[edge.target]; 42 | } 43 | let id = `${nodeID(constructedEdge.source)}-${nodeID(constructedEdge.target)}`; 44 | 45 | if (directed === false && 46 | constructedEdge.source.sortedIndex < constructedEdge.target.sortedIndex) { 47 | id = `${nodeID(constructedEdge.target)}-${nodeID(constructedEdge.source)}`; 48 | } 49 | if (!edgeHash[id]) { 50 | edgeHash[id] = constructedEdge; 51 | } else { 52 | edgeHash[id].weight = edgeHash[id].weight + constructedEdge.weight; 53 | } 54 | }); 55 | 56 | console.log('nodes', nodes, nodes.length); 57 | 58 | nodes.forEach((sourceNode, a) => { 59 | nodes.forEach((targetNode, b) => { 60 | const grid = { 61 | id: `${nodeID(sourceNode)}-${nodeID(targetNode)}`, 62 | source: sourceNode, 63 | target: targetNode, 64 | x: xScale(b), 65 | y: yScale(a), 66 | weight: 0, 67 | height: nodeHeight, 68 | width: nodeWidth 69 | }; 70 | let edgeWeight = 0; 71 | if (edgeHash[grid.id]) { 72 | edgeWeight = edgeHash[grid.id].weight; 73 | grid.weight = edgeWeight; 74 | } 75 | if (directed === true || b < a) { 76 | matrix.push(grid); 77 | if (directed === false) { 78 | const mirrorGrid = { 79 | id: `${nodeID(sourceNode)}-${nodeID(targetNode)}`, 80 | source: sourceNode, 81 | target: targetNode, 82 | x: xScale(a), 83 | y: yScale(b), 84 | weight: 0, 85 | height: nodeHeight, 86 | width: nodeWidth 87 | }; 88 | mirrorGrid.weight = edgeWeight; 89 | matrix.push(mirrorGrid); 90 | } 91 | } 92 | }); 93 | }); 94 | 95 | console.log('matrix', matrix, matrix.length); 96 | 97 | return matrix; 98 | } 99 | 100 | matrix.directed = function (x) { 101 | if (!arguments.length) return directed; 102 | directed = x; 103 | return matrix; 104 | }; 105 | 106 | matrix.size = function (x) { 107 | if (!arguments.length) return size; 108 | size = x; 109 | return matrix; 110 | }; 111 | 112 | matrix.nodes = function (x) { 113 | if (!arguments.length) return nodes; 114 | nodes = x; 115 | return matrix; 116 | }; 117 | 118 | matrix.links = function (x) { 119 | if (!arguments.length) return edges; 120 | edges = x; 121 | return matrix; 122 | }; 123 | 124 | matrix.edgeWeight = function (x) { 125 | if (!arguments.length) return edgeWeight; 126 | if (typeof x === 'function') { 127 | edgeWeight = x; 128 | } else { 129 | edgeWeight = () => x; 130 | } 131 | return matrix; 132 | }; 133 | 134 | matrix.nodeID = function (x) { 135 | if (!arguments.length) return nodeID; 136 | if (typeof x === 'function') { 137 | nodeID = x; 138 | } 139 | return matrix; 140 | }; 141 | 142 | matrix.xAxis = calledG => { 143 | const nameScale = d3.scalePoint() 144 | .domain(nodes.map(nodeID)) 145 | .range([0, size[0]]) 146 | .padding(1); 147 | 148 | const xAxis = d3.axisTop() 149 | .scale(nameScale) 150 | .tickSize(4); 151 | 152 | calledG 153 | .append('g') 154 | .attr('class', 'am-xAxis am-axis') 155 | .call(xAxis) 156 | .selectAll('text') 157 | .style('text-anchor', 'end') 158 | .attr('transform', 'translate(-10,-10) rotate(90)'); 159 | }; 160 | 161 | matrix.yAxis = calledG => { 162 | const nameScale = d3.scalePoint() 163 | .domain(nodes.map(nodeID)) 164 | .range([0, size[1]]) 165 | .padding(1); 166 | 167 | const yAxis = d3.axisLeft() 168 | .scale(nameScale) 169 | .tickSize(4); 170 | 171 | calledG.append('g') 172 | .attr('class', 'am-yAxis am-axis') 173 | .call(yAxis); 174 | }; 175 | 176 | return matrix; 177 | } 178 | --------------------------------------------------------------------------------