Reputation: 189
I have the need to create a force directed layout that can be updated like in this example.
On the other hand, I need the force layout with labels in the edges as well as with arrow heads at the end of every edge. To this end, I'm trying to combine this example with the previous one.
So, in order to have the label in the edges and the arrow head, I'm using the path
of the second example.
Here is my code so far:
<!DOCTYPE html>
<html>
<head>
<title>Animating Changes in Force Diagram</title>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<style>
.link {
stroke: #2E2E2E;
stroke-width: 2px;
}
.node {
stroke: #fff;
stroke-width: 2px;
}
.textClass {
stroke: #323232;
font-family: "Lucida Grande", "Droid Sans", Arial, Helvetica, sans-serif;
font-weight: normal;
stroke-width: .5;
font-size: 14px;
}
.edgepath {
stroke: #323232;
font-family: "Lucida Grande", "Droid Sans", Arial, Helvetica, sans-serif;
font-weight: normal;
stroke-width: .5;
font-size: 14px;
}
.aEnd{
fill: #444;
font-family: helvetica;
font-size: 8pt;
}
</style>
</head>
<body>
<button onclick="addNodes()">Restart Animation</button>
<button onclick="addNode1()">Test</button>
<script>
var graph;
function myGraph() {
// Add and remove elements on the graph object
this.addNode = function (id) {
nodes.push({"id": id});
update();
};
this.removeNode = function (id) {
var i = 0;
var n = findNode(id);
while (i < links.length) {
if ((links[i]['source'] == n) || (links[i]['target'] == n)) {
links.splice(i, 1);
}
else i++;
}
nodes.splice(findNodeIndex(id), 1);
update();
};
this.removeLink = function (source, target) {
for (var i = 0; i < links.length; i++) {
if (links[i].source.id == source && links[i].target.id == target) {
links.splice(i, 1);
break;
}
}
update();
};
this.removeallLinks = function () {
links.splice(0, links.length);
update();
};
this.removeAllNodes = function () {
nodes.splice(0, links.length);
update();
};
this.addLink = function (source, target, value) {
links.push({"source": findNode(source), "target": findNode(target), "value": value});
update();
};
var findNode = function (id) {
for (var i in nodes) {
if (nodes[i]["id"] === id) return nodes[i];
}
;
};
var findNodeIndex = function (id) {
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].id == id) {
return i;
}
}
;
};
// set up the D3 visualisation in the specified element
var w = 960, h = 450;
var color = d3.scale.category10();
var vis = d3.select("body")
.append("svg:svg")
.attr("width", w)
.attr("height", h)
.attr("id", "svg")
.attr("pointer-events", "all")
.attr("viewBox", "0 0 " + w + " " + h)
.attr("perserveAspectRatio", "xMinYMid")
.append('svg:g');
var force = d3.layout.force();
var nodes = force.nodes();
var links = force.links();
var update = function () {
var link = vis.selectAll("line")
.data(links, function (d) {
return d.source.id + "-" + d.target.id;
});
link.enter().append("line")
.attr("id", function (d) {
return d.source.id + "-" + d.target.id;
})
.attr('marker-end','url(#arrowhead)')
.style("stroke","#ccc")
.style("pointer-events", "none");
link.exit().remove();
var edgepaths = svg.selectAll(".edgepath")
.data(links)
.enter()
.append('path')
.attr({'d': function(d) {return 'M '+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y},
'class':'edgepath',
'fill-opacity':0,
'stroke-opacity':0,
'fill':'blue',
'stroke':'red',
'id':function(d,i) {return 'edgepath'+i}})
.style("pointer-events", "none");
var node = vis.selectAll("g.node")
.data(nodes, function (d) {
return d.id;
});
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.call(force.drag);
nodeEnter.append("svg:circle")
.attr("r", 12)
.attr("id", function (d) {
return "Node;" + d.id;
})
.attr("class", "nodeStrokeClass")
.attr("fill", function(d) { return color(d.id); });
nodeEnter.append("svg:text")
.attr("class", "textClass")
.attr("x", 14)
.attr("y", ".31em")
.text(function (d) {
return d.id;
});
node.exit().remove();
force.on("tick", function () {
node.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
link.attr("x1", function (d) {
return d.source.x;
})
.attr("y1", function (d) {
return d.source.y;
})
.attr("x2", function (d) {
return d.target.x;
})
.attr("y2", function (d) {
return d.target.y;
});
});
// Restart the force layout.
force
.gravity(.01)
.charge(-80000)
.friction(0)
.linkDistance( function(d) { return d.value * 10 } )
.size([w, h])
.start();
};
// Make it all go
update();
}
function drawGraph() {
graph = new myGraph("#svgdiv");
graph.addNode('Sophia');
graph.addNode('Ryan');
graph.addNode('Alex');
graph.addNode('Daniel');
graph.addNode('Too');
graph.addLink('Alex', 'Ryan', '20');
graph.addLink('Sophia', 'Ryan', '20');
graph.addLink('Daniel', 'Ryan', '20');
graph.addLink('Daniel', 'Too', '20');
keepNodesOnTop();
}
drawGraph();
// because of the way the network is created, nodes are created first, and links second,
// so the lines were on top of the nodes, this just reorders the DOM to put the svg:g on top
function keepNodesOnTop() {
$(".nodeStrokeClass").each(function( index ) {
var gnode = this.parentNode;
gnode.parentNode.appendChild(gnode);
});
}
function addNodes() {
d3.select("svg")
.remove();
drawGraph();
}
function addNode1() {
d3.select("svg")
.remove();
drawGraph();
graph.addNode('Test1');
graph.addNode('Test2');
graph.addLink('Test1', 'Test2', '20');
keepNodesOnTop();
}
</script>
</body>
</html>
The problem comes with the selection and the path
showing this error:
Uncaught TypeError: svg.selectAll is not a function at update (combine2.html:148)
because of this line of code:
var edgepaths = svg.selectAll(".edgepath")
Upvotes: 3
Views: 9953
Reputation: 38151
The object svg
is not defined. You have not created it. But you do have an object vis
to which you append your links:
var link = vis.selectAll("line")...
Vis
is the selection of your svg, so you need to append your svg elements to it:
var vis = d3.select("body")
.append("svg:svg")
For your edgepaths, try:
var edgepaths = vis.selectAll(".edgepath")...
Rather than:
var edgepaths = svg.selectAll(".edgepath")...
Upvotes: 3