节点删除的D3更新总是删除SVG DOM中的最后一个条目

时间:2014-03-07 03:28:55

标签: javascript svg d3.js labels

我在我的D3应用程序中看到一种奇怪的行为,经过数小时的努力弄清楚发生了什么,我希望有人可以指出我明显做错的事情。

我将应用简化为非常简单,但仍然存在问题。正如你所看到的那样,它源于所有伟大的D3例子。 我遇到的一个简单方案是:选择一个节点(通过点击它),并在点击删除键时删除所述节点以及节点和链接的所有相关链接和标签。

下面粘贴的代码几乎就在那里,因为它完全按照预期减少了节点和链接的数量(给定任何特定的图形),但是有一个问题:节点和链接标签都不正确,最终分布在不同的圈子......

对于可能发生的事情的任何想法将不胜感激!

代码:

var width = 960,
    height = 700,
    colors = d3.scale.category20();

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

var force = d3.layout.force()
    .gravity(.05)
    .distance(200)
    .charge(-150)
    .size([width, height]);

var jsonnodes, jsonlinks;
var node, link, label;
var selected_node = null,
    mousedown_node = null,
    mousedown_link = null;

d3.json("graph.json", jsondatacallback);


//
// Functions
//

function jsondatacallback(error, json) {
jsonnodes = json.nodes;
jsonlinks = json.links;
force.nodes(jsonnodes)
        .links(jsonlinks);

//
// Nodes
//
node = svg.selectAll(".node")
        .data(jsonnodes);    
node.enter().append("g")
        .attr("class", "node")
        .on('mousedown', function(d) {
            mousedown_node = d;
            if (mousedown_node === selected_node)
                selected_node = null;
            else
                selected_node = mousedown_node;
        })
        .call(force.drag);
node.append("circle")
        .attr('r', 11)
        .style('stroke', function(d) {
            return d3.rgb(colors(d.name)).darker().toString();
        });
node.append("text")
        .attr("dx", 12)
        .attr("dy", ".35em")
        .text(function(d) {
            return d.name;
        });

//
// Links
//
link = svg.selectAll(".link")
        .data(jsonlinks);  
link.enter().append("line")
        .attr("class", "link");

//
// Labels (for links)
//
label = svg.selectAll(".label")
        .data(jsonlinks);
label.enter().append("text")
        .attr("class", "label");    
label.attr("dx", 12)
        .attr("dy", ".35em")
        .attr("x", function(d) {return (d.source.x + d.target.x) / 2;})
        .attr("y", function(d) {return (d.source.y + d.target.y) / 2;})
        .text(function(e) {
            return Math.random().toString(36).substring(7); ;  
        });

force.on("tick", function() {
    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 + ")";});
    label.attr("x", function(d) {return (d.source.x + d.target.x) / 2;})
            .attr("y", function(d) {return (d.source.y + d.target.y) / 2;});
});

d3.select(window)
        .on("keydown", keydown);
restart();
}

function keydown() {
d3.event.preventDefault();
var lastKeyDown = d3.event.keyCode;

if (!selected_node)
    return;
switch (d3.event.keyCode) {
    case 8: // backspace
    case 46: // delete
        if (selected_node) {
            removeNode(selected_node);
            removeLinks(selected_node);
        }
        selected_node = null;
        restart();
        break;
}
}

function restart() {
//
// nodes
//
node = svg.selectAll(".node")
        .data(jsonnodes);
node.exit().remove();
node.style('fill', function(d) {
            return (d === selected_node) ? d3.rgb(colors(d.name)).brighter().toString() : colors(d.name);
        })
        .on('mousedown', function(d) {
            mousedown_node = d;
            if (mousedown_node === selected_node)
                selected_node = null;
            else
                selected_node = mousedown_node;
            restart();
        });
node.enter().append("g")
        .attr("class", "node")
        .on('mousedown', function(d) {
            mousedown_node = d;
            if (mousedown_node === selected_node)
                selected_node = null;
            else
                selected_node = mousedown_node;
        });
node.enter().append("text")
        .attr("dx", 12)
        .attr("dy", ".35em")
        .text(function(d) {
            return Math.random().toString(36).substring(7);
        });
node.enter().append("circle")
        .attr('r', 11)
        .style('stroke', function(d) {
            return d3.rgb(colors(d.name)).darker().toString();
        });

//
// links
//
link = svg.selectAll(".link")
        .data(jsonlinks);
link.exit().remove();
link.enter().append("line")
        .attr("class", "link");

//
// labels
//
label = svg.selectAll(".label")
        .data(jsonlinks);
label.exit().remove();
label.enter().append("text")
        .attr("class", "label")
        .text(function(d) {
            var lbl = d.source.name + "_" + d.target.name;
            return lbl ;
        });    
label.attr("x", function(d) {return (d.source.x + d.target.x) / 2;})
        .attr("y", function(d) {return (d.source.y + d.target.y) / 2;});;

force.start();
}

function removeNode(victim) {
var searchres = findNodeIndex(jsonnodes, victim.name);
if (searchres === null) {
    console.log("Node to be removed not found.");
} else {
    jsonnodes.splice(searchres, 1);
}
}

function removeLinks(victim) {
var searchres = findFirstLinkIndex(jsonlinks, victim.name);
if (searchres !== null) {
    jsonlinks.splice(searchres, 1);
    removeLinks(victim);
}
}

// Returns the position/index in node collection of the node with name value name
function findNodeIndex(coll, name) {
if (coll === null)
    return null;
for (var i=0; i<coll.length; i++) {
    if (coll[i].name === name) {
        return i;
    }
}
return null;
}

// Returns the position/index of the first link matching the provided node name
function findFirstLinkIndex(coll, name) {
if (coll === null)
    return null;
for (var i=0; i<coll.length; i++) {
    if ((coll[i].source.name === name) || (coll[i].target.name === name))
        return i;
}
return null;
}

1 个答案:

答案 0 :(得分:9)

如果要从数组中间删除数据元素,则需要为数据连接指定一个键函数,以便d3知道哪个数据应该与哪个元素一起使用。否则,数据按照它们被找到的顺序与元素匹配,并且当没有足够的数据绕过最后一个元素时,最终被删除的元素。

由于您使用每个数据元素的name属性作为删除元素的标识符,因此这是数据键的逻辑选择。

node = svg.selectAll(".node")
        .data(jsonnodes, function(d){return d.name;});    
/*...*/
link = svg.selectAll(".link")
        .data(jsonlinks, 
              function(d){return d.source.name + "_" + d.target.name;});
/*...*/
label = svg.selectAll(".label")
        .data(jsonlinks,  
              function(d){return d.source.name + "_" + d.target.name;});