This works probably only for blueprint/entity based APIs like loopback.io.
xxxxxxxxxx
<html lang="en">
<head>
<meta charset="UTF-8">
<meta charset="utf-8">
<style>
text {
font-size: 14px;
line-height: 14px;
}
.node rect {
fill: orange;
fill-opacity: 0.42;
stroke: #666;
stroke-opacity: 0.66;
stroke-width: 0.5px;
shape-rendering: geometricPrecision;
}
.node text {
fill: #333;
}
.edgePath path {
stroke: #666;
stroke-opacity: 0.5;
stroke-width: 1.5px;
}
body {
margin: 24px;
font-family: sans-serif;
}
hr {
border: 0;
height: 1px;
background: #ccc;
margin: 12px 0;
}
input {
width: 600px;
}
</style>
</head>
<body>
Swagger URL <input type="text" id="url" value="./swagger.json">
<hr>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/dagre-d3/0.4.17/dagre-d3.js"></script>
<script>
var SVG = d3.select('body').append('svg');
var ROOT = SVG.append('g');
var RENDER = new dagreD3.render();
var ICONS = {
get: 'arrow_forward',
post: 'arrow_backward',
put: 'chevron_left',
patch: 'first_page',
delete: 'close',
options: 'radio_button_unchecked',
head: 'sentiment_neutral'
};
var methods = ['get', 'post', 'put', 'path', 'delete', 'head', 'options'];
var exclude = ['findOne'];
function chart (data) {
var g = new dagreD3.graphlib.Graph()
.setGraph({
rankdir: 'LR',
nodesep: 24,
ranksep: 48
});
data.nodes.forEach(function (item, index) {
g.setNode(index, {
label: item,
height: 20,
rx: 0,
ry: 0,
paddingLeft: 10,
paddingRight: 10,
paddingTop: 2,
paddingBottom: 5
});
});
data.links.forEach(function (link) {
var s = data.nodes.indexOf(link[0]);
var t = data.nodes.indexOf(link[1]);
g.setEdge(s, t, {
lineInterpolate: 'bundle'
});
});
RENDER(ROOT, g);
ROOT.attr('transform', 'translate(' + [2, 2.5] + ')');
SVG.attr('height', ((g.graph().height > 0) ? g.graph().height : 0) + 4 + 14)
.attr('width', ((g.graph().width > 0) ? g.graph().width : 0) + 4);
setTimeout(function () {
try {
d3.select(self.frameElement).style('height', (48 + document.body.getBoundingClientRect().height) + 'px');
} catch (ign) {
}
}, 50);
}
function swaggerPathModels (swagger) {
var routes = {};
Object.keys(swagger.paths).forEach(function (path) {
var pathObject = swagger.paths[path];
var parts = path.split('/');
var parent = routes;
parts.forEach(function (part) {
if (exclude.indexOf(part) > -1) {
return;
}
var p = part.replace(/\{.*}/g, '');
if (p) {
parent[p] = parent[p] || {};
parent = parent[p];
}
});
methods.forEach(function (method) {
var op = pathObject[method];
if (op) {
parent.$models = parent.$models || {};
operationModels(op, parent.$models);
if (!Object.keys(parent.$models).length) {
delete parent.$models;
}
}
});
});
var nodes = {};
var links = {};
function iterate (route) {
if (!route.$models) {
return;
}
var sources = Object.keys(route.$models);
var targets = {};
Object.keys(route).forEach(function (subRoute) {
if (subRoute === '$models') {
return;
}
var obj = route[subRoute];
if (obj.$models) {
Object.keys(obj.$models).forEach(function (target) {
targets[target] = true;
});
iterate(obj);
}
});
sources.forEach(function (source) {
nodes[source] = true;
Object.keys(targets).forEach(function (target) {
nodes[target] = true;
var link = [source, target];
links[link.toString()] = link;
});
});
}
Object.keys(routes).forEach(function (route) {
iterate(routes[route]);
});
return {
nodes: Object.keys(nodes),
links: Object.keys(links).map(function (link) {
return links[link];
})
};
}
function operationModels (op, models) {
Object.keys(op.responses).forEach(function (response) {
var responseObject = op.responses[response];
var schema = responseObject.schema;
if (schema) {
var ref = schema.$ref || (schema.items && schema.items.$ref);
if (ref) {
ref = ref.replace('#/definitions/', '');
models[ref] = true;
}
}
});
}
function swaggerRelations (swagger) {
var relations = {};
var nodes = {};
Object.keys(swagger.paths).forEach(function (path) {
var pathObject = swagger.paths[path];
methods.forEach(function (method) {
var op = pathObject[method];
if (op) {
Object.keys(op.responses).forEach(function (response) {
var responseObject = op.responses[response];
var schema = responseObject.schema;
if (schema) {
var ref = schema.$ref || (schema.items && schema.items.$ref);
if (ref) {
ref = ref.replace('#/definitions/', '');
(op.tags || []).forEach(function (tag) {
if (tag !== ref) {
relations[tag] = relations[tag] || {};
relations[tag][ref] = true;
nodes[tag] = true;
nodes[ref] = true;
}
});
}
}
});
}
});
});
var links = [];
Object.keys(relations).forEach(function (source) {
Object.keys(relations[source]).forEach(function (target) {
links.push([source, target]);
});
});
return {
nodes: Object.keys(nodes),
links: links
};
}
function load (url) {
d3.select('#url').attr('disabled', true);
d3.json(url, function (error, swagger) {
d3.select('#url').attr('disabled', null);
if (error) throw error;
// var data = swaggerRelations(swagger);
var data = swaggerPathModels(swagger);
chart(data);
});
}
d3.select('#url').on('input', function () {
load(this.value);
});
d3.selectAll('a').on('click', function () {
d3.event.preventDefault();
d3.select('#url').attr('value', this.href);
load(this.href);
});
// load('https://apis-guru.github.io/api-models/instagram.com/1.0.0/swagger.json');
load('./swagger.json');
</script>
</body>
</html>
https://d3js.org/d3.v3.min.js
https://cdnjs.cloudflare.com/ajax/libs/dagre-d3/0.4.17/dagre-d3.js