Force Directed Graph Error, “Cannot Read Property 'Push' of Undefined”

馋奶兔 提交于 2020-01-02 04:10:13

问题


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>

回答1:


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>



回答2:


In my case, I had this problem when My source or target value of links which gave the index of nodes was a string.



来源:https://stackoverflow.com/questions/38907522/force-directed-graph-error-cannot-read-property-push-of-undefined

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!