I am new to coding and recently began using d3 to generate a force directed graph. I successfully generated a four node graph when using the links to derive the nodes. However, when I explicitly list the nodes I receive the error "Uncaught TypeError: Cannot read property 'push' of undefined (d3.v3.min.js)". I have studied responses to the following two similar questions but have been unable to resolve this issue using the answers. I attempted to remove as many of the non-relevant features as possible, thanks.
JavaScript error "Uncaught TypeError: Cannot call method 'push' of undefined" D3.js
Uncaught TypeError: Cannot call method 'push' of undefined (d3 force layout)
Force Directed Graph Fails:
<script type="text/javascript" src="d3.v3.min.js"> </script>
<script>
var width = 900,
height = 590;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
var links = [
{source: 'H', target: 'I'},
{source: 'H', target: 'J'},
{source: 'I', target: 'J'},
{source: 'J', target: 'K'},
];
var nodes = [
{name: 'H'},
{name: 'I'},
{name: 'J'},
{name: 'K'},
];
var force = d3.layout.force()
.size([width, height])
.nodes(d3.values(nodes))
.links(links)
.on('tick', tick)
.linkDistance(100)
.gravity(.15)
.friction(.8)
.linkStrength(1)
.charge(-425)
.chargeDistance(600)
.start();
var link = svg.selectAll('.link')
.data(links)
.enter().append('line')
.attr('class', 'link');
var node = svg.selectAll('.node')
.data(force.nodes())
.enter().append('circle')
.attr('class', 'node')
.attr('r', width * 0.01)
function tick(e) {
node.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.call(force.drag);
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; });
};
</script>
Force Directed Graph Works:
<script type="text/javascript" src="d3.v3.min.js"> </script>
<script>
var width = 900,
height = 590;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
var links = [
{source: 'H', target: 'I'},
{source: 'H', target: 'J'},
{source: 'I', target: 'J'},
{source: 'J', target: 'K'},
];
var nodes = {};
links.forEach(function(link) {
link.source = nodes[link.source] ||
(nodes[link.source] = {name: link.source});
link.target = nodes[link.target] ||
(nodes[link.target] = {name: link.target});
});
var force = d3.layout.force()
.size([width, height])
.nodes(d3.values(nodes))
.links(links)
.on('tick', tick)
.linkDistance(100)
.gravity(.15)
.friction(.8)
.linkStrength(1)
.charge(-425)
.chargeDistance(600)
.start();
var link = svg.selectAll('.link')
.data(links)
.enter().append('line')
.attr('class', 'link');
var node = svg.selectAll('.node')
.data(force.nodes())
.enter().append('circle')
.attr('class', 'node')
.attr('r', width * 0.01)
function tick(e) {
node.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.call(force.drag);
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; });
};
</script>
The API docs have it:
Note: the values of the source and target attributes may be initially specified as indexes into the nodes array; these will be replaced by references after the call to start.
The links array needs to be referring to the nodes either by index or by reference to the nodes' objects. In your working example this is done when creating the nodes from the links:
link.source = // (3)
nodes[link.source] || // (1)
(nodes[link.source] = {name: link.source}); // (2)
This will (1) use the literal name of the node from link.source
, say H
, and fetch the node object from the nodes
array if it is already there. If it is not found in the nodes
array, the right hand side of the ||
operator is evaluated which will (2) create a new node object and place it in the array. In either case the whole expression (1) ||
(2) will evaluate to the reference of a node object which is then (3) assigned to link.source
. Hence, you are not just creating nodes from links but also altering the links themselves. After that initialization your links array will be like this:
[
{source: { name: 'H' }, target: { name: 'I' }},
{source: { name: 'H' }, target: { name: 'J' }},
{source: { name: 'I' }, target: { name: 'J' }},
{source: { name: 'J' }, target: { name: 'K' }},
];
You now have the links array ready with all link objects' source
and target
properties containing references to node objects.
If you already have the nodes' objects ready, you may do the initialization of the links array yourself by putting the references in or you may leave this to D3 by referring to the nodes by index:
var links = [
{source: 0, target: 1},
{source: 0, target: 2},
{source: 1, target: 2},
{source: 2, target: 3},
];
var nodes = [
{name: 'H'},
{name: 'I'},
{name: 'J'},
{name: 'K'},
];
Putting this in your non-working example will make it work as expected:
var width = 600,
height = 400;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
var links = [
{source: 0, target: 1},
{source: 0, target: 2},
{source: 1, target: 2},
{source: 2, target: 3},
];
var nodes = [
{name: 'H'},
{name: 'I'},
{name: 'J'},
{name: 'K'},
];
var force = d3.layout.force()
.size([width, height])
.nodes(d3.values(nodes))
.links(links)
.on('tick', tick)
.linkDistance(100)
.gravity(.15)
.friction(.8)
.linkStrength(1)
.charge(-425)
.chargeDistance(600)
.start();
var link = svg.selectAll('.link')
.data(links)
.enter().append('line')
.attr('class', 'link');
var node = svg.selectAll('.node')
.data(force.nodes())
.enter().append('circle')
.attr('class', 'node')
.attr('r', width * 0.01)
function tick(e) {
node.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.call(force.drag);
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; });
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With