initial draft

This commit is contained in:
gmamaladze 2017-09-01 10:47:30 +02:00
parent a1f2d822ed
commit 958b7e0d0f
7 changed files with 388 additions and 1 deletions

2
.gitignore vendored
View file

@ -57,3 +57,5 @@ typings/
# dotenv environment variables file
.env
# MacOsX
.DS_Store

View file

@ -1 +1,38 @@
# 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
View 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
View 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
View 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
View 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
View 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>