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
|
||||
.env
|
||||
|
||||
# MacOsX
|
||||
.DS_Store
|
||||
|
|
|
|||
39
README.md
39
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