Skip to content

Commit 958b7e0

Browse files
committed
initial draft
1 parent a1f2d82 commit 958b7e0

File tree

7 files changed

+388
-1
lines changed

7 files changed

+388
-1
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,5 @@ typings/
5757
# dotenv environment variables file
5858
.env
5959

60+
# MacOsX
61+
.DS_Store

README.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,38 @@
1-
# d3-dot-graph
1+
# d3-dot-graph
2+
3+
This module provides [D3js](#d3js) compatible library to parse and load files in graphviz [.dot](#dot) (graph description language) format.
4+
5+
## why?
6+
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.
7+
8+
In most cases it is enough, but I wanted to have nicer d3js visualization and interaction.
9+
10+
## usage
11+
12+
Usage is identical with well known ´d3.json([url], [callback])´ or ´d3.csv([url], [callback])´.
13+
14+
```js
15+
d3.dot("/path/to/graph.dot", function(error, graph) {
16+
if (error) throw error;
17+
console.log(JSON.stringify(graph, null, true));
18+
//{
19+
// "nodes": [ {"id": "Myriel"}, {"id": "Napoleon"}],
20+
// "links": [ {"source": "Myriel"}, {"target": "Napoleon"}]
21+
//}
22+
});
23+
```
24+
25+
## parser
26+
27+
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.
28+
29+
You can also use parser independently from loader and converter.
30+
31+
# build
32+
33+
34+
## notes
35+
36+
[#d3js]: https://www.d3js.org
37+
[#dot]: https://en.wikipedia.org/wiki/DOT_(graph_description_language)
38+
[#pegjs]: https://pegjs.org

example/index.html

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<!DOCTYPE html>
2+
<meta charset="utf-8">
3+
<style>
4+
5+
.links path {
6+
fill: none;
7+
stroke: #666;
8+
stroke-width: 1.5px;
9+
}
10+
11+
.nodes circle {
12+
fill: #ccc;
13+
stroke: #fff;
14+
stroke-width: 1.5px;
15+
}
16+
17+
.nodes text {
18+
fill: #000;
19+
font: 10px sans-serif;
20+
pointer-events: none;
21+
}
22+
23+
</style>
24+
<svg width="960" height="600"></svg>
25+
<script src="https://d3js.org/d3.v4.min.js"></script>
26+
<script src="/grammar/dot.js"></script>
27+
<script src="/d3-dot-graph.js"></script>
28+
<script>
29+
30+
var line = d3.line()
31+
.curve(d3.curveCatmullRom.alpha(0.5));
32+
33+
var svg = d3.select("svg"),
34+
width = +svg.attr("width"),
35+
height = +svg.attr("height");
36+
37+
var color = d3.scaleOrdinal(d3.schemeCategory20);
38+
39+
var simulation = d3.forceSimulation()
40+
.force("link", d3.forceLink().distance(100).id(function(d) { return d.id; }))
41+
.force("charge", d3.forceManyBody().strength(-10))
42+
.force("collide", d3.forceCollide(50))
43+
.force("center", d3.forceCenter(width / 2, height / 2));
44+
45+
d3.dot("summary.dot", function(graph) {
46+
//if (error) throw error;
47+
48+
// build the arrow.
49+
svg.append("svg:defs").selectAll("marker")
50+
.data(["end"]) // Different link/path types can be defined here
51+
.enter().append("svg:marker") // This section adds in the arrows
52+
.attr("id", String)
53+
.attr("viewBox", "0 -5 10 10")
54+
.attr("refX", 15)
55+
.attr("refY", -1.5)
56+
.attr("markerWidth", 6)
57+
.attr("markerHeight", 6)
58+
.attr("orient", "auto")
59+
.append("svg:path")
60+
.attr("d", "M0,-5L10,0L0,5");;
61+
62+
// add the links and the arrows
63+
var path = svg.append("svg:g").attr("class", "links").selectAll("path")
64+
.data(graph.links)
65+
.enter().append("svg:path")
66+
// .attr("class", function(d) { return "link " + d.type; })
67+
.attr("marker-end", "url(#end)");
68+
69+
var node = svg.append("g")
70+
.attr("class", "nodes")
71+
.selectAll("circle")
72+
.data(graph.nodes)
73+
.enter().append("g")
74+
75+
node
76+
.append("circle")
77+
.attr("r", 5)
78+
//.attr("fill", function(d) { return color(d.group); })
79+
.call(d3.drag()
80+
.on("start", dragstarted)
81+
.on("drag", dragged)
82+
.on("end", dragended));
83+
84+
// add the text
85+
node.append("text")
86+
.attr("x", 12)
87+
.attr("dy", ".35em")
88+
.text(function(d) { return d.id; });
89+
90+
simulation
91+
.nodes(graph.nodes)
92+
.on("tick", ticked);
93+
94+
simulation.force("link")
95+
.links(graph.links);
96+
97+
let linkGen = d3.linkVertical().x(function(d) { return d.x; })
98+
.y(function(d) { return d.y; });;
99+
100+
var linkRad = d3.linkRadial()
101+
.angle(function(d) { return d.x; })
102+
.radius(function(d) { return d.y; });
103+
104+
function ticked() {
105+
path.attr("d",
106+
(d)=>linkGen(d))
107+
//(d) => line([[d.source.x, d.source.y], [d.target.x, d.target.y]]));
108+
109+
node
110+
.attr("transform", function(d) {
111+
return "translate(" + d.x + "," + d.y + ")"; });
112+
}
113+
});
114+
115+
function dragstarted(d) {
116+
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
117+
d.fx = d.x;
118+
d.fy = d.y;
119+
}
120+
121+
function dragged(d) {
122+
d.fx = d3.event.x;
123+
d.fy = d3.event.y;
124+
}
125+
126+
function dragended(d) {
127+
if (!d3.event.active) simulation.alphaTarget(0);
128+
d.fx = null;
129+
d.fy = null;
130+
}
131+
</script>

example/summary.dot

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
digraph "summary" {
2+
"ui" -> "java.desktop (java.desktop)";
3+
"ui" -> "logic";
4+
"ui" -> "model";
5+
"logic" -> "data";
6+
"logic" -> "model";
7+
"app" -> "logic";
8+
"app" -> "ui";
9+
"model" -> "java.xml (java.xml)";
10+
"data" -> "java.sql (java.sql)";
11+
"data" -> "model";
12+
"api" -> "data";
13+
"api" -> "logic";
14+
}

package.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "d3-dot-graph",
3+
"version": "1.0.0",
4+
"description": "D3js compatible library to parse and load files in graphviz .dot (graph description language) format.",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "mocha test/*.js",
8+
"build": "pegjs --format globals --export-var d3dot grammar/dot.pegjs ",
9+
"start": "npm run build"
10+
},
11+
,
12+
"keywords": [
13+
"dot",
14+
"graphviz",
15+
"d3",
16+
"graph"
17+
],
18+
"repository": {
19+
"type": "git",
20+
"url": "git+https://github.com/gmamaladze/d3-dot-graph.git"
21+
},
22+
"author": "George Mamaladze",
23+
"license": "MIT",
24+
"bugs": {
25+
"url": "https://github.com/gmamaladze/d3-dot-graph/issues"
26+
},
27+
"homepage": "https://github.com/gmamaladze/d3-dot-graph#readme",
28+
"dependencies": {}
29+
}

src/d3-dot-graph.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
d3.dot =
2+
function(url, converter, callback) {
3+
if (arguments.length < 3) callback = converter, converter = simple;
4+
var r = d3
5+
.request(url)
6+
.mimeType("text/vnd.graphviz")
7+
.response(function(xhr) {
8+
return converter(d3dot.parse(xhr.responseText)); });
9+
return callback ? r.get(callback) : r;
10+
};
11+
12+
function simple(dotgraph) {
13+
14+
let nodeMap = {};
15+
function addNode(node) {
16+
if (nodeMap[node.id]) return;
17+
nodeMap[node.id] = node;
18+
}
19+
20+
if (dotgraph.length===0) return null;
21+
let fgraph = dotgraph[0];
22+
23+
fgraph.stmts.filter((s) => s.type === "node" ).forEach(addNode);
24+
let edges = fgraph.stmts.filter((s) => s.type === "edge" );
25+
let links = [];
26+
for(let i=0; i<edges.length; i++) {
27+
let edge = edges[i];
28+
for(let j=0; j<edge.elems.length-1; j++) {
29+
let curr = edge.elems[j];
30+
let next = edge.elems[j+1];
31+
let link = Object.assign({source:curr.id, target:next.id}, edge.attrs);
32+
links.push(link);
33+
addNode(curr);
34+
addNode(next);
35+
}
36+
}
37+
38+
let nodes = Object.values(nodeMap).map( (n) => Object.assign({id:n.id}, n.attrs));
39+
return {
40+
nodes,
41+
links
42+
};
43+
}

src/index.html

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<!DOCTYPE html>
2+
<meta charset="utf-8">
3+
<style>
4+
5+
.links path {
6+
fill: none;
7+
stroke: #666;
8+
stroke-width: 1.5px;
9+
}
10+
11+
.nodes circle {
12+
fill: #ccc;
13+
stroke: #fff;
14+
stroke-width: 1.5px;
15+
}
16+
17+
.nodes text {
18+
fill: #000;
19+
font: 10px sans-serif;
20+
pointer-events: none;
21+
}
22+
23+
</style>
24+
<svg width="960" height="600"></svg>
25+
<script src="https://d3js.org/d3.v4.min.js"></script>
26+
<script src="/grammar/dot.js"></script>
27+
<script src="/d3-dot-graph.js"></script>
28+
<script>
29+
30+
var line = d3.line()
31+
.curve(d3.curveCatmullRom.alpha(0.5));
32+
33+
var svg = d3.select("svg"),
34+
width = +svg.attr("width"),
35+
height = +svg.attr("height");
36+
37+
var color = d3.scaleOrdinal(d3.schemeCategory20);
38+
39+
var simulation = d3.forceSimulation()
40+
.force("link", d3.forceLink().distance(100).id(function(d) { return d.id; }))
41+
.force("charge", d3.forceManyBody().strength(-10))
42+
.force("collide", d3.forceCollide(50))
43+
.force("center", d3.forceCenter(width / 2, height / 2));
44+
45+
d3.dot("summary.dot", function(graph) {
46+
//if (error) throw error;
47+
48+
// build the arrow.
49+
svg.append("svg:defs").selectAll("marker")
50+
.data(["end"]) // Different link/path types can be defined here
51+
.enter().append("svg:marker") // This section adds in the arrows
52+
.attr("id", String)
53+
.attr("viewBox", "0 -5 10 10")
54+
.attr("refX", 15)
55+
.attr("refY", -1.5)
56+
.attr("markerWidth", 6)
57+
.attr("markerHeight", 6)
58+
.attr("orient", "auto")
59+
.append("svg:path")
60+
.attr("d", "M0,-5L10,0L0,5");;
61+
62+
// add the links and the arrows
63+
var path = svg.append("svg:g").attr("class", "links").selectAll("path")
64+
.data(graph.links)
65+
.enter().append("svg:path")
66+
// .attr("class", function(d) { return "link " + d.type; })
67+
.attr("marker-end", "url(#end)");
68+
69+
var node = svg.append("g")
70+
.attr("class", "nodes")
71+
.selectAll("circle")
72+
.data(graph.nodes)
73+
.enter().append("g")
74+
75+
node
76+
.append("circle")
77+
.attr("r", 5)
78+
//.attr("fill", function(d) { return color(d.group); })
79+
.call(d3.drag()
80+
.on("start", dragstarted)
81+
.on("drag", dragged)
82+
.on("end", dragended));
83+
84+
// add the text
85+
node.append("text")
86+
.attr("x", 12)
87+
.attr("dy", ".35em")
88+
.text(function(d) { return d.id; });
89+
90+
simulation
91+
.nodes(graph.nodes)
92+
.on("tick", ticked);
93+
94+
simulation.force("link")
95+
.links(graph.links);
96+
97+
let linkGen = d3.linkVertical().x(function(d) { return d.x; })
98+
.y(function(d) { return d.y; });;
99+
100+
var linkRad = d3.linkRadial()
101+
.angle(function(d) { return d.x; })
102+
.radius(function(d) { return d.y; });
103+
104+
function ticked() {
105+
path.attr("d",
106+
(d)=>linkGen(d))
107+
//(d) => line([[d.source.x, d.source.y], [d.target.x, d.target.y]]));
108+
109+
node
110+
.attr("transform", function(d) {
111+
return "translate(" + d.x + "," + d.y + ")"; });
112+
}
113+
});
114+
115+
function dragstarted(d) {
116+
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
117+
d.fx = d.x;
118+
d.fy = d.y;
119+
}
120+
121+
function dragged(d) {
122+
d.fx = d3.event.x;
123+
d.fy = d3.event.y;
124+
}
125+
126+
function dragended(d) {
127+
if (!d3.event.active) simulation.alphaTarget(0);
128+
d.fx = null;
129+
d.fy = null;
130+
}
131+
</script>

0 commit comments

Comments
 (0)