3

I have a problem with link.exit().remove(); and node.exit().remove();. If I set it in the initializeGraph method then I get several errors with the tick function I think. So my question is how or why do I get those errors:

Uncaught TypeError: undefined is not a function graph-d3.js:156initializeGraph graph-d3.js:156updateForceUsingNewNodes graph-d3.js:108createGraph graph-d3.js:18$.ajax.success ajax-stuff.js:106j jquery-2.1.1.min.js:2k.fireWith jquery-2.1.1.min.js:2x jquery-2.1.1.min.js:4n.prop.on.c jquery-2.1.1.min.js:4n.event.dispatch jquery-2.1.1.min.js:3r.handle jquery-2.1.1.min.js:3
3Error: Invalid value for <line> attribute x1="NaN" d3.min.js:1
3Error: Invalid value for <line> attribute y1="NaN" d3.min.js:1
3Error: Invalid value for <line> attribute x2="NaN" d3.min.js:1
3Error: Invalid value for <line> attribute y2="NaN" d3.min.js:1
Uncaught TypeError: Cannot read property 'attr' of undefined 

Here is an exerpt of the source code. Not important lines are removed:

var alreadyThere = false;
var nodeCircles = {};
var svg, link, node;
var force = d3.layout.force();
var width = 700, height = 200;
var boxIDName = "#main-rightinfo";
var currentJSON;
var container;
var zoom = d3.behavior.zoom()
    .scaleExtent([0.4, 5]);
var drag = force.drag();

function createGraph(newJSON){
    if (alreadyThere){
        svg.remove();
        nodeCircles = {};
    }
    updateForceUsingNewNodes(generateObjects(newJSON));
    alreadyThere = true;
    currentJSON = newJSON;
    force.start();
}

function updateGraph(newJSON){
    svg.remove();
    findDuplicatesAndSetEmpty(newJSON);
    deleteEmptyObjectsInJSON(newJSON);
    currentJSON = currentJSON.concat(newJSON);
    updateForceUsingNewNodes(generateObjects(currentJSON));
    force.start();
}

//here are some methods forming the json and array...

function initializeGraph(){
    zoom.on("zoom", zoomed);
    drag.on("dragstart", dragstart);

    force
        .size([width, height])
        .gravity(.1)
        .charge(-400)
        .friction(0.9)
        .theta(0.9) 
        .linkStrength(0.9)
        .distance(55)
        .alpha(0.1)
        .on("tick", tick);

    svg = d3.select("#main-right")
        .append("svg")
        .attr("width", width)
        .attr("height", height)
        .call(zoom).on("dblclick.zoom", null);
    svg
        .append("svg:defs").selectAll("marker")
        .data(["end"]) 
        .enter().append("svg:marker")   
        .attr("id", String)
        .attr("viewBox", "0 -5 10 10")
        .attr("refX", 32)
        .attr("refY", -0.05)
        .attr("markerWidth", 6)
        .attr("markerHeight", 6)
        .attr("orient", "auto")
        .append("svg:path")
        .attr("d", "M0,-5L10,0L0,5")
        .attr('fill', '#359AF4');

    container = svg.append("g");

    link = container.append("g")
        .attr("class", "links")
        .selectAll(".link")
        .data(force.links())
        .enter().append("line")
        .attr("class", "link")
        .attr("marker-end", "url(#end)");

    node = container.append("g")
        .attr("class", "nodes")
        .selectAll(".node")
        .data(force.nodes())
        .enter().append("g")
        .attr("class", "node")
        .on("mouseover", mouseover)
        .on("mouseout", mouseout)
        .on("click",        function(d) { click(d); })
        .on("dblclick",     function(d) { dblclick(d); })
        .on('contextmenu',  function(data, index) {
                                                d3.event.preventDefault();
                                                updateGraphByRemoveElement(data, index);
                                        })
        .call(drag);

    node
        .append("circle")
        .attr("r", 20)
        .attr("cx", 0)
        .attr("cy", 0)
        .style("fill", '#eee')
        .style("stroke", '#fff')
        .style("stroke-width", '0.5px');
    node
        .append("image")
        .attr("xlink:href", function(d) {
                                            if (d.class == "Person") {
                                                return "pics/node_person.svg";
                                            } else {
                                                return "pics/node_appln.svg";
                                        }} )
        .attr("x", -20)
        .attr("y", -20)
        .attr("width", 40)
        .attr("height", 40)
        .attr("color", "white");
    node
        .append("text")
        .attr("x", 20)
        .attr("y", 4)
        .style("fill", "#bbb")
        .text(function(d) { return d.name; });  
    node
        .append("circle")
        .attr("r", 7)
        .attr("cx", 0)
        .attr("cy", -16)
        .style("fill", '#359AF4');
    node
        .append("text")
        .attr("text-anchor", "center")
        .attr("x", -3)
        .attr("y", -13)
        .style("fill", "white")
        .text(function(d) { return d.linkCount; });

    function tick() {
      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; });
      node
          .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
    }
}

//here are some functions tick, mousedown and so on...

Well as you can see the svg.remove(); from the code is not needed. But until the exit().remove() do not work, it's needed. So yeah how to handle that with the tickevent/ .exit().remove().

Thank you for any tips.

PS: I used this very basic one https://gist.github.com/mbostock/1095795 and another which is very close to mine D3.js - exit() section does not remove all data and this working not working, too Why does d3.js v3 break my force graph when implementing zooming when v2 doesn't?

Whole code or let's say how I think it should run, but currently getting some errors (not identical to the code above, but just changed some lines)

var alreadyThere = false;
var nodeCircles = {};
var svg, link, node;
var force = d3.layout.force();
var width = 700, height = 400;
var boxIDName = "#main-rightinfo";
var currentJSON;
var container;
var zoom = d3.behavior.zoom()
    .scaleExtent([0.4, 5]);
var drag = force.drag();

function createGraph(newJSON){
    if (alreadyThere){
        //svg.remove();
        nodeCircles = {};
    }
    updateForceUsingNewNodes(generateObjects(newJSON));
    alreadyThere = true;
    currentJSON = newJSON;
    force.start();
}

function updateGraph(newJSON){
    //svg.remove();
    findDuplicatesAndSetEmpty(newJSON);
    deleteEmptyObjectsInJSON(newJSON);
    currentJSON = currentJSON.concat(newJSON);
    updateForceUsingNewNodes(generateObjects(currentJSON));
    force.start();
}

function findDuplicatesAndSetEmpty(newJSON){
    for (var i = 0; i < currentJSON.length; i++) {
        for (var o = 0; o < newJSON.length; o++) {
            if ((currentJSON[i].source.ID == newJSON[o].source) && (currentJSON[i].target.ID == newJSON[o].target)
            ||  (currentJSON[i].source.ID == newJSON[o].target) && (currentJSON[i].target.ID == newJSON[o].source)){
                newJSON[o] = {};
            }
        }
    }
}

function deleteEmptyObjectsInJSON(json){
    for (var i = 0; i < json.length; i++) {
        var y = json[i].source;
        if (y==="null" || y===null || y==="" || typeof y === "undefined"){
            json.splice(i,1);
            i--;
        }
    }
}

function updateGraphByRemoveElement(clickedNode, index){
    svg.remove();
    var json4Splicing = currentJSON;
    for (var i = 0; i < json4Splicing.length; i++) {
        if (json4Splicing[i].source.ID == clickedNode.ID){
            json4Splicing[i] = {};
        } else if (json4Splicing[i].target.ID == clickedNode.ID){
            json4Splicing[i] = {};
        }
    }
    deleteEmptyObjectsInJSON(json4Splicing);
    deleteNode(force.nodes(),clickedNode);  
    currentJSON = json4Splicing;
    updateForceRemoveElement(generateObjects(currentJSON)); 
}

function deleteNode(allNodes, clickedNode){
    allNodes.forEach(function(node) {
        if (node == clickedNode){
            force.links().forEach(function(link) {
                if (node.ID == link.source.ID){
                    link.target.linkCount--;
                }
                if (node.ID == link.target.ID){
                    link.source.linkCount--;
                }
            }); 
            node.linkCount = 0;
        }
    }); 
}

function generateObjects(json) {
    json.forEach(function(link) {
        if (typeof(link.source) == "string"){
            link.source = nodeCircles[link.source] || (nodeCircles[link.source] = {name: link.sourceName, ID: link.source, class: link.sourceClass, linkCount:0});
            link.source.linkCount++;
        }
        if (typeof(link.target) == "string"){
            link.target = nodeCircles[link.target] || (nodeCircles[link.target] = {name: link.targetName, ID: link.target, class: link.targetClass, linkCount:0});
            link.target.linkCount++;
        }
    });   
    return json;
}
function updateForceRemoveElement(links){
    force.nodes(d3.values(nodeCircles).filter(function(d){ return d.linkCount; }) );
    force.links(d3.values(links));
    initializeGraph();
}
function updateForceUsingNewNodes(links){
    force.nodes(d3.values(nodeCircles).filter(function(d){ return d.linkCount; }) );
    force.links(d3.values(links));
    initializeGraph();
}

function initializeGraph(){
    zoom.on("zoom", zoomed);
    drag.on("dragstart", dragstart);

    force
        .size([width, height])
        .gravity(.1)
        .charge(-400)
        .friction(0.9)
        .theta(0.9) 
        .linkStrength(0.9)
        .distance(55)
        .alpha(0.1)
        .on("tick", tick);

    svg = d3.select("#main-right")
        .append("svg")
        .attr("width", width)
        .attr("height", height)
        .call(zoom).on("dblclick.zoom", null);
    svg
        .append("svg:defs").selectAll("marker")
        .data(["end"]) 
        .enter().append("svg:marker")   
        .attr("id", String)
        .attr("viewBox", "0 -5 10 10")
        .attr("refX", 32)
        .attr("refY", -0.05)
        .attr("markerWidth", 6)
        .attr("markerHeight", 6)
        .attr("orient", "auto")
        .append("svg:path")
        .attr("d", "M0,-5L10,0L0,5")
        .attr('fill', '#359AF4');

    container = svg.append("g");

    link = container.append("g")
        .attr("class", "links")
        .selectAll(".link")
        .data(force.links())
        .enter().append("line")
        .attr("class", "link")
        .attr("marker-end", "url(#end)");
    link.exit().remove();
    node = container.append("g")
        .attr("class", "nodes")
        .selectAll(".node")
        .data(force.nodes())
        .enter().append("g")
        .attr("class", "node")
        .on("mouseover", mouseover)
        .on("mouseout", mouseout)
        .on("click",        function(d) { click(d); })
        .on("dblclick",     function(d) { dblclick(d); })
        .on('contextmenu',  function(data, index) {
                                                d3.event.preventDefault();
                                                updateGraphByRemoveElement(data, index);
                                        })
        .call(drag);

    node
        .append("circle")
        .attr("r", 20)
        .attr("cx", 0)
        .attr("cy", 0)
        .style("fill", '#eee')
        .style("stroke", '#fff')
        .style("stroke-width", '0.5px');
    node
        .append("image")
        .attr("xlink:href", function(d) {
                                            if (d.class == "Person") {
                                                return "pics/node_person.svg";
                                            } else {
                                                return "pics/node_appln.svg";
                                        }} )
        .attr("x", -20)
        .attr("y", -20)
        .attr("width", 40)
        .attr("height", 40)
        .attr("color", "white");
    node
        .append("text")
        .attr("x", 20)
        .attr("y", 4)
        .style("fill", "#bbb")
        .text(function(d) { return d.name; });  
    node
        .append("circle")
        .attr("r", 7)
        .attr("cx", 0)
        .attr("cy", -16)
        .style("fill", '#359AF4');
    node
        .append("text")
        .attr("text-anchor", "center")
        .attr("x", -3)
        .attr("y", -13)
        .style("fill", "white")
        .text(function(d) { return d.linkCount; });

    node.exit().remove();


}
function tick() {
      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; });
      node
          .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
    }
d3.selection.prototype.moveToFront = function() {
  return this.each(function(){
    this.parentNode.appendChild(this);
  });
};
function zoomed() {
    d3.event.sourceEvent.stopPropagation(); 
    container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
function dragstart(d) {
    d3.event.sourceEvent.stopPropagation();
    d3.select(this).classed("fixed", d.fixed = true);
}
function mouseover() {
    d3.select(this).select("text").transition()
      .duration(750)
      .style("font-size","15px")
      .style("fill","black");
    d3.select(this).moveToFront();
}
function mouseout() {
    d3.select(this).select("text").transition()
      .duration(750)
      .style("font-size","10px")
      .style("fill","#ccc");
}
function click(d) {
    $(boxIDName).empty();
    if (d.class == "Person"){
        $(boxIDName).append("<h2>Person/Company</h2>");
        getNodeInfoPerson(d.ID);
    }
    if (d.class == "Appln"){
        $(boxIDName).append("<h2>Application</h2>");
        getNodeInfoAppln(d.ID);
    }
}
function dblclick(d) {
    entryClicked(d.ID+"|"+d.class,false);
}
Community
  • 1
  • 1
kwoxer
  • 3,734
  • 4
  • 40
  • 70
  • 1
    When removing nodes, you also need to update the selection variables you use in the tick handler function. You haven't posted the code for this, but my guess is that the variables you're using in the tick handler still reference the old selections with the now removed elements. – Lars Kotthoff Oct 01 '14 at 21:03
  • Alright I now added the whole source code on the bottom. I know that I have to update the selects. But I'm currently already doing it. Don't I? – kwoxer Oct 02 '14 at 09:38
  • And not to forget. In the code on the bottom if I remove the lines link.exit().remove(); and node.exit().remove(); it works without any errors. So yeah why is it that way? – kwoxer Oct 02 '14 at 09:43
  • If you are interested about a detailed picture based issue description just let me know and I'll create it so you can actually see the problem on an example. If you would like to see a live presentation it's also no problem, just add me in Skype or elsewhere. Thank you. – kwoxer Oct 02 '14 at 10:06
  • 1
    Alright I found a way to solve my problem. It's basically the old version with svg.remove(); and here I had a problem with the zoom. So now I save the current zoom and set it on the end of the createGraph() methode. That works perfectly. Now my graph can be zoomed and also nodes can be deleted or remove. But if someone has an idea why the exit().remove() does not work. I would be really interested if it's a thing with the code or an issue. Thank you. – kwoxer Oct 02 '14 at 11:57
  • 1
    If you add nodes or links then you will probably have this exact problem if you don't stop the layout first. This is because there are arrays closured inside the layout that are assumed to have lengths matching the nodes and links. Not sure about removing elements, but anyway, all of this is re-aligned in `force.start()`... Calculations on missing elements will error in the next tick, so you should always stop the layout, change its structure and then call `force.start()`. This way the internal structures will always be aligned with the nodes and links... – Cool Blue Apr 28 '15 at 14:38

0 Answers0