本文介绍了D3更新节点删除总是删除SVG DOM中的最后一个条目的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在D3应用程序中看到一个奇怪的行为,经过几个小时的努力想出发生的事情,我希望有人能指出我明显做错了。



我已经简化了应用程序,非常简单,仍然显示的问题。你会看到它从所有伟大的D3例子派生。
我遇到一个问题的简单场景是:选择一个节点(通过点击它),并且在击中删除键时删除所述节点以及节点和链接的所有相关链接和标签。 p>

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



对任何可能发生的任何想法都将非常感激!



代码:

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

var svg = d3.select(body)append(svg)
.attr(width,width)
.attr高度);

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);


//
//函数
//

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

//
//节点
//
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;
})
。 (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 d){
return d.name;
});

//
//链接
//
link = svg.selectAll(。link)
.data(jsonlinks);
link.enter()。append(line)
.attr(class,link);

//
//标签(用于链接)
//
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 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){returntranslate(+ dx + + dy +);});
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颜色(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);

//
//标签
//
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 ){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);
}
}

//返回名为value name的节点的节点集合中的位置/索引
function findNodeIndex(coll,name){
if(coll === null)
return null;
for(var i = 0; i if(coll [i] .name === name){
return i;
}
}
return null;
}

//返回与提供的节点名匹配的第一个链接的位置/索引
function findFirstLinkIndex(coll,name){
if(coll == = null)
return null;
for(var i = 0; i if((coll [i] .source.name === name)||(coll [i] .target。 name === name))
return i;
}
return null;
}


解决方案

从数组中间删除数据元素,需要为数据连接指定一个键函数,因此d3知道哪个数据应该与哪个元素一起使用。否则,数据按照它们被找到的顺序与元素匹配,当没有足够的数据去处理时,最后一个元素是最后被删除的元素。








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

  node = svg.selectAll(。node)
.data(jsonnodes,function {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;});


I'm seeing a weird behaviour in my D3 application and after hours of trying to figure out what's happening I hope someone can point me at the thing I obviously do wrong.

I have simplified the app down to be very simple and still exhibit the problem. As you'll see it's derived from all the great D3 examples out there.The simple scenario I have an issue with is: select a node (by clicking on it) and, upon hitting the delete key remove said node along with all related links and labels of both the node and the links.

The code pasted below is nearly there since it decreases the number of Nodes and Links exactly as anticipated (given any particular graph) but there is one issue: both the node and link labels are not the correct ones and end up distributed over different circles...

Any idea as to what might be going on would be greatly appreciated!

Code:

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;
}
解决方案

If you are going to be deleting data elements from the middle of your array, you need to specify a key function to the data join so d3 knows which data should go with which element. Otherwise, the data is matched to elements in the order they are found and when there isn't enough data to go around the last element is the one that ends up removed.

Since you're using the name property of each data element as the identifier for removing elements, that is the logical choice for a data key.

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;});

这篇关于D3更新节点删除总是删除SVG DOM中的最后一个条目的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-05 11:53
查看更多