【问题标题】:D3 update on node removal always remove the last entry in SVG DOM删除节点的 D3 更新总是删除 SVG DOM 中的最后一个条目
【发布时间】:2014-04-10 01:18:22
【问题描述】:

我在我的 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;
}

【问题讨论】:

  • 添加一个key function to all the data joins,这样即使您从数组中间删除了一个数据对象,给定的数据对象也将始终连接到同一个屏幕元素。
  • 哦,哇,非常感谢您的回答和链接;我错过了 D3 文档的那一部分。我只是将“function(d) {return d.name;}”添加到节点的数据选择中,它就可以工作了。有趣的是,我见过的大多数示例都只是依赖于默认的基于索引的行为。干杯!
  • 太棒了。仅当您要在更新时弄乱数据数组顺序时,关键功能才重要——但它们非常非常重要!我会将其添加为正确答案。

标签: javascript svg d3.js labels


【解决方案1】:

如果要从数组中间删除数据元素,则需要为数据连接指定一个键函数,以便 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;});

【讨论】:

    猜你喜欢
    • 2019-01-02
    • 1970-01-01
    • 2022-08-05
    • 2013-03-25
    • 1970-01-01
    • 2016-06-11
    • 1970-01-01
    相关资源
    最近更新 更多