├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── example ├── code.png ├── diagram.png └── index.js ├── package.json ├── publish.js └── tpl ├── index.js └── index.tpl /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | out -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10.31 4 | before_install: npm install -g npm@'>=2.13.5' 5 | notifications: 6 | email: 7 | recipients: 8 | - tates001@gmail.com 9 | on_success: always 10 | on_failure: always 11 | deploy: 12 | provider: npm 13 | email: tates001@gmail.com 14 | api_key: $API_KEY 15 | on: 16 | tags: true 17 | branch: master 18 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Andrei McMillan (http://github.com/amcmillan01/jsdoc2diagram) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## jsdoc2diagram 2 | 3 | [![npm version](https://badge.fury.io/js/jsdoc2diagram.svg)](https://badge.fury.io/js/jsdoc2diagram) 4 | [![Build Status](https://travis-ci.org/amcmillan01/jsdoc2diagram.svg?branch=master)](https://travis-ci.org/amcmillan01/jsdoc2diagram) 5 | 6 | jsdoc2diagram is a [JSDoc](http://usejsdoc.org/) template that creates a [D3 tree diagram](http://bl.ocks.org/d3noob/8329404) from your code's jsdoc documentation. 7 | 8 | #### Code 9 | 10 | 11 | #### Diagram 12 | 13 | 14 | ### Getting Started 15 | 16 | - npm install jsdoc 17 | - npm install jsdoc2diagram 18 | - jsdoc -t `PATH/TO/jsdoc2diagram` -r `JS_SOURCE_DIR/FILE` -d `OUTPUT_DIR` 19 | 20 | ### Reference 21 | 22 | - http://usejsdoc.org/ 23 | - http://bl.ocks.org/d3noob/8329404 24 | 25 | ### License 26 | 27 | Licensed under The MIT License (MIT) 28 | 29 | For the full copyright and license information, please view the LICENSE.txt file. 30 | 31 | -------------------------------------------------------------------------------- /example/code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amcmillan01/jsdoc2diagram/41260c04113199856d1cc27fe2b132cde6acbf0d/example/code.png -------------------------------------------------------------------------------- /example/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amcmillan01/jsdoc2diagram/41260c04113199856d1cc27fe2b132cde6acbf0d/example/diagram.png -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @constructor 3 | */ 4 | var Garage = function(){ 5 | 6 | }; 7 | 8 | /** 9 | * @return {number} 10 | */ 11 | Garage.prototype.getVehicleCount = function(){ 12 | 13 | }; 14 | 15 | 16 | /** 17 | * 18 | * @constructor 19 | * @memberof Garage 20 | */ 21 | var Car = function(){ 22 | /** 23 | * 24 | * @type {string} 25 | */ 26 | this.name = ''; 27 | /** 28 | * 29 | * @type {string} 30 | */ 31 | this.color = ''; 32 | }; 33 | 34 | /** 35 | * @return {boolean} 36 | */ 37 | Car.prototype.isOn = function(){ 38 | 39 | }; 40 | 41 | /** 42 | * @return {boolean} 43 | */ 44 | Car.prototype.hasNavigation = function(){ 45 | 46 | }; 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsdoc2diagram", 3 | "version": "0.0.6", 4 | "license": "MIT", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/amcmillan01/jsdoc2diagram.git" 8 | }, 9 | "author": "Andrei McMillan", 10 | "description": "jsdoc2diagram is a JSDoc template that creates a D3 tree diagram from your code's jsdoc documentation", 11 | "keywords": [ 12 | "jsdoc2diagram", 13 | "jsdoc", 14 | "diagram", 15 | "d3", 16 | "tree" 17 | ], 18 | "scripts": { 19 | "test": "echo 'write some tests!'" 20 | }, 21 | "dependencies": { 22 | "lodash.template": "^3.6.2" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /publish.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jsdoc2diagram 3 | */ 4 | 5 | 'use strict'; 6 | 7 | var fs = require('fs'); 8 | var path = require('path'); 9 | var template = require('lodash.template'); 10 | 11 | /** 12 | * Create the tree structure need for the d3 tree diagram 13 | * - original code from http://bl.ocks.org/ 14 | * 15 | * @param {Array.} data 16 | * @return {Array} 17 | */ 18 | var createTreeStructure = function(data) { 19 | var dataMap = data.reduce(function(map, node) { 20 | map[node.name] = node; 21 | return map; 22 | }, {}); 23 | 24 | // create the tree array 25 | var treeData = []; 26 | data.forEach(function(node) { 27 | // add to parent 28 | var parent = dataMap[node.parent]; 29 | if (parent) { 30 | // create child array if it doesn't exist 31 | (parent.children || (parent.children = [])) 32 | // add node to child array 33 | .push(node); 34 | } else { 35 | // parent is null or missing 36 | treeData.push(node); 37 | } 38 | }); 39 | 40 | return treeData; 41 | }; 42 | 43 | /** 44 | @param {TAFFY} data 45 | @param {object} opts 46 | */ 47 | exports.publish = function(data, opt) { 48 | data({undocumented: true}).remove(); 49 | data({kind: 'package'}).remove(); 50 | 51 | //an array of Doclet objects 52 | var docs = data().get(); 53 | 54 | /** 55 | * 56 | * @param {string} text 57 | * @return {string} 58 | */ 59 | var cleanName = function(text) { 60 | return (text || '').trim().replace(/(\#|\/|\~|\-|\:)/g, '.'); 61 | }; 62 | 63 | var dataSet = []; 64 | 65 | docs.forEach(function(doc) { 66 | var path = cleanName(doc.longname); 67 | var memberOf = cleanName(doc.memberof); 68 | var name = path.split('.').pop(); 69 | 70 | dataSet.push({ 71 | name: path, 72 | shortName: name, 73 | parent: memberOf, 74 | type: doc.kind || '' 75 | }); 76 | 77 | }); 78 | 79 | var mainObj = { 80 | shortName: 'root', 81 | type: 'root', 82 | children: createTreeStructure(dataSet) 83 | }; 84 | 85 | var indexContent = fs.readFileSync(__dirname + '/tpl/index.tpl', 'utf-8'); 86 | var mainJsContent = fs.readFileSync(__dirname + '/tpl/index.js', 'utf-8'); 87 | var dataHtmlFile = path.join(opt.destination, '/diagram.html'); 88 | 89 | var html = template(indexContent)({ 90 | mainJS: mainJsContent, 91 | treeData: JSON.stringify(mainObj) 92 | }); 93 | 94 | if (!fs.existsSync(opt.destination)) { 95 | fs.mkdirSync(opt.destination); 96 | } 97 | 98 | fs.writeFileSync(dataHtmlFile, html, 'utf-8'); 99 | }; 100 | -------------------------------------------------------------------------------- /tpl/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jsdoc2diagram 3 | * 4 | * copied from http://bl.ocks.org/d3noob/8329404 5 | */ 6 | 7 | /*global d3*/ 8 | 9 | 'use strict'; 10 | 11 | var treeData = {}; 12 | var margin = {top: 20, right: 120, bottom: 20, left: 120}; 13 | var width; 14 | var height; 15 | var duration = 0; 16 | var tree = d3.layout.tree(); 17 | 18 | var diagonal = d3.svg.diagonal() 19 | .projection(function(d) { 20 | return [d.y, d.x]; 21 | }); 22 | 23 | var svg = d3.select('body').append('svg') 24 | .attr('id', 'diagram') 25 | .append('g') 26 | .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); 27 | 28 | function update(source) { 29 | // Compute the new tree layout. 30 | var i = 0; 31 | var nodes = tree.nodes(treeData).reverse(); 32 | var links = tree.links(nodes); 33 | 34 | // Normalize for fixed-depth. 35 | nodes.forEach(function(d) { 36 | d.y = d.depth * 180; 37 | }); 38 | 39 | // Update the nodes… 40 | var node = svg.selectAll('g.node') 41 | .data(nodes, function(d) { 42 | return d.id || (d.id = ++i); 43 | }); 44 | 45 | // Enter any new nodes at the parent's previous position. 46 | var nodeEnter = node.enter().append('g') 47 | .attr('class', 'node') 48 | .attr('transform', function(d) { 49 | return 'translate(' + source.y0 + ',' + source.x0 + ')'; 50 | }) 51 | .on('click', click); 52 | 53 | nodeEnter.append('text') 54 | .text(function(d) { 55 | return d.shortName + (d.type === 'function' ? '()' : ''); 56 | }) 57 | .attr('x', function(d) { 58 | return d.children || d._children ? -10 : 10; 59 | }) 60 | .attr('dy', '.35em') 61 | .attr('text-anchor', function(d) { 62 | return d.children || d._children ? 'end' : 'start'; 63 | }) 64 | .style('fill-opacity', 1e-6); 65 | 66 | // Transition nodes to their new position. 67 | var nodeUpdate = node.transition() 68 | .duration(duration) 69 | .attr('transform', function(d) { 70 | return 'translate(' + d.y + ',' + d.x + ')'; 71 | }); 72 | 73 | nodeUpdate.select('circle') 74 | .attr('r', 4.5); 75 | 76 | var allTextNodes = nodeEnter.select('text'); 77 | allTextNodes.each(function(d, index) { 78 | var textNodeParent = d3.select(allTextNodes[0][index].parentNode); 79 | if (d.type.match(/(root|class|function)/)) { 80 | textNodeParent.append('circle') 81 | .attr('r', 5) 82 | .attr('class', d.type); 83 | } else { 84 | textNodeParent.append('rect') 85 | .attr('width', 10) 86 | .attr('height', 10) 87 | .attr('x', -5) 88 | .attr('y', -5) 89 | .attr('class', d.type); 90 | } 91 | }); 92 | 93 | nodeUpdate.select('text') 94 | .style('fill-opacity', 1); 95 | 96 | // Transition exiting nodes to the parent's new position. 97 | var nodeExit = node.exit().transition() 98 | .duration(duration) 99 | .attr('transform', function(d) { 100 | return 'translate(' + source.y + ',' + source.x + ')'; 101 | }) 102 | .remove(); 103 | 104 | nodeExit.select('circle') 105 | .attr('r', 1e-6); 106 | 107 | nodeExit.select('text') 108 | .style('fill-opacity', 1e-6); 109 | 110 | // Update the links… 111 | var link = svg.selectAll('path.link') 112 | .data(links, function(d) { 113 | return d.target.id; 114 | }); 115 | 116 | // Enter any new links at the parent's previous position. 117 | link.enter().insert('path', 'g') 118 | .attr('class', 'link') 119 | .attr('d', function(d) { 120 | var o = {x: source.x0, y: source.y0}; 121 | return diagonal({source: o, target: o}); 122 | }); 123 | 124 | // Transition links to their new position. 125 | link.transition() 126 | .duration(duration) 127 | .attr('d', diagonal); 128 | 129 | // Transition exiting nodes to the parent's new position. 130 | link.exit().transition() 131 | .duration(duration) 132 | .attr('d', function(d) { 133 | var o = {x: source.x, y: source.y}; 134 | return diagonal({source: o, target: o}); 135 | }) 136 | .remove(); 137 | 138 | // Stash the old positions for transition. 139 | nodes.forEach(function(d) { 140 | d.x0 = d.x; 141 | d.y0 = d.y; 142 | }); 143 | 144 | // adjust the width/height 145 | setTimeout(function() { 146 | var d = d3.select('#diagram'); 147 | var w = svg[0][0].getBBox().width; 148 | var h = svg[0][0].getBBox().height; 149 | d[0][0].style.width = (w + 100); 150 | d[0][0].style.height = (h + 100); 151 | }, 100); 152 | } 153 | 154 | function init(data) { 155 | 156 | var nodes = tree.nodes(data).reverse(); 157 | var map = {}; 158 | svg.selectAll('g.node') 159 | .data(nodes, function(d) { 160 | var maxRows = d.parent ? d.parent.children.length : 1; 161 | var preMax = map[d.depth] || 1; 162 | map[d.depth] = Math.max(preMax, maxRows); 163 | return d.shortName; 164 | }); 165 | 166 | var maxRow = 1; 167 | var maxCols = 1; 168 | // get max rows 169 | for (var key in map) { 170 | maxRow = Math.max(maxRow, map[key]); 171 | maxCols++; 172 | } 173 | 174 | width = (maxCols * 200) - margin.right - margin.left; 175 | height = (maxRow * 30) - margin.top - margin.bottom; 176 | tree = d3.layout.tree().size([height, width]); 177 | 178 | d3.select('#diagram') 179 | .attr('width', width + margin.right + margin.left) 180 | .attr('height', height + margin.top + margin.bottom); 181 | 182 | data.x0 = height / 2; 183 | data.y0 = 0; 184 | treeData = data; 185 | update(data); 186 | } 187 | 188 | // Toggle children on click. 189 | function click(d) { 190 | if (d.children) { 191 | d._children = d.children; 192 | d.children = null; 193 | } else { 194 | d.children = d._children; 195 | d._children = null; 196 | } 197 | update(d); 198 | } 199 | -------------------------------------------------------------------------------- /tpl/index.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 36 | 37 | 38 | 39 | 40 | 41 | 46 | 47 | 48 | --------------------------------------------------------------------------------