initial draft
This commit is contained in:
parent
a1f2d822ed
commit
958b7e0d0f
7 changed files with 388 additions and 1 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -57,3 +57,5 @@ typings/
|
||||||
# dotenv environment variables file
|
# dotenv environment variables file
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
# MacOsX
|
||||||
|
.DS_Store
|
||||||
|
|
|
||||||
37
README.md
37
README.md
|
|
@ -1 +1,38 @@
|
||||||
# d3-dot-graph
|
# d3-dot-graph
|
||||||
|
|
||||||
|
This module provides [D3js](#d3js) compatible library to parse and load files in graphviz [.dot](#dot) (graph description language) format.
|
||||||
|
|
||||||
|
## why?
|
||||||
|
While working on Java Platform Module System migration projects coming with Java 9 (as of August 2017), I am havily using jdeps which is generating DOT (.dot) files. These are usually visualized using dot tool of graphviz.
|
||||||
|
|
||||||
|
In most cases it is enough, but I wanted to have nicer d3js visualization and interaction.
|
||||||
|
|
||||||
|
## usage
|
||||||
|
|
||||||
|
Usage is identical with well known ´d3.json([url], [callback])´ or ´d3.csv([url], [callback])´.
|
||||||
|
|
||||||
|
```js
|
||||||
|
d3.dot("/path/to/graph.dot", function(error, graph) {
|
||||||
|
if (error) throw error;
|
||||||
|
console.log(JSON.stringify(graph, null, true));
|
||||||
|
//{
|
||||||
|
// "nodes": [ {"id": "Myriel"}, {"id": "Napoleon"}],
|
||||||
|
// "links": [ {"source": "Myriel"}, {"target": "Napoleon"}]
|
||||||
|
//}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## parser
|
||||||
|
|
||||||
|
The parser was generated using [PEG.js](#pegjs). The grammer is taken from here [cpettitt/graphlib-dot](https://github.com/cpettitt/graphlib-dot). Thanks to Chris Pettitt.
|
||||||
|
|
||||||
|
You can also use parser independently from loader and converter.
|
||||||
|
|
||||||
|
# build
|
||||||
|
|
||||||
|
|
||||||
|
## notes
|
||||||
|
|
||||||
|
[#d3js]: https://www.d3js.org
|
||||||
|
[#dot]: https://en.wikipedia.org/wiki/DOT_(graph_description_language)
|
||||||
|
[#pegjs]: https://pegjs.org
|
||||||
|
|
|
||||||
131
example/index.html
Normal file
131
example/index.html
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<style>
|
||||||
|
|
||||||
|
.links path {
|
||||||
|
fill: none;
|
||||||
|
stroke: #666;
|
||||||
|
stroke-width: 1.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nodes circle {
|
||||||
|
fill: #ccc;
|
||||||
|
stroke: #fff;
|
||||||
|
stroke-width: 1.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nodes text {
|
||||||
|
fill: #000;
|
||||||
|
font: 10px sans-serif;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<svg width="960" height="600"></svg>
|
||||||
|
<script src="https://d3js.org/d3.v4.min.js"></script>
|
||||||
|
<script src="/grammar/dot.js"></script>
|
||||||
|
<script src="/d3-dot-graph.js"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var line = d3.line()
|
||||||
|
.curve(d3.curveCatmullRom.alpha(0.5));
|
||||||
|
|
||||||
|
var svg = d3.select("svg"),
|
||||||
|
width = +svg.attr("width"),
|
||||||
|
height = +svg.attr("height");
|
||||||
|
|
||||||
|
var color = d3.scaleOrdinal(d3.schemeCategory20);
|
||||||
|
|
||||||
|
var simulation = d3.forceSimulation()
|
||||||
|
.force("link", d3.forceLink().distance(100).id(function(d) { return d.id; }))
|
||||||
|
.force("charge", d3.forceManyBody().strength(-10))
|
||||||
|
.force("collide", d3.forceCollide(50))
|
||||||
|
.force("center", d3.forceCenter(width / 2, height / 2));
|
||||||
|
|
||||||
|
d3.dot("summary.dot", function(graph) {
|
||||||
|
//if (error) throw error;
|
||||||
|
|
||||||
|
// build the arrow.
|
||||||
|
svg.append("svg:defs").selectAll("marker")
|
||||||
|
.data(["end"]) // Different link/path types can be defined here
|
||||||
|
.enter().append("svg:marker") // This section adds in the arrows
|
||||||
|
.attr("id", String)
|
||||||
|
.attr("viewBox", "0 -5 10 10")
|
||||||
|
.attr("refX", 15)
|
||||||
|
.attr("refY", -1.5)
|
||||||
|
.attr("markerWidth", 6)
|
||||||
|
.attr("markerHeight", 6)
|
||||||
|
.attr("orient", "auto")
|
||||||
|
.append("svg:path")
|
||||||
|
.attr("d", "M0,-5L10,0L0,5");;
|
||||||
|
|
||||||
|
// add the links and the arrows
|
||||||
|
var path = svg.append("svg:g").attr("class", "links").selectAll("path")
|
||||||
|
.data(graph.links)
|
||||||
|
.enter().append("svg:path")
|
||||||
|
// .attr("class", function(d) { return "link " + d.type; })
|
||||||
|
.attr("marker-end", "url(#end)");
|
||||||
|
|
||||||
|
var node = svg.append("g")
|
||||||
|
.attr("class", "nodes")
|
||||||
|
.selectAll("circle")
|
||||||
|
.data(graph.nodes)
|
||||||
|
.enter().append("g")
|
||||||
|
|
||||||
|
node
|
||||||
|
.append("circle")
|
||||||
|
.attr("r", 5)
|
||||||
|
//.attr("fill", function(d) { return color(d.group); })
|
||||||
|
.call(d3.drag()
|
||||||
|
.on("start", dragstarted)
|
||||||
|
.on("drag", dragged)
|
||||||
|
.on("end", dragended));
|
||||||
|
|
||||||
|
// add the text
|
||||||
|
node.append("text")
|
||||||
|
.attr("x", 12)
|
||||||
|
.attr("dy", ".35em")
|
||||||
|
.text(function(d) { return d.id; });
|
||||||
|
|
||||||
|
simulation
|
||||||
|
.nodes(graph.nodes)
|
||||||
|
.on("tick", ticked);
|
||||||
|
|
||||||
|
simulation.force("link")
|
||||||
|
.links(graph.links);
|
||||||
|
|
||||||
|
let linkGen = d3.linkVertical().x(function(d) { return d.x; })
|
||||||
|
.y(function(d) { return d.y; });;
|
||||||
|
|
||||||
|
var linkRad = d3.linkRadial()
|
||||||
|
.angle(function(d) { return d.x; })
|
||||||
|
.radius(function(d) { return d.y; });
|
||||||
|
|
||||||
|
function ticked() {
|
||||||
|
path.attr("d",
|
||||||
|
(d)=>linkGen(d))
|
||||||
|
//(d) => line([[d.source.x, d.source.y], [d.target.x, d.target.y]]));
|
||||||
|
|
||||||
|
node
|
||||||
|
.attr("transform", function(d) {
|
||||||
|
return "translate(" + d.x + "," + d.y + ")"; });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function dragstarted(d) {
|
||||||
|
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
|
||||||
|
d.fx = d.x;
|
||||||
|
d.fy = d.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragged(d) {
|
||||||
|
d.fx = d3.event.x;
|
||||||
|
d.fy = d3.event.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragended(d) {
|
||||||
|
if (!d3.event.active) simulation.alphaTarget(0);
|
||||||
|
d.fx = null;
|
||||||
|
d.fy = null;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
14
example/summary.dot
Normal file
14
example/summary.dot
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
digraph "summary" {
|
||||||
|
"ui" -> "java.desktop (java.desktop)";
|
||||||
|
"ui" -> "logic";
|
||||||
|
"ui" -> "model";
|
||||||
|
"logic" -> "data";
|
||||||
|
"logic" -> "model";
|
||||||
|
"app" -> "logic";
|
||||||
|
"app" -> "ui";
|
||||||
|
"model" -> "java.xml (java.xml)";
|
||||||
|
"data" -> "java.sql (java.sql)";
|
||||||
|
"data" -> "model";
|
||||||
|
"api" -> "data";
|
||||||
|
"api" -> "logic";
|
||||||
|
}
|
||||||
29
package.json
Normal file
29
package.json
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"name": "d3-dot-graph",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "D3js compatible library to parse and load files in graphviz .dot (graph description language) format.",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "mocha test/*.js",
|
||||||
|
"build": "pegjs --format globals --export-var d3dot grammar/dot.pegjs ",
|
||||||
|
"start": "npm run build"
|
||||||
|
},
|
||||||
|
,
|
||||||
|
"keywords": [
|
||||||
|
"dot",
|
||||||
|
"graphviz",
|
||||||
|
"d3",
|
||||||
|
"graph"
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/gmamaladze/d3-dot-graph.git"
|
||||||
|
},
|
||||||
|
"author": "George Mamaladze",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/gmamaladze/d3-dot-graph/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/gmamaladze/d3-dot-graph#readme",
|
||||||
|
"dependencies": {}
|
||||||
|
}
|
||||||
43
src/d3-dot-graph.js
vendored
Normal file
43
src/d3-dot-graph.js
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
d3.dot =
|
||||||
|
function(url, converter, callback) {
|
||||||
|
if (arguments.length < 3) callback = converter, converter = simple;
|
||||||
|
var r = d3
|
||||||
|
.request(url)
|
||||||
|
.mimeType("text/vnd.graphviz")
|
||||||
|
.response(function(xhr) {
|
||||||
|
return converter(d3dot.parse(xhr.responseText)); });
|
||||||
|
return callback ? r.get(callback) : r;
|
||||||
|
};
|
||||||
|
|
||||||
|
function simple(dotgraph) {
|
||||||
|
|
||||||
|
let nodeMap = {};
|
||||||
|
function addNode(node) {
|
||||||
|
if (nodeMap[node.id]) return;
|
||||||
|
nodeMap[node.id] = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dotgraph.length===0) return null;
|
||||||
|
let fgraph = dotgraph[0];
|
||||||
|
|
||||||
|
fgraph.stmts.filter((s) => s.type === "node" ).forEach(addNode);
|
||||||
|
let edges = fgraph.stmts.filter((s) => s.type === "edge" );
|
||||||
|
let links = [];
|
||||||
|
for(let i=0; i<edges.length; i++) {
|
||||||
|
let edge = edges[i];
|
||||||
|
for(let j=0; j<edge.elems.length-1; j++) {
|
||||||
|
let curr = edge.elems[j];
|
||||||
|
let next = edge.elems[j+1];
|
||||||
|
let link = Object.assign({source:curr.id, target:next.id}, edge.attrs);
|
||||||
|
links.push(link);
|
||||||
|
addNode(curr);
|
||||||
|
addNode(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let nodes = Object.values(nodeMap).map( (n) => Object.assign({id:n.id}, n.attrs));
|
||||||
|
return {
|
||||||
|
nodes,
|
||||||
|
links
|
||||||
|
};
|
||||||
|
}
|
||||||
131
src/index.html
Normal file
131
src/index.html
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<style>
|
||||||
|
|
||||||
|
.links path {
|
||||||
|
fill: none;
|
||||||
|
stroke: #666;
|
||||||
|
stroke-width: 1.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nodes circle {
|
||||||
|
fill: #ccc;
|
||||||
|
stroke: #fff;
|
||||||
|
stroke-width: 1.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nodes text {
|
||||||
|
fill: #000;
|
||||||
|
font: 10px sans-serif;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<svg width="960" height="600"></svg>
|
||||||
|
<script src="https://d3js.org/d3.v4.min.js"></script>
|
||||||
|
<script src="/grammar/dot.js"></script>
|
||||||
|
<script src="/d3-dot-graph.js"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var line = d3.line()
|
||||||
|
.curve(d3.curveCatmullRom.alpha(0.5));
|
||||||
|
|
||||||
|
var svg = d3.select("svg"),
|
||||||
|
width = +svg.attr("width"),
|
||||||
|
height = +svg.attr("height");
|
||||||
|
|
||||||
|
var color = d3.scaleOrdinal(d3.schemeCategory20);
|
||||||
|
|
||||||
|
var simulation = d3.forceSimulation()
|
||||||
|
.force("link", d3.forceLink().distance(100).id(function(d) { return d.id; }))
|
||||||
|
.force("charge", d3.forceManyBody().strength(-10))
|
||||||
|
.force("collide", d3.forceCollide(50))
|
||||||
|
.force("center", d3.forceCenter(width / 2, height / 2));
|
||||||
|
|
||||||
|
d3.dot("summary.dot", function(graph) {
|
||||||
|
//if (error) throw error;
|
||||||
|
|
||||||
|
// build the arrow.
|
||||||
|
svg.append("svg:defs").selectAll("marker")
|
||||||
|
.data(["end"]) // Different link/path types can be defined here
|
||||||
|
.enter().append("svg:marker") // This section adds in the arrows
|
||||||
|
.attr("id", String)
|
||||||
|
.attr("viewBox", "0 -5 10 10")
|
||||||
|
.attr("refX", 15)
|
||||||
|
.attr("refY", -1.5)
|
||||||
|
.attr("markerWidth", 6)
|
||||||
|
.attr("markerHeight", 6)
|
||||||
|
.attr("orient", "auto")
|
||||||
|
.append("svg:path")
|
||||||
|
.attr("d", "M0,-5L10,0L0,5");;
|
||||||
|
|
||||||
|
// add the links and the arrows
|
||||||
|
var path = svg.append("svg:g").attr("class", "links").selectAll("path")
|
||||||
|
.data(graph.links)
|
||||||
|
.enter().append("svg:path")
|
||||||
|
// .attr("class", function(d) { return "link " + d.type; })
|
||||||
|
.attr("marker-end", "url(#end)");
|
||||||
|
|
||||||
|
var node = svg.append("g")
|
||||||
|
.attr("class", "nodes")
|
||||||
|
.selectAll("circle")
|
||||||
|
.data(graph.nodes)
|
||||||
|
.enter().append("g")
|
||||||
|
|
||||||
|
node
|
||||||
|
.append("circle")
|
||||||
|
.attr("r", 5)
|
||||||
|
//.attr("fill", function(d) { return color(d.group); })
|
||||||
|
.call(d3.drag()
|
||||||
|
.on("start", dragstarted)
|
||||||
|
.on("drag", dragged)
|
||||||
|
.on("end", dragended));
|
||||||
|
|
||||||
|
// add the text
|
||||||
|
node.append("text")
|
||||||
|
.attr("x", 12)
|
||||||
|
.attr("dy", ".35em")
|
||||||
|
.text(function(d) { return d.id; });
|
||||||
|
|
||||||
|
simulation
|
||||||
|
.nodes(graph.nodes)
|
||||||
|
.on("tick", ticked);
|
||||||
|
|
||||||
|
simulation.force("link")
|
||||||
|
.links(graph.links);
|
||||||
|
|
||||||
|
let linkGen = d3.linkVertical().x(function(d) { return d.x; })
|
||||||
|
.y(function(d) { return d.y; });;
|
||||||
|
|
||||||
|
var linkRad = d3.linkRadial()
|
||||||
|
.angle(function(d) { return d.x; })
|
||||||
|
.radius(function(d) { return d.y; });
|
||||||
|
|
||||||
|
function ticked() {
|
||||||
|
path.attr("d",
|
||||||
|
(d)=>linkGen(d))
|
||||||
|
//(d) => line([[d.source.x, d.source.y], [d.target.x, d.target.y]]));
|
||||||
|
|
||||||
|
node
|
||||||
|
.attr("transform", function(d) {
|
||||||
|
return "translate(" + d.x + "," + d.y + ")"; });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function dragstarted(d) {
|
||||||
|
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
|
||||||
|
d.fx = d.x;
|
||||||
|
d.fy = d.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragged(d) {
|
||||||
|
d.fx = d3.event.x;
|
||||||
|
d.fy = d3.event.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragended(d) {
|
||||||
|
if (!d3.event.active) simulation.alphaTarget(0);
|
||||||
|
d.fx = null;
|
||||||
|
d.fy = null;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
Loading…
Add table
Reference in a new issue