提交 a919542f authored 作者: Christof Angermueller's avatar Christof Angermueller

Refactor and document

上级 406ed6f1
......@@ -36,5 +36,7 @@ Theano.suo
*.bak
.ipynb_checkpoints
doc/d3printing/GSoC/150724_opfrom/bundle/
doc/d3printing/GSoC/150724_opfrom/bundle2/
doc/d3viz/GSoC/150724_opfrom/bundle/
doc/d3viz/GSoC/150724_opfrom/bundle2/
doc/d3viz/GSoC/150819
doc/d3viz/GSoC/150820
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Couldn't import dot_parser, loading of dot files will not be possible.\n"
]
}
],
"source": [
"import numpy as np\n",
"import theano\n",
"import theano.tensor as T\n",
"import theano.d3printing.d3printing as d3p\n",
"\n",
"%load_ext autoreload\n",
"%autoreload 2"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# Logistic regression model\n",
"num_in = 64**2\n",
"num_out = 10\n",
"W = theano.shared(np.random.randn(num_in, num_out), name='W', borrow=True)\n",
"b = theano.shared(np.random.randn(num_out), name='b', borrow=True)\n",
"X = T.dmatrix('X')\n",
"y = T.nnet.softmax(X.dot(W) + b)\n",
"pred = y > 0.5"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The output file is available at logreg_y.html\n"
]
}
],
"source": [
"d3p.d3print(y, 'logreg_y.html')"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"[open](./logreg_y.html)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The output file is available at logreg_pred.html\n"
]
}
],
"source": [
"d3p.d3print(pred, 'logreg_pred.html')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[open](./logreg_pred.html)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.9"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<script type='text/javascript' src="http://cpettitt.github.io/project/dagre-d3/v0.1.5/dagre-d3.min.js"></script>
<script type='text/javascript' src="http://cpettitt.github.io/project/graphlib-dot/v0.4.10/graphlib-dot.min.js"></script>
</head>
<body>
<style>
.svg {
margin-left:auto;
margin-right:auto;
display:block;
position: fixed;
border: 0px solid black;
top:0%; left:0%; right:0% bottom=0%
}
.nodeRect {
stroke: black;
border: 3px solid black;
}
.nodeEllipse {
stroke: black;
border: 3px solid black;
}
.nodeText {
color: black;
}
.edge {
stroke: black;
stroke-width: 3px;
cursor: pointer;
}
.edgeLabelRect {
stroke: black;
border: 1px solid black;
fill: skyblue;
opacity: 0.9;
}
.edgeLabelText {
fill: black;
text-anchor: start;
}
.arrowHead {
stroke: black;
stroke-width: 3px;
fill: black;
}
</style>
<script type="text/javascript">
var width = 1200;
var height = 900;
// Global attributes
var pad = 10;
d3.select('body').select('svg').remove();
var svg = d3.select('body').append('svg')
.attr('class', 'svg')
.attr('width', '100%')
.attr('height', '100%');
var pane = svg.append('g');
// Definition head of edges
svg.append("defs").append("marker")
.attr("id", 'edgeArrow')
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("refX", 3)
.attr("refY", 3)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,0 L6,3 L0,6 Z")
.attr('style', 'arrowHead');
function textSize(text, attr) {
var t = svg.append('text').text(text);
if (typeof(attr) != 'undefined') {
for (a in attr) {
t.attr(a, attr[a]);
}
}
var bbox = t.node().getBBox();
t.remove();
return bbox;
}
var dotGraph;
var graph = {};
var nodes = [];
var edges = [];
// d3.text(path, function(dotGraphDef) {
var dotGraphDef = 'digraph G { graph [bb="0,0,728,476"]; "DimShuffle{x,x}" [height=0.5, pos="488,194", shape=ellipse, width=2.0339]; "Elemwise{gt,no_inplace}" [fillcolor="#FFAABB", height=0.5, pos="379,106", shape=ellipse, style=filled, width=2.8978]; "DimShuffle{x,x}" -> "Elemwise{gt,no_inplace}" [label="1 TensorType(float32, (True, True))", lp="570.5,150", pos="e,423.33,122.35 480.19,176.08 474.48,165.2 465.84,151.39 455,142 448.3,136.19 440.48,131.17 432.44,126.88"]; "val=0.5 TensorType(float32, scalar)" [fillcolor=limegreen, height=0.5, pos="510,282", shape=box, style=filled, width=3.0278]; "val=0.5 TensorType(float32, scalar)" -> "DimShuffle{x,x}" [label="TensorType(float32, scalar)", lp="579,238", pos="e,492.37,212.08 505.55,263.6 502.49,251.63 498.36,235.5 494.88,221.89"]; "DimShuffle{x,0}" [height=0.5, pos="119,370", shape=ellipse, width=2.0339]; "Elemwise{add,no_inplace}" [fillcolor="#FFAABB", height=0.5, pos="273,282", shape=ellipse, style=filled, width=3.0624]; "DimShuffle{x,0}" -> "Elemwise{add,no_inplace}" [label="1 TensorType(float64, row)", lp="229,326", pos="e,198,295.25 125.78,352.01 131.07,340.81 139.47,326.67 151,318 162.05,309.69 175.02,303.35 188.3,298.51"]; "name=b TensorType(float64, vector)" [fillcolor=limegreen, height=0.5, pos="111,458", shape=box, style=filled, width=3.0903]; "name=b TensorType(float64, vector)" -> "DimShuffle{x,0}" [label="TensorType(float64, vector)", lp="195,414", pos="e,117.41,388.08 112.62,439.6 113.72,427.75 115.2,411.82 116.46,398.29"]; dot [height=0.5, pos="355,370", shape=ellipse, width=0.75]; dot -> "Elemwise{add,no_inplace}" [label="0 TensorType(float64, matrix)", lp="410,326", pos="e,292.35,300.03 342.73,353.87 334.13,343.51 322.22,329.59 311,318 307.43,314.31 303.55,310.52 299.68,306.85"]; "name=X TensorType(float64, matrix)" [fillcolor=limegreen, height=0.5, pos="355,458", shape=box, style=filled, width=3.1667]; "name=X TensorType(float64, matrix)" -> dot [label="0 TensorType(float64, matrix)", lp="440,414", pos="e,355,388.08 355,439.6 355,427.75 355,411.82 355,398.29"]; "name=W TensorType(float64, matrix)" [fillcolor=limegreen, height=0.5, pos="603,458", shape=box, style=filled, width=3.2014]; "name=W TensorType(float64, matrix)" -> dot [label="1 TensorType(float64, matrix)", lp="643,414", pos="e,381.63,373 582.96,439.84 568.78,428.57 548.82,414.4 529,406 483.94,386.9 427.82,378.03 391.71,374.04"]; Softmax [height=0.5, pos="276,194", shape=ellipse, width=1.1472]; "Elemwise{add,no_inplace}" -> Softmax [label="TensorType(float64, matrix)", lp="355,238", pos="e,275.4,212.08 273.61,263.6 274.02,251.75 274.58,235.82 275.05,222.29"]; Softmax -> "Elemwise{gt,no_inplace}" [label="0 TensorType(float64, matrix)", lp="370,150", pos="e,315.56,120.32 274.93,175.6 275.18,164.8 277.29,151.26 285,142 290.89,134.93 298.36,129.28 306.5,124.78"]; "TensorType(int8, matrix)" [fillcolor=dodgerblue, height=0.5, pos="379,18", shape=box, style=filled, width=2.2014]; "Elemwise{gt,no_inplace}" -> "TensorType(int8, matrix)" [label="TensorType(int8, matrix)", lp="450.5,62", pos="e,379,36.084 379,87.597 379,75.746 379,59.817 379,46.292"]; } ';
dotGraph = graphlibDot.parse(dotGraphDef);
// Calculate width and height
var posMax = [0, 0];
for (var nodeId in dotGraph._nodes) {
var node = dotGraph._nodes[nodeId];
node.value.pos = node.value.pos.split(',').map(function(d) {return parseInt(d);});
node.value.width = parseInt(node.value.width);
node.value.height = parseInt(node.value.height);
posMax[0] = Math.max(posMax[0], node.value.pos[0] + node.value.width);
posMax[1] = Math.max(posMax[1], node.value.pos[1] + node.value.height);
}
dotWidth = posMax[0];
dotHeight = posMax[1];
svg.attr('viewBox', '0,0,' + width + ',' + height);
var scaleDotX = d3.scale.linear().domain([0, dotWidth + 100]).range([0, width]);
var scaleDotY = d3.scale.linear().domain([-25, dotHeight + 100]).range([0, height]);
// Parse nodes
var i = 0;
for (nodeId in dotGraph._nodes) {
var node = dotGraph._nodes[nodeId];
node.index = i++;
// x, y is center of node (not corner)
node.x = scaleDotX(node.value.pos[0]);
node.y = scaleDotY(dotHeight - (node.value.pos[1] + node.value.height));
var size = textSize(node.value.label, {'class': 'nodeText'});
node.value.width = size.width + 2 * pad;
node.value.height = size.height + 2 * pad;
node.value.cx = node.value.width / 2;
node.value.cy = node.value.height / 2;
node.fixed = true;
nodes.push(node);
dotGraph._nodes[nodeId] = node;
}
// Parse edges
for (edgeId in dotGraph._edges) {
var edge = dotGraph._edges[edgeId];
edge.source = dotGraph._nodes[edge.u].index;
edge.target = dotGraph._nodes[edge.v].index;
var size = textSize(edge.value.label, {'class': 'edgeLabelText'});
edge.value.width = size.width + 2 * pad;
edge.value.height = size.height + 2 * pad;
edges.push(edge);
dotGraph._edges[edgeId] = edge;
}
// Setup graph
graph['nodes'] = nodes;
graph['edges'] = edges;
var isEdgeOver = false;
var isEdgeLabelOver = false;
// Add edges
edges = pane.append('g').attr('id', 'edges').selectAll('path').data(graph['edges']).enter().append('path')
.attr('class', 'edge')
.attr('marker-mid', 'url(#edgeArrow)')
.on('mouseover', function(d) {
var edgeLabel = pane.select('#edgeLabels .' + d.id);
edgeLabel.attr('style', 'display: inline');
})
.on('mouseout', function(d) {
if (!isEdgeLabelOver) {
var edgeLabel = pane.select('#edgeLabels .' + d.id);
edgeLabel.attr('style', 'display: none');
}
});
// Add edge labels
edgeLabels = pane.append('g').attr('id', 'edgeLabels').selectAll('g').data(graph['edges']).enter().append('g')
.attr('style', 'display: none')
.attr('class', function(d) {return d.id;})
.on('mouseover', function(d) {
isEdgeLabelOver = true;
var edgeLabel = d3.select(this);
edgeLabel.attr('style', 'display: inline');
})
.on('mouseout', function(d) {
var edgeLabel = d3.select(this);
edgeLabel.attr('style', 'display: none');
isEdgeLabelOver = false;
});
var edgeLabelsRect = edgeLabels.append('rect')
.attr('class', 'edgeLabelRect')
.attr('fill', 'white')
.attr('width', function(d) {return d.value.width;})
.attr('height', function(d) {return d.value.height;})
.attr('rx', 5)
.attr('ry', 5);
var edgeLabelsText = edgeLabels.append('text')
.attr('class', 'edgeLabelText')
.attr('x', function(d) {return pad;})
.attr('dy', function(d) {return d.value.height - pad - 5;})
.text(function(d) {return d.value.label;});
// Add nodes
nodes = pane.append('g').attr('id', 'nodes').selectAll('g').data(graph['nodes']).enter().append('g');
function fillColor(f) {
return typeof(f) == 'undefined' ? 'white' : f;
}
nodes.each(function(d) {
sel = d3.select(this);
var shape;
if (d.value.shape == 'ellipse') {
shape = sel.append('ellipse')
.attr('class', 'nodeEllipse')
.attr('cx', d.value.cx)
.attr('cy', d.value.cy)
.attr('rx', d.value.width * 0.6)
.attr('ry', d.value.height * 0.6);
} else {
shape = sel.append('rect')
.attr('class', 'nodeRect')
.attr('width', d.value.width)
.attr('height', d.value.height);
}
shape.attr('fill', fillColor(d.value.fillcolor));
});
var nodesText = nodes.append('text')
.attr('class', 'nodeText')
.attr('x', pad)
.attr('dy', function(d) {return d.value.height - pad - 5;})
.text(function(d) {return d.value.label;});
// Update graph
function updateGraph() {
// Update nodes
nodes.attr('transform', function(d) { return 'translate(' + (d.x - d.value.cx) + ' ' + (d.y - d.value.cy) + ')'; });
// Update edges
edges.attr('d', function(d) {
return 'M' + d.source.x + ',' + d.source.y + ' L' + 0.5 * (d.source.x + d.target.x) + ',' + 0.5 * (d.source.y + d.target.y) + ' L' + d.target.x + ',' + d.target.y;
});
// Update edge labels
edgeLabels.attr('transform', function(d) {
return 'translate(' + (0.5 * (d.source.x + d.target.x - d.value.width)) + ',' + (0.5 * (d.source.y + d.target.y - d.value.height)) + ')';
});
}
// Drag-start event handler
function dragStart(d) {
d3.event.sourceEvent.stopPropagation();
d.fixed = true;
}
// Zoom and translate event handler
function zoom(d) {
pane.attr('transform', 'translate(' + d3.event.translate + ') scale(' + d3.event.scale + ')');
}
// Force layout
var layout = d3.layout.force()
.nodes(graph['nodes'])
.links(graph['edges'])
.size([width, height])
.gravity(0.00)
.charge(-1000)
.linkDistance(50)
.linkStrength(0.1)
.on('tick', updateGraph);
// Drag behavour
var drag = layout.drag()
.on('dragstart', dragStart);
nodes.call(drag);
// Zoom behaviour
var bZoom = d3.behavior.zoom()
.scaleExtent([0.2, 8])
.on('zoom', zoom);
svg.call(bZoom);
// Start force layout
layout.start();
// });
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<script type='text/javascript' src="http://cpettitt.github.io/project/dagre-d3/v0.1.5/dagre-d3.min.js"></script>
<script type='text/javascript' src="http://cpettitt.github.io/project/graphlib-dot/v0.4.10/graphlib-dot.min.js"></script>
</head>
<body>
<style>
.svg {
margin-left:auto;
margin-right:auto;
display:block;
position: fixed;
border: 0px solid black;
top:0%; left:0%; right:0% bottom=0%
}
.nodeRect {
stroke: black;
border: 3px solid black;
}
.nodeEllipse {
stroke: black;
border: 3px solid black;
}
.nodeText {
color: black;
}
.edge {
stroke: black;
stroke-width: 3px;
cursor: pointer;
}
.edgeLabelRect {
stroke: black;
border: 1px solid black;
fill: skyblue;
opacity: 0.9;
}
.edgeLabelText {
fill: black;
text-anchor: start;
}
.arrowHead {
stroke: black;
stroke-width: 3px;
fill: black;
}
</style>
<script type="text/javascript">
var width = 1200;
var height = 900;
// Global attributes
var pad = 10;
d3.select('body').select('svg').remove();
var svg = d3.select('body').append('svg')
.attr('class', 'svg')
.attr('width', '100%')
.attr('height', '100%');
var pane = svg.append('g');
// Definition head of edges
svg.append("defs").append("marker")
.attr("id", 'edgeArrow')
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("refX", 3)
.attr("refY", 3)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,0 L6,3 L0,6 Z")
.attr('style', 'arrowHead');
function textSize(text, attr) {
var t = svg.append('text').text(text);
if (typeof(attr) != 'undefined') {
for (a in attr) {
t.attr(a, attr[a]);
}
}
var bbox = t.node().getBBox();
t.remove();
return bbox;
}
var dotGraph;
var graph = {};
var nodes = [];
var edges = [];
// d3.text(path, function(dotGraphDef) {
var dotGraphDef = 'digraph G { graph [bb="0,0,763,388"]; "DimShuffle{x,0}" [height=0.5, pos="600,282", shape=ellipse, width=2.0339]; "Elemwise{add,no_inplace}" [fillcolor="#FFAABB", height=0.5, pos="474,194", shape=ellipse, style=filled, width=3.0624]; "DimShuffle{x,0}" -> "Elemwise{add,no_inplace}" [label="1 TensorType(float64, row)", lp="648,238", pos="e,516.94,210.65 587.13,264.21 578.18,253.39 565.47,239.59 552,230 544.1,224.38 535.16,219.35 526.22,214.97"]; "name=b TensorType(float64, vector)" [fillcolor=limegreen, height=0.5, pos="607,370", shape=box, style=filled, width=3.0903]; "name=b TensorType(float64, vector)" -> "DimShuffle{x,0}" [label="TensorType(float64, vector)", lp="684,326", pos="e,601.39,300.08 605.58,351.6 604.62,339.75 603.32,323.82 602.22,310.29"]; dot [height=0.5, pos="362,282", shape=ellipse, width=0.75]; dot -> "Elemwise{add,no_inplace}" [label="0 TensorType(float64, matrix)", lp="467,238", pos="e,414.72,209.23 365,263.89 367.74,252.92 372.81,239.1 382,230 388.76,223.31 396.93,217.87 405.58,213.46"]; "name=X TensorType(float64, matrix)" [fillcolor=limegreen, height=0.5, pos="114,370", shape=box, style=filled, width=3.1667]; "name=X TensorType(float64, matrix)" -> dot [label="0 TensorType(float64, matrix)", lp="273,326", pos="e,335.37,285 134.04,351.84 148.22,340.57 168.18,326.4 188,318 233.06,298.9 289.18,290.03 325.29,286.04"]; "name=W TensorType(float64, matrix)" [fillcolor=limegreen, height=0.5, pos="362,370", shape=box, style=filled, width=3.2014]; "name=W TensorType(float64, matrix)" -> dot [label="1 TensorType(float64, matrix)", lp="447,326", pos="e,362,300.08 362,351.6 362,339.75 362,323.82 362,310.29"]; Softmax [height=0.5, pos="474,106", shape=ellipse, width=1.1472]; "Elemwise{add,no_inplace}" -> Softmax [label="TensorType(float64, matrix)", lp="554,150", pos="e,474,124.08 474,175.6 474,163.75 474,147.82 474,134.29"]; "TensorType(float64, matrix) id=6" [fillcolor=dodgerblue, height=0.5, pos="474,18", shape=box, style=filled, width=2.8403]; Softmax -> "TensorType(float64, matrix) id=6" [label="TensorType(float64, matrix)", lp="554,62", pos="e,474,36.084 474,87.597 474,75.746 474,59.817 474,46.292"]; } ';
dotGraph = graphlibDot.parse(dotGraphDef);
// Calculate width and height
var posMax = [0, 0];
for (var nodeId in dotGraph._nodes) {
var node = dotGraph._nodes[nodeId];
node.value.pos = node.value.pos.split(',').map(function(d) {return parseInt(d);});
node.value.width = parseInt(node.value.width);
node.value.height = parseInt(node.value.height);
posMax[0] = Math.max(posMax[0], node.value.pos[0] + node.value.width);
posMax[1] = Math.max(posMax[1], node.value.pos[1] + node.value.height);
}
dotWidth = posMax[0];
dotHeight = posMax[1];
svg.attr('viewBox', '0,0,' + width + ',' + height);
var scaleDotX = d3.scale.linear().domain([0, dotWidth + 100]).range([0, width]);
var scaleDotY = d3.scale.linear().domain([-25, dotHeight + 100]).range([0, height]);
// Parse nodes
var i = 0;
for (nodeId in dotGraph._nodes) {
var node = dotGraph._nodes[nodeId];
node.index = i++;
// x, y is center of node (not corner)
node.x = scaleDotX(node.value.pos[0]);
node.y = scaleDotY(dotHeight - (node.value.pos[1] + node.value.height));
var size = textSize(node.value.label, {'class': 'nodeText'});
node.value.width = size.width + 2 * pad;
node.value.height = size.height + 2 * pad;
node.value.cx = node.value.width / 2;
node.value.cy = node.value.height / 2;
node.fixed = true;
nodes.push(node);
dotGraph._nodes[nodeId] = node;
}
// Parse edges
for (edgeId in dotGraph._edges) {
var edge = dotGraph._edges[edgeId];
edge.source = dotGraph._nodes[edge.u].index;
edge.target = dotGraph._nodes[edge.v].index;
var size = textSize(edge.value.label, {'class': 'edgeLabelText'});
edge.value.width = size.width + 2 * pad;
edge.value.height = size.height + 2 * pad;
edges.push(edge);
dotGraph._edges[edgeId] = edge;
}
// Setup graph
graph['nodes'] = nodes;
graph['edges'] = edges;
var isEdgeOver = false;
var isEdgeLabelOver = false;
// Add edges
edges = pane.append('g').attr('id', 'edges').selectAll('path').data(graph['edges']).enter().append('path')
.attr('class', 'edge')
.attr('marker-mid', 'url(#edgeArrow)')
.on('mouseover', function(d) {
var edgeLabel = pane.select('#edgeLabels .' + d.id);
edgeLabel.attr('style', 'display: inline');
})
.on('mouseout', function(d) {
if (!isEdgeLabelOver) {
var edgeLabel = pane.select('#edgeLabels .' + d.id);
edgeLabel.attr('style', 'display: none');
}
});
// Add edge labels
edgeLabels = pane.append('g').attr('id', 'edgeLabels').selectAll('g').data(graph['edges']).enter().append('g')
.attr('style', 'display: none')
.attr('class', function(d) {return d.id;})
.on('mouseover', function(d) {
isEdgeLabelOver = true;
var edgeLabel = d3.select(this);
edgeLabel.attr('style', 'display: inline');
})
.on('mouseout', function(d) {
var edgeLabel = d3.select(this);
edgeLabel.attr('style', 'display: none');
isEdgeLabelOver = false;
});
var edgeLabelsRect = edgeLabels.append('rect')
.attr('class', 'edgeLabelRect')
.attr('fill', 'white')
.attr('width', function(d) {return d.value.width;})
.attr('height', function(d) {return d.value.height;})
.attr('rx', 5)
.attr('ry', 5);
var edgeLabelsText = edgeLabels.append('text')
.attr('class', 'edgeLabelText')
.attr('x', function(d) {return pad;})
.attr('dy', function(d) {return d.value.height - pad - 5;})
.text(function(d) {return d.value.label;});
// Add nodes
nodes = pane.append('g').attr('id', 'nodes').selectAll('g').data(graph['nodes']).enter().append('g');
function fillColor(f) {
return typeof(f) == 'undefined' ? 'white' : f;
}
nodes.each(function(d) {
sel = d3.select(this);
var shape;
if (d.value.shape == 'ellipse') {
shape = sel.append('ellipse')
.attr('class', 'nodeEllipse')
.attr('cx', d.value.cx)
.attr('cy', d.value.cy)
.attr('rx', d.value.width * 0.6)
.attr('ry', d.value.height * 0.6);
} else {
shape = sel.append('rect')
.attr('class', 'nodeRect')
.attr('width', d.value.width)
.attr('height', d.value.height);
}
shape.attr('fill', fillColor(d.value.fillcolor));
});
var nodesText = nodes.append('text')
.attr('class', 'nodeText')
.attr('x', pad)
.attr('dy', function(d) {return d.value.height - pad - 5;})
.text(function(d) {return d.value.label;});
// Update graph
function updateGraph() {
// Update nodes
nodes.attr('transform', function(d) { return 'translate(' + (d.x - d.value.cx) + ' ' + (d.y - d.value.cy) + ')'; });
// Update edges
edges.attr('d', function(d) {
return 'M' + d.source.x + ',' + d.source.y + ' L' + 0.5 * (d.source.x + d.target.x) + ',' + 0.5 * (d.source.y + d.target.y) + ' L' + d.target.x + ',' + d.target.y;
});
// Update edge labels
edgeLabels.attr('transform', function(d) {
return 'translate(' + (0.5 * (d.source.x + d.target.x - d.value.width)) + ',' + (0.5 * (d.source.y + d.target.y - d.value.height)) + ')';
});
}
// Drag-start event handler
function dragStart(d) {
d3.event.sourceEvent.stopPropagation();
d.fixed = true;
}
// Zoom and translate event handler
function zoom(d) {
pane.attr('transform', 'translate(' + d3.event.translate + ') scale(' + d3.event.scale + ')');
}
// Force layout
var layout = d3.layout.force()
.nodes(graph['nodes'])
.links(graph['edges'])
.size([width, height])
.gravity(0.00)
.charge(-1000)
.linkDistance(50)
.linkStrength(0.1)
.on('tick', updateGraph);
// Drag behavour
var drag = layout.drag()
.on('dragstart', dragStart);
nodes.call(drag);
// Zoom behaviour
var bZoom = d3.behavior.zoom()
.scaleExtent([0.2, 8])
.on('zoom', zoom);
svg.call(bZoom);
// Start force layout
layout.start();
// });
</script>
</body>
</html>
function flipAxes(nodes) {
var size = [0, 0];
for (var i in nodes) {
var node = nodes[i];
size[0] = Math.max(size[0], node.pos[0] + node.width);
size[1] = Math.max(size[1], node.pos[1] + node.height);
}
for (var i in nodes) {
var node = nodes[i];
node.pos[1] = size[1] - (node.pos[1] + node.height);
}
}
function processDotGraph(dotGraph) {
dotGraph.rnodes = {};
for (var nodeId in dotGraph._nodes) {
var node = dotGraph._nodes[nodeId];
node.id = nodeId;
node.isCluster = nodeId.startsWith('cluster');
if (!node.isCluster) {
dotGraph.rnodes[nodeId] = node;
}
}
var i = 0;
for (var nodeId in dotGraph.rnodes) {
var node = dotGraph._nodes[nodeId];
node.pos = node.pos.split(',').map(function(d) {return parseInt(d);});
var size = textSize(node.label, {'class': 'nodeText'});
node.width = size.width + 2 * pad;
node.height = size.height + 2 * pad;
node.cx = node.width / 2;
node.cy = node.height / 2;
node.hasChilds = exists(node.subg);
node.showChilds = false;
if (exists(node.profile)) {
node.profile = parseProfile(node.profile);
isProfiled = true;
}
if (exists(node.tag)) {
node.tag = parseList(node.tag);
}
if (exists(node.subg_map_inputs)) {
node.subg_map_inputs = eval(node.subg_map_inputs)
}
if (exists(node.subg_map_outputs)) {
node.subg_map_outputs = eval(node.subg_map_outputs)
}
}
flipAxes(dotGraph.rnodes);
// Offset and scale positions
var posMin = [Infinity, Infinity];
for (var i in dotGraph.rnodes) {
var node = dotGraph._nodes[i];
posMin[0] = Math.min(posMin[0], node.pos[0]);
posMin[1] = Math.min(posMin[1], node.pos[1]);
}
for (var i in dotGraph.rnodes) {
var node = dotGraph._nodes[i];
var pos = node.pos;
pos[0] -= posMin[0];
pos[1] -= posMin[1];
pos[0] = 1.2 * pos[0];
pos[1] = 1.2 * pos[1];
}
var edges = dotGraph.edges();
for (var i in edges) {
var edge = dotGraph.edge(edges[i]);
var size = textSize(edge.label, {'class': 'edgeLabelText'});
edge.width = size.width + 2 * pad;
edge.height = size.height + 2 * pad;
if (!exists(edge.color)) {
edge.color = 'black';
}
switch (edge.color) {
case 'dodgerblue':
edge.type = 'b';
break;
case 'red':
edge.type = 'r';
break;
default:
edge.type = 'n';
}
}
}
function traverseChilds(dotGraph, nodes, groups, parent) {
var preId = '';
var ref = undefined;
var group = {'id': groups.length, 'nodes': [], 'parent': parent};
if (exists(parent)) {
ref = parent.value.subg;
group.parent = parent;
group.nodes.push(parent);
parent.group = group;
}
groups.push(group);
var childs = dotGraph.children(ref);
for (var i in childs) {
var child = dotGraph.node(childs[i]);
if (child.isCluster) {
continue;
}
var node = {
'id': child.id,
'value': child,
'index': nodes.length,
'fixed': fixedDefault,
'group': group,
'isParent': child.showChilds,
'parent': parent
};
nodes.push(node);
if (child.showChilds) {
traverseChilds(dotGraph, nodes, groups, node);
} else {
group.nodes.push(node);
}
group.childs = [];
for (var i = group.id + 1; i < groups.length; ++i) {
group.childs.push(groups[i].id);
}
}
}
function groupSize(nodes) {
var minPos = [Infinity, Infinity];
var maxPos = [-Infinity, -Infinity];
for (var i in nodes) {
var node = nodes[i];
if (node.isParent) {
continue;
}
minPos[0] = Math.min(minPos[0], node.value.pos[0]);
minPos[1] = Math.min(minPos[1], node.value.pos[1]);
maxPos[0] = Math.max(maxPos[0], node.value.pos[0] + node.value.width);
maxPos[1] = Math.max(maxPos[1], node.value.pos[1] + node.value.height);
}
return [maxPos[0] - minPos[0], maxPos[1] - minPos[1]];
}
function forceGraph(dotGraph, prevGraph) {
var graph = {'nodes': [], 'groups': []};
traverseChilds(dotGraph, graph.nodes, graph.groups);
graph.nodesd = {};
for (var i in graph.nodes) {
var node = graph.nodes[i];
graph.nodesd[node.id] = node;
}
graph.groupsd = {};
for (var i in graph.groups) {
var group = graph.groups[i];
graph.groupsd[group.id] = group;
}
graph.nodesp = graph.nodes.filter(function(d) {return d.isParent;});
graph.nodesn = graph.nodes.filter(function(d) {return !d.isParent;});
for (i in graph.groups) {
var group = graph.groups[i];
group.size = groupSize(group.nodes);
var parent = group.parent;
if (exists(parent)) {
var prevParent = prevGraph.nodesd[group.parent.id];
if (exists(prevParent)) {
group.pos = [prevParent.x, prevParent.y];
} else {
group.pos = parent.value.pos.slice(0);
}
group.pos[0] += parent.value.cx;
group.pos[1] += parent.value.cy;
} else {
group.pos = [group.size[0] / 2, group.size[1] / 2];
}
var min = [Infinity, Infinity];
for (var j in group.nodes) {
var node = group.nodes[j];
if (!node.isParent) {
min[0] = Math.min(min[0], node.value.pos[0]);
min[1] = Math.min(min[0], node.value.pos[1]);
}
}
for (var j in group.nodes) {
var node = group.nodes[j];
if (!node.isParent) {
node.x = group.pos[0] - group.size[0] / 2 + node.value.pos[0] - min[0];
node.y = group.pos[1] - group.size[1] / 2 + node.value.pos[1] - min[1];
}
}
}
graph.size = graph.groups[0].size;
// Reuse previous positions
if (exists(prevGraph)) {
for (var i in graph.nodes) {
var node = graph.nodes[i];
var prevNode;
prevNode = prevGraph.nodesd[node.id];
if (exists(prevNode)) {
node.x = prevNode.x;
node.y = prevNode.y;
node.fixed = prevNode.fixed;
} else {
for (var j in prevGraph.groups) {
var group = prevGraph.groups[j];
if (exists(group.parent) && group.parent.id == node.id) {
node.x = group.pos[0] + group.size[0] / 2;
node.y = group.pos[1] + group.size[1] / 2;
}
}
}
}
}
// Edges
graph.edges = [];
for (var i in graph.nodesn) {
for (var j in graph.nodesn) {
var source = graph.nodesn[i];
var target = graph.nodesn[j];
var dotEdge = dotGraph.edge(source.value.id, target.value.id);
if (exists(dotEdge)) {
var edge = {};
edge.source = parseInt(source.index);
edge.target = parseInt(target.index);
edge.value = dotEdge;
graph.edges.push(edge);
}
function redirectEdges(map, dotEdge) {
for (var k in map) {
var kmap = map[k];
if (kmap[0] == source.id && kmap[1] == target.id) {
var edge = {};
edge.source = parseInt(source.index);
edge.target = parseInt(target.index);
edge.value = dotEdge;
graph.edges.push(edge);
}
}
}
var map = undefined;
if (exists(target.parent)) {
var parent = target.parent;
var dotEdge = dotGraph.edge(source.id, parent.id);
if (exists(dotEdge)) {
map = parent.value.subg_map_inputs;
redirectEdges(map, dotEdge);
}
}
if (exists(source.parent)) {
var parent = source.parent;
var dotEdge = dotGraph.edge(parent.id, target.id);
if (exists(dotEdge)) {
map = parent.value.subg_map_outputs;
redirectEdges(map, dotEdge);
}
}
}
}
return graph;
}
function convexHulls(graph, offset) {
var hulls = [];
offset = offset || 20;
for (var i in graph.groups) {
var group = graph.groups[i];
if (!exists(group.parent)) {
continue;
}
var points = [];
for (var j in group.nodes) {
var node = group.nodes[j];
if (!node.isParent) {
points.push([node.x - node.value.cx - offset, node.y - node.value.cy - offset]);
points.push([node.x - node.value.cx - offset, node.y + node.value.cy + offset]);
points.push([node.x + node.value.cx + offset, node.y - node.value.cy - offset]);
points.push([node.x + node.value.cx + offset, node.y + node.value.cy + offset]);
}
}
for (var k in group.childs) {
var nodes = graph.groupsd[group.childs[k]].nodes;
for (var j in nodes) {
var node = nodes[j];
if (!node.isParent) {
points.push([node.x - node.value.cx - offset, node.y - node.value.cy - offset]);
points.push([node.x - node.value.cx - offset, node.y + node.value.cy + offset]);
points.push([node.x + node.value.cx + offset, node.y - node.value.cy - offset]);
points.push([node.x + node.value.cx + offset, node.y + node.value.cy + offset]);
}
}
}
hulls.push({group: i, path: d3.geom.hull(points)});
}
return hulls;
}
function drawCluster(d) {
return curve(d.path); // 0.8
}
function setupGraph() {
if (isProfiled) {
d3.select('body').select('#menu').append('input')
.attr('name', 'tColors')
.attr('type', 'button')
.attr('value', 'Toggle profile colors')
.attr('onclick', "toggleColors()");
maxProfilePer = 0;
for (i in graph.nodes) {
var p = graph.nodes[i].value.profile;
if (exists(p)) {
maxProfilePer = Math.max(maxProfilePer, p[0] / p[1]);
}
}
}
var isEdgeOver = false;
var isEdgeLabelOver = false;
var dragHulls = d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", function(d) {
d3.event.sourceEvent.stopPropagation();
d3.event.sourceEvent.preventDefault();
layout.stop();
})
.on("drag", function dragged(d) {
var group = graph.groups[d.group];
for (var i in group.nodes) {
var node = group.nodes[i];
node.x += d3.event.dx;
node.y += d3.event.dy;
node.px += d3.event.dx;
node.py += d3.event.dy;
}
group.pos[0] += d3.event.dx;
group.pos[1] += d3.event.dy;
for (var k in group.childs) {
var cgroup = graph.groupsd[group.childs[k]];
var nodes = cgroup.nodes;
for (var j in nodes) {
var node = nodes[j];
node.x += d3.event.dx;
node.y += d3.event.dy;
node.px += d3.event.dx;
node.py += d3.event.dy;
cgroup.pos[0] += d3.event.dx;
cgroup.pos[1] += d3.event.dy;
}
}
updateGraph();
})
.on('dragend', function(d) {layout.resume();});
graph.hulls = convexHulls(graph);
hulls = pane.selectAll('#hulls').remove();
hulls = pane.append('g').attr('id', 'hulls')
.selectAll('path')
.data(graph.hulls).enter()
.append('path')
.attr('class', 'hull')
.attr('d', drawCluster)
.call(dragHulls);
hulls.on('dblclick', function(d) {
var group = graph.groups[d.group];
group.parent.value.showChilds = !group.parent.value.showChilds;
if (!group.parent.value.showChilds) {
for (i in group.childs) {
var child = graph.groupsd[group.childs[i]];
child.parent.value.showChilds = false;
}
}
graph = forceGraph(dotGraph, graph);
setupGraph();
});
// Add edges
edges = pane.selectAll('#edges').remove();
edges = pane.append('g').attr('id', 'edges')
.selectAll('path').data(graph.edges).enter().append('path')
.attr('class', 'edge')
.attr('stroke', function(d) {return d.value.color;})
.attr('marker-mid', function(d) { return 'url(#edgeArrow_' + d.value.type + ')';});
edges.on('mouseover', function(d) {
var edge = d3.select(this);
edge.transition()
.duration(200)
.style('opacity', 1.0);
edgeDiv.transition()
.duration(200)
.style('opacity', .9);
edgeDiv
.html(d.value.label)
.style('left', (d3.event.pageX) + 'px')
.style('top', (d3.event.pageY - 28) + 'px');
});
edges.on('mouseout', function(d) {
var edge = d3.select(this);
edge.transition()
.duration(200)
.style('opacity', 0.4);
edgeDiv.transition()
.duration(200)
.style('opacity', 0);
});
// Add nodes
pane.selectAll('#nodes').remove();
nodes = pane.append('g').attr('id', 'nodes')
.selectAll('g').data(graph.nodesn).enter().append('g');
updateNodes();
updateGraph();
nodes.on('dblclick', function(d) {
if (d.value.hasChilds) {
d.value.showChilds = !d.value.showChilds;
graph = forceGraph(dotGraph, graph);
if (!fixedDefault && d.value.showChilds) {
var n = dotGraph.neighbors(d.id);
for (i in n) {
graph.nodesd[n[i]].fixed = false;
}
}
setupGraph();
}
});
nodes.on('mouseover', function(node) {
// Highlight incoming edges
edges.each(function (d, i) {
var edge = d3.select(this);
if (d.source == node || d.target == node) {
edge.transition()
.duration(200)
.style('opacity', 1.0);
}
});
// Show node details if node is not edited as has profiling information
if (!isEditNode) {
nodeInfo.transition()
.duration(200)
.style('opacity', .9);
nodeInfo
.html(formatNodeInfos(node))
.style('left', (d3.event.pageX) + 30 + 'px')
.style('top', (d3.event.pageY - 28) + 'px');
}
});
nodes.on('mouseout', function(node) {
edges.each(function (d, i) {
var edge = d3.select(this);
if (d.source.index == node.index || d.target.index == node.index) {
edge.transition()
.duration(200)
.style('opacity', 0.4);
}
});
hideNodeInfo();
});
nodes.on('contextmenu', d3.contextMenu(menuItems));
// Force layout
layout = d3.layout.force()
.nodes(graph.nodes)
.links(graph.edges)
.size(graph.size)
.linkDistance(function(d) {
return 300;
})
.charge(-600)
.linkStrength(1)
.gravity(0.05)
.friction(0.5)
.on('tick', updateGraph)
.start();
// Drag behavour
var drag = layout.drag()
.on('dragstart', function(d) {
d3.event.sourceEvent.stopPropagation();
d3.event.sourceEvent.preventDefault();
d.fixed = true;
});
nodes.call(drag);
}
function length(x1, y1, x2, y2) {
return Math.sqrt(Math.pow(x1-x2, 2) + Math.pow(y1-y2, 2));
}
function pathPos(x1, y1, x2, y2, c) {
x = (1 - c) * x1 + c * x2;
y = (1 - c) * y1 + c * y2;
p = x + ',' + y;
return p;
}
function collide(node) {
var eps = 10;
var nx1 = node.x - node.value.cx - eps;
var nx2 = node.x + node.value.cx + eps;
var ny1 = node.y - node.value.cy - eps;
var ny2 = node.y + node.value.cy + eps;
return function(quad, x1, y1, x2, y2) {
var point = quad.point;
if (point && (point != node) && !point.fixed && ! node.fixed) {
var px1 = point.x - point.value.cx;
var px2 = point.x + point.value.cx;
var py1 = point.y - point.value.cy;
var py2 = point.y + point.value.cy;
if (!(px1 > nx2 || px2 < nx1 || py1 >= ny2 || py2 <= ny1)) {
var eta = 0.1;
if (px1 < nx1) {
// move quad to left
var d = eta * (px2 - nx1);
point.x -= d;
node.x += d;
} else {
var d = eta * (nx2 - px1);
point.x += d;
node.x -= d;
}
if (py1 < ny1) {
// move quad to top
var d = eta * (py2 - ny1);
point.y -= d;
node.y += d;
} else {
var d = eta * (ny2 - py1);
point.y += d;
node.y -= d;
}
}
}
return x1 > nx2 || x2 < nx1 || y1 >= ny2 || y2 <= ny1;
};
}
function updateGraph() {
var q = d3.geom.quadtree(graph.nodes);
for (var i in graph.nodes) {
q.visit(collide(graph.nodes[i]));
}
graph.hulls = convexHulls(graph);
hulls.data(graph.hulls)
.attr('d', drawCluster);
// Update nodes
nodes.attr('transform', function(d) { return 'translate(' + (d.x - d.value.cx) + ' ' + (d.y - d.value.cy) + ')'; });
// Update edges
edges.attr('d', function(d) {
var dist = 100;
var l = length(d.source.x, d.source.y, d.target.x, d.target.y);
var n = Math.max(2, Math.floor(l / dist));
var marker = [];
for (var i = 1; i < n; ++i) {
marker.push(i / n);
}
var markerPos = marker.map(function(c) {return pathPos(d.source.x, d.source.y, d.target.x, d.target.y, c);});
var markerPos = ' L' + markerPos.join(' L');
return 'M' + d.source.x + ',' + d.source.y + markerPos + ' L' + d.target.x + ',' + d.target.y;
});
}
function toggleColors() {
colorProfile = !colorProfile;
updateNodes();
updateGraph();
}
function textSize(text, attr) {
var t = svg.append('text').text(text);
if (typeof(attr) != 'undefined') {
for (a in attr) {
t.attr(a, attr[a]);
}
}
var bbox = t.node().getBBox();
t.remove();
return bbox;
}
function assert(condition, message) {
if (!condition) {
throw message || "Assertion failed";
}
}
function exists(x) {
return typeof(x) != 'undefined';
}
function replaceAll(str, find, replace) {
return str.replace(new RegExp(find, 'g'), replace);
}
function parseList(s) {
var h = ['(', ')', '[', ']', '<', '>'];
for (var i = 0; i < h.length; ++i) {
s = s.replace(h[i], '');
}
s = replaceAll(s, "'", "");
s = replaceAll(s, ' ', '');
s = s.split(',');
return s;
}
function parseProfile(s) {
var p = parseList(s);
p = p.map(function(x) { return parseFloat(x); });
return p;
}
function linspace(start, end, len) {
var d = (end - start) / (len - 1);
var rv = [start];
for (i = 1; i < len; ++i) {
rv.push(rv[i - 1] + d);
}
return rv;
}
function profileColor(per) {
var s = d3.scale.linear()
.domain(linspace(0, maxProfilePer, profileColors.length))
.range(profileColors)
.interpolate(d3.interpolateRgb);
return s(per);
}
function nodeFillColor(d) {
if (colorProfile) {
var p = d.value.profile;
if (d.value.node_type == 'apply' && exists(p)) {
return profileColor(d.value.profile[0] / d.value.profile[1]);
} else {
return 'white';
}
} else {
return typeof(d.value.fillcolor) == 'undefined' ? 'white' : d.value.fillcolor;
}
}
function formatTime(sec) {
var s;
if (sec < 0.1) {
s = (sec * 1000).toFixed(1) + 'ms';
} else {
s = sec.toFixed(1) + 's';
}
return s;
}
function formatNodeInfos(node) {
var v = node.value;
var s = '<b><center>' + v.label + '</center></b><hr>';
s += '<b>Node:</b> ' + replaceAll(v.node_type, '_', ' ') + ' node';
if (exists(v.dtype)) {
s += '</br>';
s += '<b>Type:</b> <source>' + v.dtype + '</source>';
}
if (exists(v.apply_op)) {
s += '</br>';
s += '<b>Apply:</b> <source>' + v.apply_op + '</source>';
}
if (exists(v.tag)) {
s += '<p>';
s += '<b>Location:</b> <source>' + v.tag[1] + ': ' + v.tag[0] + '</source><br>';
s += '<b>Definition:</b> <source>' + v.tag[2] + '</source><br>';
s += '</p>';
}
var p = v.profile;
if (exists(p)) {
s += '<p>';
s += '<b>Time:</b> ' + formatTime(p[0]) + '<br>';
s += '<b>Time:</b> ' + (p[0] / p[1] * 100).toFixed(1) + '%';
s += '</p>';
}
return s;
}
function updateNode(d, node) {
var shape;
if (d.value.shape == 'ellipse') {
node.selectAll('ellipse').remove();
shape = node.append('ellipse')
.attr('class', 'nodeEllipse')
.attr('cx', d.value.cx)
.attr('cy', d.value.cy)
.attr('rx', d.value.width * 0.6)
.attr('ry', d.value.height * 0.6);
} else {
node.selectAll('rect').remove();
shape = node.append('rect')
.attr('class', 'nodeRect')
.attr('width', d.value.width)
.attr('height', d.value.height);
}
shape.attr('fill', nodeFillColor(d));
node.selectAll('text').remove();
var text = node.append('text')
.attr('class', 'nodeText')
.attr('x', pad)
.attr('dy', function(d) {return d.value.height - pad - 5;})
.text(function(d) {return d.value.label;});
if (d.value.hasChilds) {
node.style('cursor', 'pointer');
}
}
function updateNodes() {
nodes.each(function(d) {
var node = d3.select(this);
updateNode(d, node);
});
}
function hideNodeInfo() {
nodeInfo.transition()
.duration(200)
.style('opacity', 0);
}
function setNodeSize(node) {
var size = textSize(node.value.label, {'class': 'nodeText'});
node.value.width = size.width + 2 * pad;
node.value.height = size.height + 2 * pad;
node.value.cx = node.value.width / 2;
node.value.cy = node.value.height / 2;
}
function editNode(elm, d) {
var node = d3.select(elm);
var pos = elm.getBBox();
if (d3.event.defaultPrevented) return;
isEditNode = true;
hideNodeInfo();
var form = node.append('foreignObject')
.attr('x', pos.x)
.attr('y', pos.y)
.attr('width', d.value.width)
.attr('height', 25);
var input = form.append('xhtml:form').append('input')
.attr('style', 'width: ' + d.value.width + 'px')
.attr('value', function() {
this.focus();
return d.value.label;
})
.on('blur', function() {
d.value.label = input.node().value;
setNodeSize(d);
updateNode(d, node);
form.remove(); // TODO: check this
isEditNode = false;
})
.on('keypress', function() {
if (!d3.event) {
d3.event = window.event;
}
var event = d3.event;
if (event.keyCode == 13) {
if (typeof(event.cancelBubble)) {
event.cancelBubble = true;
}
if (event.stopPropagation) {
event.stopPropagation();
}
event.preventDefault();
d.value.label = input.node().value;
setNodeSize(d);
updateNode(d, node);
form.remove(); // TODO: check this
isEditNode = false;
}
});
}
function releaseNode(d) {
d.fixed = false;
layout.start();
}
function releaseNodes() {
graph['nodes'].forEach (function (d) {
d.fixed = false;
});
layout.start();
}
function resetNodes() {
layout.stop();
var nodes = graph['nodes'];
nodes.forEach(function (node, i){
nodes[i].x = scaleDotX(node.value.pos[0]);
nodes[i].y = scaleDotY(dotGraph.values.height - (node.value.pos[1] + node.value.height));
nodes[i].px = nodes[i].x;
nodes[i].py = nodes[i].y;
nodes[i].fixed = true;
});
updateGraph();
layout.start();
}
"""Dynamic visualization of Theano graphs."""
"""Dynamic visualization of Theano graphs.
# Authors: Christof Angermueller <cangermueller@gmail.com>
Author: Christof Angermueller <cangermueller@gmail.com
"""
import os
import os.path as pt
from glob import glob
import shutil
from formatting import GraphFormatter
from formatting import PyDotFormatter
__path__ = pt.dirname(pt.realpath(__file__))
def replace_patterns(x, replace):
""" Replaces patterns defined by `replace` in x."""
""" Replace patterns `replace` in x."""
for from_, to in replace.items():
x = x.replace(str(from_), str(to))
return x
def d3write(fct, path, *args, **kwargs):
# Convert theano graph to pydot graph and write to file
gf = GraphFormatter(*args, **kwargs)
g = gf.to_pydot(fct)
"""Convert theano graph to pydot graph and write to file."""
gf = PyDotFormatter(*args, **kwargs)
g = gf(fct)
g.write_dot(path)
def d3print(fct, outfile, *args, **kwargs):
"""Creates dynamic graph visualization using d3.js javascript library.
def d3viz(fct, outfile, *args, **kwargs):
"""Create dynamic graph visualization using d3.js javascript library.
:param fct: A compiled Theano function, variable, apply or a list of
variables
variables.
:param outfile: The output file
:param args, kwargs: Additional parameters passed to PyDotFromatter.
"""
outdir = pt.dirname(outfile)
......
"""Functions for formatting Theano compute graphs.
Author: Christof Angermueller <cangermueller@gmail.com
"""
import numpy as np
import logging
import re
......@@ -21,76 +26,63 @@ from theano.compile import builders
_logger = logging.getLogger("theano.printing")
class PyDotFormatter(object):
class GraphFormatter(object):
def __init__(self):
"""Formatter class
def __init__(self, compact=True):
"""Converts compute to to `pydot` object.
:param compact: if True, will remove intermediate var that don't have name.
:param with_ids: Print the toposort index of the node in the node name.
and an index number in the variable ellipse.
:param scan_graphs: if true it will plot the inner graph of each scan op
in files with the same name as the name given for the main
file to which the name of the scan op is concatenated and
the index in the toposort of the scan.
This index can be printed with the option with_ids.
:param var_with_name_simple: If true and a variable have a name,
we will print only the variable name.
Otherwise, we concatenate the type to the var name.
:param compact: if True, will remove intermediate variables without
name.
"""
self.compact = True
self.with_ids = False
self.scan_graphs = True
self.var_with_name_simple = False
self.compact = compact
self.node_colors = {'input': 'limegreen',
'constant_input': 'SpringGreen',
'shared_input': 'YellowGreen',
'output': 'dodgerblue',
'unused': 'lightgrey'}
self.apply_colors = {'GpuFromHost': 'red',
'HostFromGpu': 'red',
'Scan': 'yellow',
'Shape': 'cyan',
'IfElse': 'magenta',
'Elemwise': '#FFAABB', # dark pink
'Subtensor': '#FFAAFF', # purple
'Alloc': '#FFAA22'} # orange
self.max_label_size = 70
self.node_prefix = 'n'
def add_node(self, node):
assert not node in self.nodes
_id = '%s%d' % (self.node_prefix, len(self.nodes) + 1)
self.nodes[node] = _id
'HostFromGpu': 'red',
'Scan': 'yellow',
'Shape': 'cyan',
'IfElse': 'magenta',
'Elemwise': '#FFAABB', # dark pink
'Subtensor': '#FFAAFF', # purple
'Alloc': '#FFAA22'} # orange
self.__node_prefix = 'n'
def __add_node(self, node):
"""Add new node to node list and return unique id."""
assert node not in self.__nodes
_id = '%s%d' % (self.__node_prefix, len(self.__nodes) + 1)
self.__nodes[node] = _id
return _id
def node_id(self, node):
if node in self.nodes:
return self.nodes[node]
def __node_id(self, node):
"""Return unique node id."""
if node in self.__nodes:
return self.__nodes[node]
else:
return self.add_node(node)
return self.__add_node(node)
def to_pydot(self, fct, graph=None):
def __call__(self, fct, graph=None):
"""Create pydot graph from function.
:param fct: a compiled Theano function, a Variable, an Apply or
a list of Variable.
:param graph: `pydot` graph to which nodes are added. Creates new one
if not given.
"""
if graph is None:
graph = pd.Dot()
self.nodes = {}
self.var_str = {}
self.all_strings = set()
self.apply_name_cache = {}
self.__nodes = {}
profile = None
if isinstance(fct, Function):
mode = fct.maker.mode
if (not isinstance(mode, ProfileMode) or
fct not in mode.profile_stats):
mode = None
fct not in mode.profile_stats):
mode = None
if mode:
profile = mode.profile_stats[fct]
else:
......@@ -114,24 +106,15 @@ class GraphFormatter(object):
if not pydot_imported:
raise RuntimeError("Failed to import pydot. You must install pydot"
" for `pydotprint` to work.")
# Update the inputs that have an update function
self.input_update = {}
# Here outputs can be the original list, as we should not change
# it, we must copy it.
outputs = list(outputs)
if isinstance(fct, Function):
for i in reversed(fct.maker.expanded_inputs):
if i.update is not None:
self.input_update[outputs.pop()] = i
# Loop over apply nodes
for node in topo:
nparams = {}
node_id = self.node_id(node)
nparams['name'] = node_id
__node_id = self.__node_id(node)
nparams['name'] = __node_id
nparams['label'] = apply_label(node)
nparams['profile'] = self.apply_profile(node, profile)
nparams['profile'] = apply_profile(node, profile)
nparams['node_type'] = 'apply'
nparams['apply_op'] = apply_op(node)
......@@ -144,70 +127,81 @@ class GraphFormatter(object):
nparams['fillcolor'] = use_color
nparams['type'] = 'colored'
pd_node = self.dict_to_pdnode(nparams)
pd_node = dict_to_pdnode(nparams)
graph.add_node(pd_node)
# Loop over input nodes
for id, var in enumerate(node.inputs):
var_id = self.node_id(var.owner if var.owner else var)
var_id = self.__node_id(var.owner if var.owner else var)
if var.owner is None:
vparams = {}
vparams['name'] = var_id
vparams['label'] = self.var_label(var)
vparams['label'] = var_label(var)
vparams['node_type'] = 'input'
if isinstance(var, gof.Constant):
vparams['node_type'] = 'constant_input'
elif isinstance(var, theano.tensor.sharedvar.TensorSharedVariable):
elif isinstance(var, theano.tensor.sharedvar.
TensorSharedVariable):
vparams['node_type'] = 'shared_input'
vparams['dtype'] = type_to_str(var.type)
vparams['tag'] = self.var_tag(var)
vparams['tag'] = var_tag(var)
vparams['style'] = 'filled'
vparams['fillcolor'] = self.node_colors[vparams['node_type']]
vparams['fillcolor'] = self.node_colors[
vparams['node_type']]
vparams['shape'] = 'ellipse'
pd_var = self.dict_to_pdnode(vparams)
pd_var = dict_to_pdnode(vparams)
graph.add_node(pd_var)
edge_params = {}
if hasattr(node.op, 'view_map') and id in reduce(list.__add__, node.op.view_map.values(), []):
if hasattr(node.op, 'view_map') and \
id in reduce(list.__add__,
node.op.view_map.values(), []):
edge_params['color'] = self.node_colors['output']
elif hasattr(node.op, 'destroy_map') and id in reduce(
list.__add__, node.op.destroy_map.values(), []):
edge_params['color'] = 'red'
elif hasattr(node.op, 'destroy_map') and \
id in reduce(list.__add__,
node.op.destroy_map.values(), []):
edge_params['color'] = 'red'
edge_label = vparams['dtype']
if len(node.inputs) > 1:
edge_label = str(id) + ' ' + edge_label
pdedge = pd.Edge(var_id, node_id, label=edge_label,
pdedge = pd.Edge(var_id, __node_id, label=edge_label,
**edge_params)
graph.add_edge(pdedge)
# Loop over ouput nodes
for id, var in enumerate(node.outputs):
var_id = self.node_id(var)
var_id = self.__node_id(var)
if var in outputs or len(var.clients) == 0:
vparams = {}
vparams['name'] = var_id
vparams['label'] = self.var_label(var)
vparams['label'] = var_label(var)
vparams['node_type'] = 'output'
vparams['dtype'] = type_to_str(var.type)
vparams['tag'] = self.var_tag(var)
vparams['tag'] = var_tag(var)
vparams['style'] = 'filled'
if len(var.clients) == 0:
vparams['fillcolor'] = self.node_colors['unused']
else:
vparams['fillcolor'] = self.node_colors['output']
vparams['shape'] = 'box'
pd_var = self.dict_to_pdnode(vparams)
pd_var = dict_to_pdnode(vparams)
graph.add_node(pd_var)
graph.add_edge(pd.Edge(node_id, var_id, label=vparams['dtype']))
graph.add_edge(pd.Edge(__node_id, var_id,
label=vparams['dtype']))
elif var.name or not self.compact:
graph.add_edge(pd.Edge(node_id, var_id, label=vparams['dtype']))
graph.add_edge(pd.Edge(__node_id, var_id,
label=vparams['dtype']))
# Create sub-groph for OpFromGraph nodes
if isinstance(node.op, builders.OpFromGraph):
subgraph = pd.Cluster(node_id)
gf = GraphFormatter()
gf.node_prefix = node_id
gf.to_pydot(node.op.fn, subgraph)
subgraph = pd.Cluster(__node_id)
gf = PyDotFormatter()
# Use different node prefix for sub-graphs
gf.__node_prefix = __node_id
gf(node.op.fn, subgraph)
graph.add_subgraph(subgraph)
pd_node.get_attributes()['subg'] = subgraph.get_name()
......@@ -215,8 +209,9 @@ class GraphFormatter(object):
return str([list(x) for x in m])
# Inputs mapping
ext_inputs = [self.node_id(x) for x in node.inputs]
int_inputs = [gf.node_id(x) for x in node.op.fn.maker.fgraph.inputs]
ext_inputs = [self.__node_id(x) for x in node.inputs]
int_inputs = [gf.__node_id(x)
for x in node.op.fn.maker.fgraph.inputs]
assert len(ext_inputs) == len(int_inputs)
h = format_map(zip(ext_inputs, int_inputs))
pd_node.get_attributes()['subg_map_inputs'] = h
......@@ -227,70 +222,78 @@ class GraphFormatter(object):
for i in n.inputs:
h = i.owner if i.owner else i
if h is node:
ext_outputs.append(self.node_id(n))
ext_outputs.append(self.__node_id(n))
int_outputs = node.op.fn.maker.fgraph.outputs
int_outputs = [gf.node_id(x) for x in int_outputs]
int_outputs = [gf.__node_id(x) for x in int_outputs]
assert len(ext_outputs) == len(int_outputs)
h = format_map(zip(int_outputs, ext_outputs))
pd_node.get_attributes()['subg_map_outputs'] = h
return graph
def dict_to_pdnode(self, d):
e = dict()
for k, v in d.items():
if v is not None:
v = str(v)
v = v.replace('"', '')
e[k] = v
pynode = pd.Node(**e)
return pynode
def var_label(self, var, precision=3):
if var.name is not None:
return var.name
elif isinstance(var, gof.Constant):
h = np.asarray(var.data)
is_const = False
if h.ndim == 0:
is_const = True
h = np.array([h])
dstr = np.array2string(h, precision=precision)
if '\n' in dstr:
dstr = dstr[:dstr.index('\n')]
if is_const:
dstr = dstr.replace('[', '').replace(']', '')
return dstr
elif (var in self.input_update and
self.input_update[var].variable.name is not None):
return self.input_update[var].variable.name + " UPDATE"
else:
return type_to_str(var.type)
def var_tag(self, var):
tag = var.tag
if hasattr(tag, 'trace') and len(tag.trace) and len(tag.trace[0]) == 4:
path, line, _, src = tag.trace[0]
path = pt.basename(path)
src = src.encode()
return (path, line, src)
else:
return None
def apply_profile(self, node, profile):
if not profile or profile.fct_call_time == 0:
return None
time = profile.apply_time.get(node, 0)
call_time = profile.fct_call_time
return [time, call_time]
def var_label(var, precision=3):
"""Return label of variable node."""
if var.name is not None:
return var.name
elif isinstance(var, gof.Constant):
h = np.asarray(var.data)
is_const = False
if h.ndim == 0:
is_const = True
h = np.array([h])
dstr = np.array2string(h, precision=precision)
if '\n' in dstr:
dstr = dstr[:dstr.index('\n')]
if is_const:
dstr = dstr.replace('[', '').replace(']', '')
return dstr
else:
return type_to_str(var.type)
def var_tag(var):
"""Parse tag attribute of variable node."""
tag = var.tag
if hasattr(tag, 'trace') and len(tag.trace) and len(tag.trace[0]) == 4:
path, line, _, src = tag.trace[0]
path = pt.basename(path)
path = path.replace('<', '')
path = path.replace('>', '')
src = src.encode()
return [path, line, src]
else:
return None
def apply_label(node):
"""Return label of apply node."""
return node.op.__class__.__name__
def apply_op(node):
"""Return apply operation."""
name = str(node.op).replace(':', '_')
name = re.sub('^<', '', name)
name = re.sub('>$', '', name)
return name
def apply_profile(node, profile):
"""Return apply profiling informaton."""
if not profile or profile.fct_call_time == 0:
return None
time = profile.apply_time.get(node, 0)
call_time = profile.fct_call_time
return [time, call_time]
def broadcastable_to_str(b):
named_broadcastable = {(): 'scalar',
(False,): 'vector',
(False, True): 'col',
(True, False): 'row',
(False, False): 'matrix'}
(False,): 'vector',
(False, True): 'col',
(True, False): 'row',
(False, False): 'matrix'}
if b in named_broadcastable:
bcast = named_broadcastable[b]
else:
......@@ -315,6 +318,7 @@ def dtype_to_char(dtype):
def type_to_str(t):
"""Return str of variable type."""
if not hasattr(t, 'broadcastable'):
return str(t)
s = broadcastable_to_str(t.broadcastable)
......@@ -325,12 +329,17 @@ def type_to_str(t):
return s
def apply_label(node):
return node.op.__class__.__name__
def apply_op(node):
name = str(node.op).replace(':', '_')
name = re.sub('^<', '', name)
name = re.sub('>$', '', name)
return name
def dict_to_pdnode(d):
"""Create pydot node from dict."""
e = dict()
for k, v in d.items():
if v is not None:
if isinstance(v, list):
v = '\t'.join([str(x) for x in v])
else:
v = str(v)
v = str(v)
v = v.replace('"', '\'')
e[k] = v
pynode = pd.Node(**e)
return pynode
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="%% CSS_DIR %%/d3viz.css"/>
<link rel="stylesheet" href="%% CSS_DIR %%/d3-context-menu.css"/>
<link rel="stylesheet" href="%% CSS_DIR %%/d3theano.css"/>
<script type='text/javascript' src="%% JS_DIR %%/d3viz.js"></script>
<script type="text/javascript" src="%% JS_DIR %%/d3.v3.min.js"></script>
<script type='text/javascript' src="%% JS_DIR %%/dagre-d3.js"></script>
<script type='text/javascript' src="%% JS_DIR %%/graphlib-dot.js"></script>
<script src="%% JS_DIR %%/d3-context-menu.js"></script>
<script src="%% JS_DIR %%/d3theano.js"></script>
<script type='text/javascript' src="%% JS_DIR %%/d3-context-menu.js"></script>
</head>
<body>
<div id='menu'>
......@@ -22,20 +22,21 @@
</div>
<script type="text/javascript">
// Path to DOT file
var path = '%% DOT_FILE %%';
// Backend graph in DOT format
var dotGraph;
// Frontend graph for visualization
var graph = {};
var nodes = [];
var edges = [];
var isProfiled = false;
var colorProfile = false;
var fixedDefault = true;
var forceLayout;
var isProfiled = false; // true if profiling information available
var useProfileColors = false;
var fixOnInit = true; // fix nodes on initialization
var maxProfilePer = 0;
var profileColors = ["#fff5f0","#fee0d2","#fcbba1","#fc9272","#fb6a4a","#ef3b2c","#cb181d","#a50f15"];
var pad = 10;
var isEditNode = false;
var isEditNode = false; // true if node is edited
var menuItems = [
{
title: 'Edit',
......@@ -51,20 +52,32 @@
}
];
var layout;
var scaleDotX;
var scaleDotY;
// Create main panel
d3.select('body').select('svg').remove();
var svg = d3.select('body').append('svg')
.attr('width', '100%')
.attr('height', '95%');
var pane = svg.append('g');
// Zoom behaviour
var zoom = d3.behavior.zoom()
.scaleExtent([0.2, 8])
.on('zoom', function(d) {
var trans = d3.event.translate;
trans[0] += 300;
trans[1] += 100;
pane.attr('transform', 'translate(' + trans + ') scale(' + d3.event.scale + ')');
});
svg.call(zoom);
zoom.event(svg);
svg.on("dblclick.zoom", null);
// Edges
var edgeDiv = d3.select('body').append('div')
.attr('class', 'edgeTooltip')
.style('opacity', 0.0);
// Div for node details
var nodeInfo = d3.select('body').append('div')
.attr('class', 'nodeInfo')
.style('opacity', 0.0);
......@@ -74,7 +87,6 @@
{'id': 'n', 'color': 'black'},
{'id': 'r', 'color': 'red'},
{'id': 'b', 'color': 'dodgerblue'}];
svg.append("defs").selectAll('marker').data(markerData).enter().append("marker")
.attr("id", function(d) { return 'edgeArrow_' + d.id;})
.attr("markerWidth", 4)
......@@ -85,33 +97,13 @@
.append("path")
.attr("d", "M0,0 L4,2 L0,4 Z")
.attr('fill', function(d) { return d.color;});
// Zoom behaviour
function zoom(d) {
var trans = d3.event.translate;
trans[0] += 300;
trans[1] += 100;
pane.attr('transform', 'translate(' + trans + ') scale(' + d3.event.scale + ')');
}
var bZoom = d3.behavior.zoom()
.scaleExtent([0.2, 8])
.on('zoom', zoom);
svg.call(bZoom);
bZoom.event(svg);
svg.on("dblclick.zoom", null);
var curve = d3.svg.line()
.interpolate("cardinal-closed")
.tension(.85);
// Read and initialize graph
d3.text(path, function(data) {
dotGraph = graphlibDot.read(data);
processDotGraph(dotGraph);
graph = forceGraph(dotGraph);
setupGraph();
graph = frontEndGraph(dotGraph);
drawGraph();
});
</script>
</body>
......
/*
* Theano javascript library for interactive visualiztion.
*
* Author: Christof Angermueller <cangermueller@gmail.com
*/
/*
* Checks if variable is defined.
*/
function exists(x) {
return typeof(x) != 'undefined';
}
/*
* Replace all patterns in string.
*/
function replaceAll(str, find, replace) {
return str.replace(new RegExp(find, 'g'), replace);
}
/*
* Computes len equally spaces points between start and end.
*/
function linspace(start, end, len) {
var d = (end - start) / (len - 1);
var rv = [start];
for (i = 1; i < len; ++i) {
rv.push(rv[i - 1] + d);
}
return rv;
}
/*
* Converts string to list
*/
function str2List(s) {
s = s.split('\t');
return s;
}
/*
* Flips y-scale such that (0, 0) points to top-left corner.
*/
function flipAxes(nodes) {
var size = [0, 0];
for (var i in nodes) {
var node = nodes[i];
size[0] = Math.max(size[0], node.pos[0] + node.width);
size[1] = Math.max(size[1], node.pos[1] + node.height);
}
for (var i in nodes) {
var node = nodes[i];
node.pos[1] = size[1] - (node.pos[1] + node.height);
}
}
/*
* Preprocesses raw dotGraph
*/
function processDotGraph(dotGraph) {
// Ignore cluster nodes
dotGraph.rnodes = {};
for (var nodeId in dotGraph._nodes) {
var node = dotGraph._nodes[nodeId];
node.id = nodeId;
node.isCluster = nodeId.startsWith('cluster');
if (!node.isCluster) {
dotGraph.rnodes[nodeId] = node;
}
}
// Precompute attributes
var i = 0;
for (var nodeId in dotGraph.rnodes) {
var node = dotGraph._nodes[nodeId];
node.pos = node.pos.split(',').map(function(d) {return parseInt(d);});
var size = textSize(node.label, {'class': 'nodeText'});
node.width = size.width + 2 * pad;
node.height = size.height + 2 * pad;
node.cx = node.width / 2;
node.cy = node.height / 2;
node.hasChilds = exists(node.subg);
node.showChilds = false;
if (exists(node.profile)) {
node.profile = parseProfile(node.profile);
isProfiled = true;
}
if (exists(node.tag)) {
node.tag = str2List(node.tag);
}
if (exists(node.subg_map_inputs)) {
node.subg_map_inputs = eval(node.subg_map_inputs)
}
if (exists(node.subg_map_outputs)) {
node.subg_map_outputs = eval(node.subg_map_outputs)
}
}
flipAxes(dotGraph.rnodes);
// Offset and scale positions
var posMin = [Infinity, Infinity];
for (var i in dotGraph.rnodes) {
var node = dotGraph._nodes[i];
posMin[0] = Math.min(posMin[0], node.pos[0]);
posMin[1] = Math.min(posMin[1], node.pos[1]);
}
for (var i in dotGraph.rnodes) {
var node = dotGraph._nodes[i];
var pos = node.pos;
pos[0] -= posMin[0];
pos[1] -= posMin[1];
pos[0] = 1.2 * pos[0];
pos[1] = 1.2 * pos[1];
}
// Preprocess edges
var edges = dotGraph.edges();
for (var i in edges) {
var edge = dotGraph.edge(edges[i]);
var size = textSize(edge.label, {'class': 'edgeLabelText'});
edge.width = size.width + 2 * pad;
edge.height = size.height + 2 * pad;
if (!exists(edge.color)) {
edge.color = 'black';
}
switch (edge.color) {
case 'dodgerblue':
edge.type = 'b';
break;
case 'red':
edge.type = 'r';
break;
default:
edge.type = 'n';
}
}
}
/*
* Extracts profiling information from string.
*/
function parseProfile(s) {
var p = str2List(s);
p = p.map(function(x) { return parseFloat(x); });
return p;
}
/*
* Preprocesses DOT nodes for front-end visualization.
* Assigns all children of parent (root of graph if not specified)
* to the same group and calls function recursively on children.
*
*/
function traverseChilds(dotGraph, nodes, groups, parent) {
var preId = '';
var ref = undefined;
// Create new group with parent as parent
var group = {'id': groups.length, 'nodes': [], 'parent': parent};
if (exists(parent)) {
ref = parent.value.subg;
group.parent = parent;
group.nodes.push(parent);
parent.group = group;
}
groups.push(group);
// Loop over all children
var childs = dotGraph.children(ref);
for (var i in childs) {
var child = dotGraph.node(childs[i]);
if (child.isCluster) {
continue;
}
var node = {
'id': child.id,
'value': child,
'index': nodes.length,
'fixed': fixOnInit,
'group': group,
'isParent': child.showChilds,
'parent': parent
};
nodes.push(node);
if (child.showChilds) {
// Recurse if child is root of subcluster that should be expandend
traverseChilds(dotGraph, nodes, groups, node);
} else {
group.nodes.push(node);
}
}
// Groups appended to groups after group are group children.
group.childs = [];
for (var i = group.id + 1; i < groups.length; ++i) {
group.childs.push(groups[i].id);
}
}
/*
* Computes width and height of group of nodes.
*/
function groupSize(nodes) {
var minPos = [Infinity, Infinity];
var maxPos = [-Infinity, -Infinity];
for (var i in nodes) {
var node = nodes[i];
if (node.isParent) {
continue;
}
minPos[0] = Math.min(minPos[0], node.value.pos[0]);
minPos[1] = Math.min(minPos[1], node.value.pos[1]);
maxPos[0] = Math.max(maxPos[0], node.value.pos[0] + node.value.width);
maxPos[1] = Math.max(maxPos[1], node.value.pos[1] + node.value.height);
}
return [maxPos[0] - minPos[0], maxPos[1] - minPos[1]];
}
/*
* Creates front-end graph for visualizing from DOT graph.
*/
function frontEndGraph(dotGraph, prevGraph) {
var graph = {'nodes': [], 'groups': []};
traverseChilds(dotGraph, graph.nodes, graph.groups);
// Dictionary to access nodes by id
graph.nodesd = {};
for (var i in graph.nodes) {
var node = graph.nodes[i];
graph.nodesd[node.id] = node;
}
// Dictionary to access groups by id
graph.groupsd = {};
for (var i in graph.groups) {
var group = graph.groups[i];
graph.groupsd[group.id] = group;
}
// Parent nodes
graph.nodesp = graph.nodes.filter(function(d) {return d.isParent;});
// Non-parent nodes
graph.nodesn = graph.nodes.filter(function(d) {return !d.isParent;});
// Compute size of groups
for (i in graph.groups) {
var group = graph.groups[i];
group.size = groupSize(group.nodes);
var parent = group.parent;
if (exists(parent)) {
var prevParent = prevGraph.nodesd[group.parent.id];
if (exists(prevParent)) {
// Restore previous group position if given
group.pos = [prevParent.x, prevParent.y];
} else {
// Use position of parent otherwise
group.pos = parent.value.pos.slice(0);
}
group.pos[0] += parent.value.cx;
group.pos[1] += parent.value.cy;
} else {
group.pos = [group.size[0] / 2, group.size[1] / 2];
}
// Offset nodes on group center
var min = [Infinity, Infinity];
for (var j in group.nodes) {
var node = group.nodes[j];
if (!node.isParent) {
min[0] = Math.min(min[0], node.value.pos[0]);
min[1] = Math.min(min[0], node.value.pos[1]);
}
}
for (var j in group.nodes) {
var node = group.nodes[j];
if (!node.isParent) {
node.x = group.pos[0] - group.size[0] / 2 + node.value.pos[0] - min[0];
node.y = group.pos[1] - group.size[1] / 2 + node.value.pos[1] - min[1];
}
}
}
graph.size = graph.groups[0].size;
// Reuse previous positions
if (exists(prevGraph)) {
for (var i in graph.nodes) {
var node = graph.nodes[i];
var prevNode;
prevNode = prevGraph.nodesd[node.id];
if (exists(prevNode)) {
node.x = prevNode.x;
node.y = prevNode.y;
node.fixed = prevNode.fixed;
} else {
for (var j in prevGraph.groups) {
var group = prevGraph.groups[j];
if (exists(group.parent) && group.parent.id == node.id) {
node.x = group.pos[0] + group.size[0] / 2;
node.y = group.pos[1] + group.size[1] / 2;
}
}
}
}
}
// Edges
graph.edges = [];
for (var i in graph.nodesn) {
for (var j in graph.nodesn) {
var source = graph.nodesn[i];
var target = graph.nodesn[j];
var dotEdge = dotGraph.edge(source.value.id, target.value.id);
if (exists(dotEdge)) {
var edge = {};
edge.source = parseInt(source.index);
edge.target = parseInt(target.index);
edge.value = dotEdge;
graph.edges.push(edge);
}
// Redirect edges to subgraph
function redirectEdges(map, dotEdge) {
for (var k in map) {
var kmap = map[k];
if (kmap[0] == source.id && kmap[1] == target.id) {
var edge = {};
edge.source = parseInt(source.index);
edge.target = parseInt(target.index);
edge.value = dotEdge;
graph.edges.push(edge);
}
}
}
var map = undefined;
if (exists(target.parent)) {
var parent = target.parent;
var dotEdge = dotGraph.edge(source.id, parent.id);
if (exists(dotEdge)) {
map = parent.value.subg_map_inputs;
redirectEdges(map, dotEdge);
}
}
if (exists(source.parent)) {
var parent = source.parent;
var dotEdge = dotGraph.edge(parent.id, target.id);
if (exists(dotEdge)) {
map = parent.value.subg_map_outputs;
redirectEdges(map, dotEdge);
}
}
}
}
return graph;
}
/*
* Computes d3.js convex hull surrounding nodes that
* belong to the same group.
*/
function convexHulls(graph, offset) {
var hulls = [];
offset = offset || 20;
for (var i in graph.groups) {
var group = graph.groups[i];
if (!exists(group.parent)) {
continue;
}
var points = [];
for (var j in group.nodes) {
var node = group.nodes[j];
if (!node.isParent) {
points.push([node.x - node.value.cx - offset, node.y - node.value.cy - offset]);
points.push([node.x - node.value.cx - offset, node.y + node.value.cy + offset]);
points.push([node.x + node.value.cx + offset, node.y - node.value.cy - offset]);
points.push([node.x + node.value.cx + offset, node.y + node.value.cy + offset]);
}
}
for (var k in group.childs) {
var nodes = graph.groupsd[group.childs[k]].nodes;
for (var j in nodes) {
var node = nodes[j];
if (!node.isParent) {
points.push([node.x - node.value.cx - offset, node.y - node.value.cy - offset]);
points.push([node.x - node.value.cx - offset, node.y + node.value.cy + offset]);
points.push([node.x + node.value.cx + offset, node.y - node.value.cy - offset]);
points.push([node.x + node.value.cx + offset, node.y + node.value.cy + offset]);
}
}
}
hulls.push({group: i, path: d3.geom.hull(points)});
}
return hulls;
}
/*
* Draws convex hull.
*/
function drawConvexHull(d) {
var curve = d3.svg.line()
.interpolate("cardinal-closed")
.tension(.85);
return curve(d.path);
}
/*
* Creates skeleton for graph visualization.
* Positions will be updated by updateGraph().
*/
function drawGraph() {
if (isProfiled) {
d3.select('body').select('#menu').append('input')
.attr('name', 'tColors')
.attr('type', 'button')
.attr('value', 'Toggle profile colors')
.attr('onclick', "toggleNodeColors()");
maxProfilePer = 0;
for (i in graph.nodes) {
var p = graph.nodes[i].value.profile;
if (exists(p)) {
maxProfilePer = Math.max(maxProfilePer, p[0] / p[1]);
}
}
}
var isEdgeOver = false;
var isEdgeLabelOver = false;
// Event handler for dragging groups
var dragHulls = d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", function(d) {
d3.event.sourceEvent.stopPropagation();
d3.event.sourceEvent.preventDefault();
forceLayout.stop();
})
.on("drag", function dragged(d) {
// Shift all group members
var group = graph.groups[d.group];
for (var i in group.nodes) {
var node = group.nodes[i];
node.x += d3.event.dx;
node.y += d3.event.dy;
node.px += d3.event.dx;
node.py += d3.event.dy;
}
group.pos[0] += d3.event.dx;
group.pos[1] += d3.event.dy;
// Shift all members of sub groups
for (var k in group.childs) {
var cgroup = graph.groupsd[group.childs[k]];
var nodes = cgroup.nodes;
for (var j in nodes) {
var node = nodes[j];
node.x += d3.event.dx;
node.y += d3.event.dy;
node.px += d3.event.dx;
node.py += d3.event.dy;
cgroup.pos[0] += d3.event.dx;
cgroup.pos[1] += d3.event.dy;
}
}
updateGraph();
})
.on('dragend', function(d) {forceLayout.resume();});
// Draw convex hull surrounding group of nodes
graph.hulls = convexHulls(graph);
hulls = pane.selectAll('#hulls').remove();
hulls = pane.append('g').attr('id', 'hulls')
.selectAll('path')
.data(graph.hulls).enter()
.append('path')
.attr('class', 'hull')
.attr('d', drawConvexHull)
.call(dragHulls);
// Event handler to open/close groups
hulls.on('dblclick', function(d) {
var group = graph.groups[d.group];
group.parent.value.showChilds = !group.parent.value.showChilds;
if (!group.parent.value.showChilds) {
for (i in group.childs) {
var child = graph.groupsd[group.childs[i]];
child.parent.value.showChilds = false;
}
}
graph = frontEndGraph(dotGraph, graph);
drawGraph();
});
// Add edges
edges = pane.selectAll('#edges').remove();
edges = pane.append('g').attr('id', 'edges')
.selectAll('path').data(graph.edges).enter().append('path')
.attr('class', 'edge')
.attr('stroke', function(d) {return d.value.color;})
.attr('marker-mid', function(d) { return 'url(#edgeArrow_' + d.value.type + ')';});
edges.on('mouseover', function(d) {
var edge = d3.select(this);
edge.transition()
.duration(200)
.style('opacity', 1.0);
edgeDiv.transition()
.duration(200)
.style('opacity', .9);
edgeDiv
.html(d.value.label)
.style('left', (d3.event.pageX) + 'px')
.style('top', (d3.event.pageY - 28) + 'px');
});
edges.on('mouseout', function(d) {
var edge = d3.select(this);
edge.transition()
.duration(200)
.style('opacity', 0.4);
edgeDiv.transition()
.duration(200)
.style('opacity', 0);
});
// Add nodes
pane.selectAll('#nodes').remove();
nodes = pane.append('g').attr('id', 'nodes')
.selectAll('g').data(graph.nodesn).enter().append('g');
updateNodes();
updateGraph();
nodes.on('dblclick', function(d) {
if (d.value.hasChilds) {
d.value.showChilds = !d.value.showChilds;
graph = frontEndGraph(dotGraph, graph);
if (!fixOnInit && d.value.showChilds) {
var n = dotGraph.neighbors(d.id);
for (i in n) {
graph.nodesd[n[i]].fixed = false;
}
}
drawGraph();
}
});
nodes.on('mouseover', function(node) {
// Highlight incoming edges
edges.each(function (d, i) {
var edge = d3.select(this);
if (d.source == node || d.target == node) {
edge.transition()
.duration(200)
.style('opacity', 1.0);
}
});
// Show node details
if (!isEditNode) {
nodeInfo.transition()
.duration(200)
.style('opacity', .9);
nodeInfo
.html(formatNodeInfos(node))
.style('left', (d3.event.pageX) + 30 + 'px')
.style('top', (d3.event.pageY - 28) + 'px');
}
});
nodes.on('mouseout', function(node) {
edges.each(function (d, i) {
var edge = d3.select(this);
if (d.source.index == node.index || d.target.index == node.index) {
edge.transition()
.duration(200)
.style('opacity', 0.4);
}
});
hideNodeInfo();
});
nodes.on('contextmenu', d3.contextMenu(menuItems));
forceLayout = d3.layout.force()
.nodes(graph.nodes)
.links(graph.edges)
.size(graph.size)
.linkDistance(function(d) {
return 300;
})
.charge(-600)
.linkStrength(1)
.gravity(0.05)
.friction(0.5)
.on('tick', updateGraph)
.start();
// Drag behavour
var drag = forceLayout.drag()
.on('dragstart', function(d) {
d3.event.sourceEvent.stopPropagation();
d3.event.sourceEvent.preventDefault();
d.fixed = true;
});
nodes.call(drag);
}
/*
* Computes weighted average between two points.
*/
function avgPos(x1, y1, x2, y2, c) {
x = (1 - c) * x1 + c * x2;
y = (1 - c) * y1 + c * y2;
p = x + ',' + y;
return p;
}
/*
* Checks for collisions in d3.js quadtree.
* See http://bl.ocks.org/mbostock/3231298 for more details.
*/
function collide(node) {
var eps = 10;
var nx1 = node.x - node.value.cx - eps;
var nx2 = node.x + node.value.cx + eps;
var ny1 = node.y - node.value.cy - eps;
var ny2 = node.y + node.value.cy + eps;
return function(quad, x1, y1, x2, y2) {
var point = quad.point;
if (point && (point != node) && !point.fixed && ! node.fixed) {
var px1 = point.x - point.value.cx;
var px2 = point.x + point.value.cx;
var py1 = point.y - point.value.cy;
var py2 = point.y + point.value.cy;
if (!(px1 > nx2 || px2 < nx1 || py1 >= ny2 || py2 <= ny1)) {
var eta = 0.1;
if (px1 < nx1) {
// move quad to left
var d = eta * (px2 - nx1);
point.x -= d;
node.x += d;
} else {
var d = eta * (nx2 - px1);
point.x += d;
node.x -= d;
}
if (py1 < ny1) {
// move quad to top
var d = eta * (py2 - ny1);
point.y -= d;
node.y += d;
} else {
var d = eta * (ny2 - py1);
point.y += d;
node.y -= d;
}
}
}
return x1 > nx2 || x2 < nx1 || y1 >= ny2 || y2 <= ny1;
};
}
/*
* Computes euclidean distance between points.
*/
function distance(x1, y1, x2, y2) {
return Math.sqrt(Math.pow(x1-x2, 2) + Math.pow(y1-y2, 2));
}
/*
* Updates graph visualization.
*/
function updateGraph() {
// Avoid collisions
var q = d3.geom.quadtree(graph.nodes);
for (var i in graph.nodes) {
q.visit(collide(graph.nodes[i]));
}
graph.hulls = convexHulls(graph);
hulls.data(graph.hulls)
.attr('d', drawConvexHull);
// Update nodes
nodes.attr('transform', function(d) { return 'translate(' + (d.x - d.value.cx) + ' ' + (d.y - d.value.cy) + ')'; });
// Update edges
edges.attr('d', function(d) {
var dist = 100;
var l = distance(d.source.x, d.source.y, d.target.x, d.target.y);
var n = Math.max(2, Math.floor(l / dist));
var marker = [];
for (var i = 1; i < n; ++i) {
marker.push(i / n);
}
var markerPos = marker.map(function(c) {
return avgPos(d.source.x, d.source.y, d.target.x, d.target.y, c);});
var markerPos = ' L' + markerPos.join(' L');
return 'M' + d.source.x + ',' + d.source.y + markerPos + ' L' + d.target.x + ',' + d.target.y;
});
}
/*
* Toggles between usual nodes colors and profiling colors
*/
function toggleNodeColors() {
useProfileColors = !useProfileColors;
updateNodes();
updateGraph();
}
/*
* Computes bounding box that fits text of a certain length.
*/
function textSize(text, attr) {
var t = svg.append('text').text(text);
if (typeof(attr) != 'undefined') {
for (a in attr) {
t.attr(a, attr[a]);
}
}
var bbox = t.node().getBBox();
t.remove();
return bbox;
}
/*
* Computes profiling color.
*/
function profileColor(per) {
var s = d3.scale.linear()
.domain(linspace(0, maxProfilePer, profileColors.length))
.range(profileColors)
.interpolate(d3.interpolateRgb);
return s(per);
}
/*
* Retuns node fill color.
*/
function nodeFillColor(d) {
if (useProfileColors) {
var p = d.value.profile;
if (d.value.node_type == 'apply' && exists(p)) {
return profileColor(d.value.profile[0] / d.value.profile[1]);
} else {
return 'white';
}
} else {
return typeof(d.value.fillcolor) == 'undefined' ? 'white' : d.value.fillcolor;
}
}
/*
* Formats profiling timing information.
*/
function formatTime(sec) {
var s;
if (sec < 0.1) {
s = (sec * 1000).toFixed(1) + ' ms';
} else {
s = sec.toFixed(1) + ' s';
}
return s;
}
/*
* Formats node details.
*/
function formatNodeInfos(node) {
var v = node.value;
var s = '<b><center>' + v.label + '</center></b><hr>';
s += '<b>Node:</b> ' + replaceAll(v.node_type, '_', ' ') + ' node';
if (exists(v.dtype)) {
s += '</br>';
s += '<b>Type:</b> <source>' + v.dtype + '</source>';
}
if (exists(v.apply_op)) {
s += '</br>';
s += '<b>Apply:</b> <source>' + v.apply_op + '</source>';
}
if (exists(v.tag)) {
s += '<p>';
s += '<b>Location:</b> <source>' + v.tag[1] + ': ' + v.tag[0] + '</source><br>';
s += '<b>Definition:</b> <source>' + v.tag[2] + '</source><br>';
s += '</p>';
}
var p = v.profile;
if (exists(p)) {
s += '<p>';
s += '<b>Time:</b> ' + formatTime(p[0]);
s += ' / ' + (p[0] / p[1] * 100).toFixed(1) + ' %';
s += '</p>';
}
return s;
}
/*
* Updates node visualization.
*/
function updateNode(d, node) {
var shape;
if (d.value.shape == 'ellipse') {
node.selectAll('ellipse').remove();
shape = node.append('ellipse')
.attr('class', 'nodeEllipse')
.attr('cx', d.value.cx)
.attr('cy', d.value.cy)
.attr('rx', d.value.width * 0.6)
.attr('ry', d.value.height * 0.6);
} else {
node.selectAll('rect').remove();
shape = node.append('rect')
.attr('class', 'nodeRect')
.attr('width', d.value.width)
.attr('height', d.value.height);
}
shape.attr('fill', nodeFillColor(d));
node.selectAll('text').remove();
var text = node.append('text')
.attr('class', 'nodeText')
.attr('x', pad)
.attr('dy', function(d) {return d.value.height - pad - 5;})
.text(function(d) {return d.value.label;});
if (d.value.hasChilds) {
node.style('cursor', 'pointer');
}
}
/*
* Updates visualization of all nodes.
*/
function updateNodes() {
nodes.each(function(d) {
var node = d3.select(this);
updateNode(d, node);
});
}
/*
* Hides node information field.
*/
function hideNodeInfo() {
nodeInfo.transition()
.duration(200)
.style('opacity', 0);
}
/*
* Adjusts node size.
*/
function setNodeSize(node) {
var size = textSize(node.value.label, {'class': 'nodeText'});
node.value.width = size.width + 2 * pad;
node.value.height = size.height + 2 * pad;
node.value.cx = node.value.width / 2;
node.value.cy = node.value.height / 2;
}
/*
* Event handler for editing nodes.
*/
function editNode(elm, d) {
var node = d3.select(elm);
var pos = elm.getBBox();
if (d3.event.defaultPrevented) return;
isEditNode = true;
hideNodeInfo();
var form = node.append('foreignObject')
.attr('x', pos.x)
.attr('y', pos.y)
.attr('width', d.value.width)
.attr('height', 25);
var input = form.append('xhtml:form').append('input')
.attr('style', 'width: ' + d.value.width + 'px')
.attr('value', function() {
this.focus();
return d.value.label;
})
.on('blur', function() {
d.value.label = input.node().value;
setNodeSize(d);
updateNode(d, node);
form.remove(); // TODO: check this
isEditNode = false;
})
.on('keypress', function() {
if (!d3.event) {
d3.event = window.event;
}
var event = d3.event;
if (event.keyCode == 13) {
if (typeof(event.cancelBubble)) {
event.cancelBubble = true;
}
if (event.stopPropagation) {
event.stopPropagation();
}
event.preventDefault();
d.value.label = input.node().value;
setNodeSize(d);
updateNode(d, node);
form.remove(); // TODO: check this
isEditNode = false;
}
});
}
/*
* Release node from fixed positions.
*/
function releaseNode(d) {
d.fixed = false;
forceLayout.start();
}
/*
* Releases positions of all nodes.
*/
function releaseNodes() {
graph['nodes'].forEach (function (d) {
d.fixed = false;
});
forceLayout.start();
}
/*
* Restores original node positions.
*/
function resetNodes() {
graph = frontEndGraph(dotGraph);
drawGraph();
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论