提交 4d38e314 authored 作者: Christof Angermueller's avatar Christof Angermueller

Make nodes editable and add prototype curved edges

上级 1f1f8eda
digraph G { graph [bb="0,0,1297,476"]; "InplaceDimShuffle{x}" [height=0.5, pos="1120,194", shape=ellipse, width=2.5686]; "Elemwise{Composite{GT(scalar_sigmoid((-((-i0) - i1))), i2)}}" [fillcolor="#FFAABB", height=0.5, pos="822,106", shape=ellipse, style=filled, width=6.6733]; "InplaceDimShuffle{x}" -> "Elemwise{Composite{GT(scalar_sigmoid((-((-i0) - i1))), i2)}}" [label="1 TensorType(float64, (True,))", lp="1130.5,150", pos="e,919.34,122.55 1087.3,177.07 1063.3,165.92 1029.7,151.41 999,142 976.72,135.18 952.45,129.37 929.22,124.56"]; "name=b TensorType(float64, scalar)" [fillcolor=limegreen, height=0.5, pos="1155,282", shape=box, style=filled, width=3.0625]; "name=b TensorType(float64, scalar)" -> "InplaceDimShuffle{x}" [color=dodgerblue, label="TensorType(float64, scalar)", lp="1219,238", pos="e,1127,212.08 1147.9,263.6 1143,251.51 1136.4,235.18 1130.8,221.49"]; "Shape_i{0}" [fillcolor=cyan, height=0.5, pos="123,370", shape=ellipse, style=filled, width=1.4763]; "AllocEmpty{dtype='float64'}" [fillcolor="#FFAA22", height=0.5, pos="117,282", shape=ellipse, style=filled, width=3.2589]; "Shape_i{0}" -> "AllocEmpty{dtype='float64'}" [label="TensorType(int64, scalar)", lp="194,326", pos="e,118.19,300.08 121.79,351.6 120.96,339.75 119.85,323.82 118.9,310.29"]; "name=x TensorType(float64, matrix)" [fillcolor=limegreen, height=0.5, pos="212,458", shape=box, style=filled, width=3.1181]; "name=x TensorType(float64, matrix)" -> "Shape_i{0}" [label="TensorType(float64, matrix)", lp="206,414", pos="e,119.68,388.12 146.59,439.97 138.58,435.35 131.35,429.47 126,422 121.13,415.21 119.44,406.5 119.25,398.22"]; "CGemv{inplace}" [height=0.5, pos="472,194", shape=ellipse, width=2.0569]; "name=x TensorType(float64, matrix)" -> "CGemv{inplace}" [label="2 TensorType(float64, matrix)", lp="387,326", pos="e,417.53,206.33 264.19,439.95 272.46,435.26 280.19,429.37 286,422 330.4,365.66 274.09,319.94 319,264 341.06,236.53 376.41,219.61 \ 407.58,209.41"]; "AllocEmpty{dtype='float64'}" -> "CGemv{inplace}" [color=red, label="0 TensorType(float64, vector)", lp="201.5,238", pos="e,398.78,197.07 111.67,263.77 109.42,252.76 108.91,238.92 117,230 134.92,210.23 289.58,201.22 388.77,197.44"]; "CGemv{inplace}" -> "Elemwise{Composite{GT(scalar_sigmoid((-((-i0) - i1))), i2)}}" [label="0 TensorType(float64, vector)", lp="733.5,150", pos="e,736.34,122.9 515.74,179.33 551.4,168.44 603.18,153.22 649,142 673.96,135.89 701.14,130.03 726.39,124.9"]; "val=1.0 TensorType(float64, scalar)" [fillcolor=limegreen, height=0.5, pos="437,282", shape=box, style=filled, width=3.0278]; "val=1.0 TensorType(float64, scalar)" -> "CGemv{inplace}" [label="1 TensorType(float64, scalar)", lp="541.5,238", pos="e,465.05,212.08 444.08,263.6 449,251.51 455.65,235.18 461.22,221.49"]; "name=w TensorType(float64, vector)" [fillcolor=limegreen, height=0.5, pos="677,282", shape=box, style=filled, width=3.1389]; "name=w TensorType(float64, vector)" -> "CGemv{inplace}" [label="3 TensorType(float64, vector)", lp="733.5,238", pos="e,540.12,201.21 665.1,263.77 656.39,252.47 643.58,238.3 629,230 605.11,216.4 576.39,208.09 550.07,203"]; "val=0.0 TensorType(float64, scalar)" [fillcolor=limegreen, height=0.5, pos="917,282", shape=box, style=filled, width=3.0278]; "val=0.0 TensorType(float64, scalar)" -> "CGemv{inplace}" [label="4 TensorType(float64, scalar)", lp="944.5,238", pos="e,541.25,200.46 891.6,263.79 873.13,252.18 847.07,237.65 822,230 796.82,222.32 646.71,209.1 551.43,201.29"]; "TensorType(int8, vector)" [fillcolor=dodgerblue, height=0.5, pos="822,18", shape=box, style=filled, width=2.1736]; "Elemwise{Composite{GT(scalar_sigmoid((-((-i0) - i1))), i2)}}" -> "TensorType(int8, vector)" [label="TensorType(int8, vector)", lp="892.5,62", pos="e,822,36.084 822,87.597 822,75.746 822,59.817 822,46.292"]; "val=[ 0.5] TensorType(float32, (True,))" [fillcolor=limegreen, height=0.5, pos="822,194", shape=box, style=filled, width=3.2847]; "val=[ 0.5] TensorType(float32, (True,))" -> "Elemwise{Composite{GT(scalar_sigmoid((-((-i0) - i1))), i2)}}" [label="2 TensorType(float32, (True,))", lp="908.5,150", pos="e,822,124.08 822,175.6 822,163.75 822,147.82 822,134.29"]; }
\ No newline at end of file
<!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:5%; left:0%; right:0% bottom=10%
}
.nodeRect {
stroke: black;
border: 3px solid black;
}
.nodeEllipse {
stroke: black;
border: 3px solid black;
}
.nodeText {
color: black;
}
.edge {
stroke-width: 3px;
fill: transparent;
cursor: pointer;
opacity: 0.4;
}
.edgeLabelRect {
stroke: black;
border: 1px solid black;
fill: skyblue;
opacity: 0.9;
}
.edgeLabelText {
fill: black;
text-anchor: start;
}
.arrowHead {
stroke: green;
stroke-width: 1px;
}
.arrowHead_n {
stroke: green;
}
.arrowHead_r {
stroke-width: 3px;
fill: red;
stroke: red;
}
.arrowHead_b {
stroke: dodgerblue;
}
.tooltip {
position: absolute;
text-align: center;
vertical-align: middle;
min-width: 10px;
min-height: 10px;
padding: 5px;
background: lightsteelblue;
border: 1px solid black;
border-radius: 8px;
pointer-events: none;
}
</style>
<div>
<input name="resetNodes"
type="button"
value="Reset nodes"
onclick="resetNodes()"/>
<input name="releaseNodes"
type="button"
value="Release nodes"
onclick="releaseNodes()"/>
</div>
<script type="text/javascript">
var path='predict.dot';
// Global attributes
var pad = 10;
d3.select('body').select('svg').remove();
var svg = d3.select('body').append('svg')
.attr('width', '100%')
.attr('height', '95%');
var pane = svg.append('g');
var edgeDiv = d3.select('body').append('div')
.attr('class', 'tooltip')
.style('opacity', 0.0);
// Definition head of edges
var markerData = [
{'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)
.attr("markerHeight", 4)
.attr("refX", 2)
.attr("refY", 2)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,0 L4,2 L0,4 Z")
.attr('fill', function(d) { return d.color;});
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 exists(x) {
return typeof(x) != 'undefined';
}
var dotGraph;
var graph = {};
var nodes = [];
var edges = [];
var layout;
var scaleDotX;
var scaleDotY;
d3.text(path, function(data) {
dotGraph = graphlibDot.parse(data);
// 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,' + dotWidth + ',' + dotHeight);
scaleDotX = d3.scale.linear().domain([0, dotWidth]).range([20, dotWidth * 1.2]);
scaleDotY = d3.scale.linear().domain([0, dotHeight]).range([20, dotHeight * 1.2]);
// Parse nodes
var nodeIdx = 0;
var nodesA = [];
for (nodeId in dotGraph._nodes) {
var node = dotGraph._nodes[nodeId];
node.index = nodeIdx++;
// 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.value.isValue = false;
node.fixed = false;
nodesA.push(node);
nodes.push(node);
dotGraph._nodes[nodeId] = node;
}
// Parse edges
var nodesB = [];
for (edgeId in dotGraph._edges) {
var edge = dotGraph._edges[edgeId];
var sourceNode = dotGraph._nodes[edge.u];
var targetNode = dotGraph._nodes[edge.v];
edge.source = sourceNode.index;
edge.target = targetNode.index;
if (!exists(edge.value.color)) {
edge.value.color = 'black';
}
switch (edge.value.color) {
case 'dodgerblue':
edge.value.type = 'b';
break;
case 'red':
edge.value.type = 'r';
break;
default:
edge.value.type = 'n';
}
node = {};
node.index = nodeIdx++;
node.value = {};
node.value.isHelper = true;
node.x = 0.5 * (sourceNode.x + targetNode.x);
node.y = 0.5 * (sourceNode.y + targetNode.y);
node.fixed = false;
nodes.push(node);
nodesB.push(node);
edge.middle = node;
edges.push(edge);
}
// Setup graph
graph['nodes'] = nodes;
graph['nodesA'] = nodesA;
graph['nodesB'] = nodesB;
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('stroke', function(d) {return d.value.color;})
.attr('marker-start', function(d) { return 'url(#edgeArrow_' + d.value.type + ')';})
.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');
})
.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
nodes = pane.append('g').attr('id', 'nodes').selectAll('g').data(graph['nodesA']).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));
});
nodes.on('dblclick', releaseNode);
// Add nodes text
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;})
.attr('contenteditable', "true")
.on('dblclick', releaseNode);
// 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
layout = d3.layout.force()
.nodes(graph['nodes'])
.links(graph['edges'])
.size([dotWidth, dotHeight])
.charge(-3000)
.linkDistance(25)
.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("dblclick.zoom", null)
.on('zoom', zoom);
svg.call(bZoom);
svg.on("dblclick.zoom", null);
// Start force layout
layout.start();
});
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;
}
// 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) {
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');
var p = 'M' + d.source.x + ',' + d.source.y + ' C' + d.source.x + ',' + d.source.y + ' ' + d.middle.x + ',' + d.middle.y + ' ' + d.target.x + ',' + d.target.y;
//var p = 'M' + d.source.x + ',' + d.source.y + markerPos + ' L' + d.target.x + ',' + d.target.y;
return p;
});
}
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(dotHeight - (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();
}
</script>
</body>
</html>
digraph G { graph [bb="0,0,1297,476"]; "InplaceDimShuffle{x}" [height=0.5, pos="1120,194", shape=ellipse, width=2.5686]; "Elemwise{Composite{GT(scalar_sigmoid((-((-i0) - i1))), i2)}}" [fillcolor="#FFAABB", height=0.5, pos="822,106", shape=ellipse, style=filled, width=6.6733]; "InplaceDimShuffle{x}" -> "Elemwise{Composite{GT(scalar_sigmoid((-((-i0) - i1))), i2)}}" [label="1 TensorType(float64, (True,))", lp="1130.5,150", pos="e,919.34,122.55 1087.3,177.07 1063.3,165.92 1029.7,151.41 999,142 976.72,135.18 952.45,129.37 929.22,124.56"]; "name=b TensorType(float64, scalar)" [fillcolor=limegreen, height=0.5, pos="1155,282", shape=box, style=filled, width=3.0625]; "name=b TensorType(float64, scalar)" -> "InplaceDimShuffle{x}" [color=dodgerblue, label="TensorType(float64, scalar)", lp="1219,238", pos="e,1127,212.08 1147.9,263.6 1143,251.51 1136.4,235.18 1130.8,221.49"]; "Shape_i{0}" [fillcolor=cyan, height=0.5, pos="123,370", shape=ellipse, style=filled, width=1.4763]; "AllocEmpty{dtype='float64'}" [fillcolor="#FFAA22", height=0.5, pos="117,282", shape=ellipse, style=filled, width=3.2589]; "Shape_i{0}" -> "AllocEmpty{dtype='float64'}" [label="TensorType(int64, scalar)", lp="194,326", pos="e,118.19,300.08 121.79,351.6 120.96,339.75 119.85,323.82 118.9,310.29"]; "name=x TensorType(float64, matrix)" [fillcolor=limegreen, height=0.5, pos="212,458", shape=box, style=filled, width=3.1181]; "name=x TensorType(float64, matrix)" -> "Shape_i{0}" [label="TensorType(float64, matrix)", lp="206,414", pos="e,119.68,388.12 146.59,439.97 138.58,435.35 131.35,429.47 126,422 121.13,415.21 119.44,406.5 119.25,398.22"]; "CGemv{inplace}" [height=0.5, pos="472,194", shape=ellipse, width=2.0569]; "name=x TensorType(float64, matrix)" -> "CGemv{inplace}" [label="2 TensorType(float64, matrix)", lp="387,326", pos="e,417.53,206.33 264.19,439.95 272.46,435.26 280.19,429.37 286,422 330.4,365.66 274.09,319.94 319,264 341.06,236.53 376.41,219.61 \ 407.58,209.41"]; "AllocEmpty{dtype='float64'}" -> "CGemv{inplace}" [color=red, label="0 TensorType(float64, vector)", lp="201.5,238", pos="e,398.78,197.07 111.67,263.77 109.42,252.76 108.91,238.92 117,230 134.92,210.23 289.58,201.22 388.77,197.44"]; "CGemv{inplace}" -> "Elemwise{Composite{GT(scalar_sigmoid((-((-i0) - i1))), i2)}}" [label="0 TensorType(float64, vector)", lp="733.5,150", pos="e,736.34,122.9 515.74,179.33 551.4,168.44 603.18,153.22 649,142 673.96,135.89 701.14,130.03 726.39,124.9"]; "val=1.0 TensorType(float64, scalar)" [fillcolor=limegreen, height=0.5, pos="437,282", shape=box, style=filled, width=3.0278]; "val=1.0 TensorType(float64, scalar)" -> "CGemv{inplace}" [label="1 TensorType(float64, scalar)", lp="541.5,238", pos="e,465.05,212.08 444.08,263.6 449,251.51 455.65,235.18 461.22,221.49"]; "name=w TensorType(float64, vector)" [fillcolor=limegreen, height=0.5, pos="677,282", shape=box, style=filled, width=3.1389]; "name=w TensorType(float64, vector)" -> "CGemv{inplace}" [label="3 TensorType(float64, vector)", lp="733.5,238", pos="e,540.12,201.21 665.1,263.77 656.39,252.47 643.58,238.3 629,230 605.11,216.4 576.39,208.09 550.07,203"]; "val=0.0 TensorType(float64, scalar)" [fillcolor=limegreen, height=0.5, pos="917,282", shape=box, style=filled, width=3.0278]; "val=0.0 TensorType(float64, scalar)" -> "CGemv{inplace}" [label="4 TensorType(float64, scalar)", lp="944.5,238", pos="e,541.25,200.46 891.6,263.79 873.13,252.18 847.07,237.65 822,230 796.82,222.32 646.71,209.1 551.43,201.29"]; "TensorType(int8, vector)" [fillcolor=dodgerblue, height=0.5, pos="822,18", shape=box, style=filled, width=2.1736]; "Elemwise{Composite{GT(scalar_sigmoid((-((-i0) - i1))), i2)}}" -> "TensorType(int8, vector)" [label="TensorType(int8, vector)", lp="892.5,62", pos="e,822,36.084 822,87.597 822,75.746 822,59.817 822,46.292"]; "val=[ 0.5] TensorType(float32, (True,))" [fillcolor=limegreen, height=0.5, pos="822,194", shape=box, style=filled, width=3.2847]; "val=[ 0.5] TensorType(float32, (True,))" -> "Elemwise{Composite{GT(scalar_sigmoid((-((-i0) - i1))), i2)}}" [label="2 TensorType(float32, (True,))", lp="908.5,150", pos="e,822,124.08 822,175.6 822,163.75 822,147.82 822,134.29"]; }
\ No newline at end of file
<!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:5%; left:0%; right:0% bottom=10%
}
.nodeRect {
stroke: black;
border: 3px solid black;
}
.nodeEllipse {
stroke: black;
border: 3px solid black;
}
.nodeText {
color: black;
}
.edge {
stroke-width: 3px;
cursor: pointer;
opacity: 0.4;
}
.edgeLabelRect {
stroke: black;
border: 1px solid black;
fill: skyblue;
opacity: 0.9;
}
.edgeLabelText {
fill: black;
text-anchor: start;
}
.arrowHead {
stroke: green;
stroke-width: 1px;
}
.arrowHead_n {
stroke: green;
}
.arrowHead_r {
stroke-width: 3px;
fill: red;
stroke: red;
}
.arrowHead_b {
stroke: dodgerblue;
}
.tooltip {
position: absolute;
text-align: center;
vertical-align: middle;
min-width: 10px;
min-height: 10px;
padding: 5px;
background: lightsteelblue;
border: 1px solid black;
border-radius: 8px;
pointer-events: none;
}
</style>
<div>
<input name="resetNodes"
type="button"
value="Reset nodes"
onclick="resetNodes()"/>
<input name="releaseNodes"
type="button"
value="Release nodes"
onclick="releaseNodes()"/>
</div>
<script type="text/javascript">
var path='predict.dot';
// Global attributes
var pad = 10;
d3.select('body').select('svg').remove();
var svg = d3.select('body').append('svg')
.attr('width', '100%')
.attr('height', '95%');
var pane = svg.append('g');
var edgeDiv = d3.select('body').append('div')
.attr('class', 'tooltip')
.style('opacity', 0.0);
// Definition head of edges
var markerData = [
{'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)
.attr("markerHeight", 4)
.attr("refX", 2)
.attr("refY", 2)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,0 L4,2 L0,4 Z")
.attr('fill', function(d) { return d.color;});
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 exists(x) {
return typeof(x) != 'undefined';
}
var dotGraph;
var graph = {};
var nodes = [];
var edges = [];
var layout;
var scaleDotX;
var scaleDotY;
d3.text(path, function(data) {
dotGraph = graphlibDot.parse(data);
// 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,' + dotWidth + ',' + dotHeight);
scaleDotX = d3.scale.linear().domain([0, dotWidth]).range([20, dotWidth * 1.2]);
scaleDotY = d3.scale.linear().domain([0, dotHeight]).range([20, dotHeight * 1.2]);
// 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;
if (!exists(edge.value.color)) {
edge.value.color = 'black';
}
switch (edge.value.color) {
case 'dodgerblue':
edge.value.type = 'b';
break;
case 'red':
edge.value.type = 'r';
break;
default:
edge.value.type = 'n';
}
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('stroke', function(d) {return d.value.color;})
.attr('marker-mid', function(d) { return 'url(#edgeArrow_' + d.value.type + ')';})
.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');
})
.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
nodes = pane.append('g').attr('id', 'nodes').selectAll('g').data(graph['nodes']).enter().append('g');
function fillColor(f) {
return typeof(f) == 'undefined' ? 'white' : f;
}
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 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', fillColor(d.value.fillcolor));
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;})
.on('dblclick', releaseNode);
}
nodes.each(function(d) {var node = d3.select(this); updateNode(d, node);});
// TODO: activate again without interfering with click event
//nodes.on('dblclick', releaseNode);
nodes.on('click', function(d) {
var pos = this.getBBox();
var node = d3.select(this);
if (d3.event.defaultPrevented) return;
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
})
.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 thi
}
});
});
nodes.on('mouseover', function(node) {
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);
}
});
});
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);
}
});
});
// Zoom and translate event handler
function zoom(d) {
pane.attr('transform', 'translate(' + d3.event.translate + ') scale(' + d3.event.scale + ')');
}
// Force layout
layout = d3.layout.force()
.nodes(graph['nodes'])
.links(graph['edges'])
.size([dotWidth, dotHeight])
.charge(-3000)
.linkDistance(50)
.linkStrength(0.1)
.on('tick', updateGraph);
// Drag behavour
var drag = layout.drag()
.on('dragstart', function(d) {
d3.event.sourceEvent.stopPropagation();
d3.event.sourceEvent.preventDefault();
d.fixed = true;
});
nodes.call(drag);
// Zoom behaviour
var bZoom = d3.behavior.zoom()
.scaleExtent([0.2, 8])
//.on("dblclick.zoom", null)
.on('zoom', zoom);
svg.call(bZoom);
svg.on("dblclick.zoom", null);
// Start force layout
layout.start();
});
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;
}
// 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) {
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 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(dotHeight - (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();
}
</script>
</body>
</html>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论