草庐IT

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

coder 2025-03-01 原文

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

最佳答案

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

关于javascript - 删除节点时的 D3 更新总是删除 SVG DOM 中的最后一个条目,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22240842/

有关javascript - 删除节点时的 D3 更新总是删除 SVG DOM 中的最后一个条目的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  3. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  4. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  5. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  6. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  7. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  8. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

    我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

  9. ruby - 我可以使用 Ruby 从 CSV 中删除列吗? - 2

    查看Ruby的CSV库的文档,我非常确定这是可能且简单的。我只需要使用Ruby删除CSV文件的前三列,但我没有成功运行它。 最佳答案 csv_table=CSV.read(file_path_in,:headers=>true)csv_table.delete("header_name")csv_table.to_csv#=>ThenewCSVinstringformat检查CSV::Table文档:http://ruby-doc.org/stdlib-1.9.2/libdoc/csv/rdoc/CSV/Table.html

  10. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

随机推荐