Skip to content

Commit

Permalink
Merge pull request #63 from bpodgursky/master
Browse files Browse the repository at this point in the history
Helper file for rendering to d3 and corresponding demo files, v2
  • Loading branch information
cpettitt committed Aug 10, 2013
2 parents eb3727a + c32c557 commit 2de65ac
Show file tree
Hide file tree
Showing 5 changed files with 432 additions and 0 deletions.
26 changes: 26 additions & 0 deletions demo/dagre-d3-simple.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
text {
font-weight: 300;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
}

rect {
fill: #fff;
}

.node > rect {
stroke-width: 3px;
stroke: #333;
fill: none;
opacity: 0.5;
}

.edge rect {
fill: #fff
}

.edge path {
fill: none;
stroke: #333;
stroke-width: 1.5px;
}
285 changes: 285 additions & 0 deletions demo/dagre-d3-simple.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@

/*
* Render a pure graphviz definition to the svg specified by svg selector.
*/
function renderDotToD3(graphDef, svgSelector) {
var result = dagre.dot.toObjects(graphDef);
renderDagreObjsToD3({nodes: result.nodes, edges: result.edges}, svgSelector);
}

/*
* Render javascript edges and nodes to the svg. This method should be more
* convenient when data was serialized as json and node definitions would be duplicated
* if edges had direct references to nodes. See state-graph-js.html for example.
*/
function renderJSObjsToD3(nodeData, edgeData, svgSelector) {

var nodeRefs = [];
var edgeRefs = [];

edgeData.forEach(function(e){
edgeRefs.push({
source: nodeData[e.source],
target: nodeData[e.target],
label: e.label,
id: e.id
});
});

for (var nodeKey in nodeData) {
if (nodeData.hasOwnProperty(nodeKey)) {
nodeRefs.push(nodeData[nodeKey]);
}
}

renderDagreObjsToD3({nodes: nodeRefs, edges: edgeRefs}, svgSelector);
}

/*
* Render javscript objects to svg, as produced by dagre.dot.
*/
function renderDagreObjsToD3(graphData, svgSelector) {

var nodeData = graphData.nodes;
var edgeData = graphData.edges;

var svg = d3.select(svgSelector);

if (svg.select("#arrowhead").empty()) {
svg.append('svg:defs').append('svg:marker')
.attr('id', 'arrowhead')
.attr('viewBox', '0 0 10 10')
.attr('refX', 8)
.attr('refY', 5)
.attr('markerUnits', 'strokewidth')
.attr('markerWidth', 8)
.attr('markerHeight', 5)
.attr('orient', 'auto')
.attr('style', 'fill: #333')
.append('svg:path')
.attr('d', 'M 0 0 L 10 5 L 0 10 z');
}

svg.selectAll("g").remove();

var svgGroup = svg.append("g");
var nodes = svgGroup
.selectAll("g .node")
.data(nodeData);

var nodeEnter = nodes
.enter()
.append("g")
.attr("class", function (d) {
if (d.nodeclass) {
return "node " + d.nodeclass;
} else {
return "node";
}
})
.attr("id", function (d) {
return "node-" + d.id;
})
.each(function (d) {
d.nodePadding = 10;
});
nodeEnter.append("rect");
addLabels(nodeEnter);
nodes.exit().remove();

var edges = svgGroup
.selectAll("g .edge")
.data(edgeData);

var edgeEnter = edges
.enter()
.append("g")
.attr("class", "edge")
.attr("id", function (d) {
return "edge-" + d.id;
})
.each(function (d) {
d.nodePadding = 0;
});

edgeEnter
.append("path")
.attr("marker-end", "url(#arrowhead)");
addLabels(edgeEnter);
edges.exit().remove();

var labelGroup = svgGroup.selectAll("g.label");

var foLabel = labelGroup
.selectAll(".htmllabel")
// TODO find a better way to get the dimensions for foriegnObjects
.attr("width", "100000");

foLabel
.select("div")
.html(function (d) {
return d.label;
})
.each(function (d) {
d.width = this.clientWidth;
d.height = this.clientHeight;
d.nodePadding = 0;
});

foLabel
.attr("width", function (d) {
return d.width;
})
.attr("height", function (d) {
return d.height;
});

var textLabel = labelGroup
.filter(function (d) {
return d.label && d.label[0] !== "<";
});

textLabel
.select("text")
.attr("text-anchor", "left")
.append("tspan")
.attr("dy", "1em")
.text(function (d) {
return d.label;
});

labelGroup
.each(function (d) {
var bbox = this.getBBox();
d.bbox = bbox;

d.width = bbox.width + 2 * d.nodePadding;
d.height = bbox.height + 2 * d.nodePadding;

});

// Add zoom behavior to the SVG canvas
svgGroup.attr("transform", "translate(5, 5)");
svg.call(d3.behavior.zoom().on("zoom", function redraw() {
svgGroup.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}));

// Run the actual layout
dagre.layout()
.nodes(nodeData)
.edges(edgeData)
.run();

nodes
.attr("transform", function (d) {
return "translate(" + d.dagre.x + "," + d.dagre.y + ")";
})
.selectAll("g.node rect")
.attr("rx", 5)
.attr("ry", 5)
.attr("x", function (d) {
return -(d.bbox.width / 2 + d.nodePadding);
})
.attr("y", function (d) {
return -(d.bbox.height / 2 + d.nodePadding);
})
.attr("width", function (d) {
return d.width;
})
.attr("height", function (d) {
return d.height;
});

edges
.selectAll("path")
.attr("d", function (d) {
var points = d.dagre.points.slice(0);
points.unshift(dagre.util.intersectRect(d.source.dagre, points[0]));

var preTarget = points[points.length - 2];
var target = dagre.util.intersectRect(d.target.dagre, points[points.length - 1]);

// This shortens the line by a couple pixels so the arrowhead won't overshoot the edge of the target
var deltaX = preTarget.x - target.x;
var deltaY = preTarget.y - target.y;
var m = 2 / Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
points.push({
x: target.x + m * deltaX,
y: target.y + m * deltaY
}
);

return d3.svg.line()
.x(function (e) {
return e.x;
})
.y(function (e) {
return e.y;
})
.interpolate("bundle")
.tension(.8)
(points);
});

svgGroup
.selectAll("g.label rect")
.attr("x", function (d) {
return -d.nodePadding;
})
.attr("y", function (d) {
return -d.nodePadding;
})
.attr("width", function (d) {
return d.width;
})
.attr("height", function (d) {
return d.height;
});

nodes
.selectAll("g.label")
.attr("transform", function (d) {
return "translate(" + (-d.bbox.width / 2) + "," + (-d.bbox.height / 2) + ")";
});

edges
.selectAll("g.label")
.attr("transform", function (d) {
var points = d.dagre.points;

if (points.length > 1) {
var x = (points[0].x + points[1].x) / 2;
var y = (points[0].y + points[1].y) / 2;
return "translate(" + (-d.bbox.width / 2 + x) + "," + (-d.bbox.height / 2 + y) + ")";
} else {
return "translate(" + (-d.bbox.width / 2 + points[0].x) + "," + (-d.bbox.height / 2 + points[0].y) + ")";
}
});
}

function addLabels(selection) {

var labelGroup = selection
.append("g")
.attr("class", "label");
labelGroup.append("rect");

var foLabel = labelGroup
.filter(function (d) {
return d.label && d.label[0] === "<";
})
.append("foreignObject")
.attr("class", "htmllabel");

foLabel
.append("xhtml:div")
.style("float", "left");

labelGroup
.filter(function (d) {
return d.label && d.label[0] !== "<";
})
.append("text")
}
61 changes: 61 additions & 0 deletions demo/sentence-tokenization.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@

<link rel="stylesheet" type="text/css" href="dagre-d3-simple.css">

<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script type="text/javascript" src="../dagre.js"></script>
<script src="dagre-d3-simple.js"></script>

<div id="attach">
<svg id="svg-canvas" width=800 height=600></svg>
</div>

<style>
.type-TK > .label > rect {
fill: #00ffd0;
}
svg {
border: 1px solid #999;
}
</style>

<script>

var nodes = {
0: {"label": "TOP", "id": "0", "nodeclass": "type-TOP"},
1: {"label": "S", "id": "1", "nodeclass": "type-S"},
2: {"label": "NP", "id": "2", "nodeclass": "type-NP"},
3: {"label": "DT", "id": "3", "nodeclass": "t1ype-DT"},
4: {"label": "This", "id": "4", "nodeclass": "type-TK"},
5: {"label": "VP", "id": "5", "nodeclass": "type-VP"},
6: {"label": "VBZ", "id": "6", "nodeclass": "type-VBZ"},
7: {"label": "is", "id": "7", "nodeclass": "type-TK"},
8: {"label": "NP", "id": "8", "nodeclass": "type-NP"},
9: {"label": "DT", "id": "9", "nodeclass": "type-DT"},
10: {"label": "an", "id": "10", "nodeclass": "type-TK"},
11: {"label": "NN", "id": "11", "nodeclass": "type-NN"},
12: {"label": "example", "id": "12", "nodeclass": "type-TK"},
13: {"label": ".", "id": "13", "nodeclass": "type-."},
14: {"label": "sentence", "id": "14", "nodeclass": "type-TK"}
};

var edges =
[
{"source": 3, "target": 4, "id": "3-4"},
{"source": 2, "target": 3, "id": "2-3"},
{"source": 1, "target": 2, "id": "1-2"},
{"source": 6, "target": 7, "id": "6-7"},
{"source": 5, "target": 6, "id": "5-6"},
{"source": 9, "target": 10, "id": "9-10"},
{"source": 8, "target": 9, "id": "8-9"},
{"source": 11, "target": 12, "id": "11-12"},
{"source": 8, "target": 11, "id": "8-11"},
{"source": 5, "target": 8, "id": "5-8"},
{"source": 1, "target": 5, "id": "1-5"},
{"source": 13, "target": 14, "id": "13-14"},
{"source": 1, "target": 13, "id": "1-13"},
{"source": 0, "target": 1, "id": "0-1"}
];

renderJSObjsToD3(nodes, edges, "svg");

</script>
20 changes: 20 additions & 0 deletions demo/state-graph-dot.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<link rel="stylesheet" type="text/css" href="dagre-d3-simple.css">

<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script type="text/javascript" src="../dagre.js"></script>
<script src="dagre-d3-simple.js"></script>

<svg id="svg-canvas" width=800 height=600></svg>

<style>
#node-E rect {
fill: #acf;
}
svg {
border: 1px solid #999;
}
</style>

<script>
renderDotToD3('digraph {A [label="<div style=\'padding: 10px;\'>A <span style=\'font-size:32px\'>Big</span> <span style=\'color:red;\'>HTML</span> Source!</div>"];C;E [label="A blue sink"];A -> B -> C;B -> D -> E;C -> E;A -> D [label="<div>A multi-rank <span style=\'color:blue;\'>HTML</span> edge!</div>"];}', "svg");
</script>
Loading

0 comments on commit 2de65ac

Please sign in to comment.