【问题标题】:D3 Force Directed Graph - Removing NodesD3 力有向图 - 删除节点
【发布时间】:2019-04-23 03:33:13
【问题描述】:

我正在尝试合并 this example 以从强制有向图中添加/删除节点。我能够很好地添加节点,并且由于某种原因,我还能够毫无问题地删除我添加的节点。但是,当我尝试删除任何原始节点时,该节点不会从显示中删除,并且还会破坏许多其他节点的链接。但是,它及其链接已根据日志从 JSON 中正确删除。

This example 使用的方法似乎与我使用拼接删除节点的方法相同。

Here 似乎也使用了拼接方法,尽管我没有完全遵循它在过滤方面所做的其余部分。

There is also this question 提出了一些类似的问题,尽管答案只是添加了地址。

我尝试在附加后退出/删除不起作用。我也尝试过使用.insert 而不是.appendupdate() 函数期间的 console.log 打印正在使用的 JSON,它显示节点和链接都已正确删除,但显示并未反映这一点。

相关代码如下。

初始化/更新图表

//Get the SVG element
var svg = d3.select("svg");

var width = 960, height = 600;
var color = d3.scaleOrdinal(d3.schemeCategory20);

var link = svg.append("g").selectAll(".link");
var node = svg.append("g").selectAll(".node");
var label = svg.append("g").selectAll(".label");

//Begin the force simulation
var simulation = d3.forceSimulation()
    .force("link", d3.forceLink().id(function (d) { return d.id; }).distance(50).strength(0.3))
    .force("charge", d3.forceManyBody().strength(-15))
    .force("center", d3.forceCenter(width / 2, height / 2));

//Highlight variables
var highlight_color = "blue";
var tHighlight = 0.05;

var config;

var linkedByIndex = {};

//Get the data
d3.json("/../../data.json", function (data) {
    //if (!localStorage.graph)
    //{
        localStorage.graph = JSON.stringify(data);
    //}
    update();
    forms();
});

function update() {

    config = JSON.parse(localStorage.graph);
    console.log(JSON.stringify(config));
    linkedByIndex = {};
    //Create an array of source,target containing all links
    config.links.forEach(function (d) {
        linkedByIndex[d.source + "," + d.target] = true;
        linkedByIndex[d.target + "," + d.source] = true;
    });

    //Draw links
    link = link.data(config.links);
    link.exit().remove();
    link = link.enter().append("line")
            .attr("class", "link")
            .attr("stroke-width", 2)
            .attr("stroke", "#888")
            //.attr("opacity", function (d) { if (d.target.radius > 7) { return 1 }; return 0; })
            .merge(link);         


    node = node.data(config.nodes);
    node.exit().remove();
    node = node.enter().append("circle")
            .attr("class", "node")
            .attr("r", function(d) { return d.radius; })
            .attr("fill", function (d) { return color(d.id); })
            .attr("stroke", "black")
        //  .attr("pointer-events", function (d) { if (d.radius <= 7) { return "none"; } return "visibleAll"; })
        //  .attr("opacity", function (d) { if (d.radius <= 7) { return 0; } return 1; })
            .call(d3.drag()
            .on("start", dragstarted)
            .on("drag", dragged)
            .on("end", dragended))
            .on("mouseover", mouseOver)
            .on("mouseout", mouseOut)
            .merge(node);

    label = label.data(config.nodes);
    label.exit().remove();
    label = label.enter().append("text")
            .attr("class", "label")
            .attr("dx", function (d) { return d.radius * 1.25; })
            .attr("dy", ".35em")
            .attr("opacity", function (d) { if (d.radius <= 7) { return 0; } return 1; })
            .attr("font-weight", "normal")
            .style("font-size", 10)
            .text(function (d) { return d.id; })
            .merge(label);

    //Add nodes to simulation
    simulation
        .nodes(config.nodes)
        .on("tick", ticked);

    //Add links to simulation
    simulation.force("link")
        .links(config.links);

    simulation.alphaTarget(0.3).restart();
}

//Animating by ticks function
function ticked() {
    node
        .attr("cx", function (d) { return d.x = Math.max(d.radius, Math.min(width - d.radius, d.x)); })
        .attr("cy", function (d) { return d.y = Math.max(d.radius, Math.min(height - d.radius, d.y)); });
    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; });
    label
        .attr("x", function (d) { return d.x = Math.max(d.radius, Math.min(width - d.radius, d.x)); })
        .attr("y", function (d) { return d.y = Math.max(d.radius, Math.min(height - d.radius, d.y)); });
}

//Using above array, check if two nodes are linked
function isConnected(node1, node2) {
    return linkedByIndex[node1.id + "," + node2.id] || node1.index == node2.index;
}

添加/删除节点和链接。 通过这种方式,添加对节点和链接都非常有效。

function newNode(name, rad)
{
    if (name == "")
    {
        alert("Must specify name");
        return;
    }
    console.log("Adding node with name: " + name + " and radius: " + rad);
    var temp = JSON.parse(localStorage.graph);
    temp.nodes.push({ "id": name, "radius": Number(rad) });
    localStorage.graph = JSON.stringify(temp);
    update();
}

function newLink(source, target)
{
    var foundSource = false;
    var foundTarget = false;

    if (source == "" && target == "")
    {
        alert("Must specify source and target");
        return;
    }
    else if(source == "")
    {
        alert("Must specify source");
        return;
    }
    else if (target == "")
    {
        alert("Must specify target")
        return;
    }

    var temp = JSON.parse(localStorage.graph);

    for (var i=0; i < temp.nodes.length; i++)
    {
        if(temp.nodes[i]['id'] === source)
        {
            foundSource = true;
        }

        if(temp.nodes[i]['id'] === target)
        {
            foundTarget = true;
        }
    }

    if (foundSource && foundTarget) {
        temp.links.push({ "source": source, "target": target });
        localStorage.graph = JSON.stringify(temp);
        update();
    }
    else {
        alert("Invalid source or target");
        return;
    }

    return;
}

function removeLink(linkSource, linkTarget)
{

}

function removeNode(nodeName)
{
    var temp = JSON.parse(localStorage.graph);
    var found = false;

    if (nodeName == "")
    {
        alert("Must specify node name");
        return;
    }

    for(var i=0; i<temp.nodes.length; i++)
    {
        if(temp.nodes[i]['id'] === nodeName)
        {
            console.log("Removing node: " + nodeName);
            found = true;
            temp.nodes.splice(i, 1);
            temp.links = temp.links.filter(function (d) { return d.source != nodeName && d.target != nodeName; });
        }
    }

    if(!found)
    {
        alert("Node does not exist");
        return;
    }

    localStorage.graph = JSON.stringify(temp);

    update();
}

JSON 数据格式

{
  "nodes":[
   {
      "id": "id1",
       "radius": 5},
   {
      "id: "id2",
       "radius": 6}
],

"links":[{
"source": "id1",
"target": "id2"    
]
}

【问题讨论】:

  • 嗨!我正在尝试在这里重现您的问题:stackblitz.com/edit/q53397252。我已将函数 newNodenewLinkremoveNoderemoveLink 设为全局函数,因此您可以在控制台中调用它们。在我的浏览器中,删除任何初始节点后,我没有看到任何错误。
  • 由于某种原因,我无法使用该站点,它只是说“正在启动开发服务器”并且永远不会加载。你如何从控制台调用函数?现在我正在使用一些按钮,所以我可以使用控制台测试它是否有效
  • 更新:我尝试从控制台运行删除节点,但它仍然无法正确更新显示。为了澄清@YaroslavSergienko,没有出现任何错误……但显示不正确。节点将变得混乱,不再存在于 JSON 中的“已删除”节点通常仍然存在。

标签: javascript html d3.js force-layout


【解决方案1】:

默认情况下,d3 通过索引连接数据,因此在您删除节点后,它会错误地分配数据。解决方案是将第二个参数传递给.data 函数。你需要更换

node = node.data(config.nodes);

node = node.data(config.nodes, d => d.id);

您也可以对链接执行此操作,但如果它们的样式相同(即它们仅相差 x1x2y1y2),则没有区别。

更新:您也应该对标签执行此操作

label = label.data(config.nodes, d => d.id);

【讨论】:

  • 我尝试更改此设置,但看到的结果相同。在这种情况下,是否有一些关于数据连接方式的文档可以参考?看起来确实是这样的原因,因为在移除时颜色也会混淆。
  • 最全面的加入它这个:bost.ocks.org/mike/selection/#key。您能否分享带有颜色的示例和重现问题的步骤?
  • 节点的颜色由其 ID 定义。这是由我的代码绘制的标准图:imgur.com/CcTL4mL 接下来我执行 removeNode('CCC') 这是结果:imgur.com/a/d0ThkhU 大多数节点保持不变,一些改变颜色和名称。 (KYC 更改为 MAL,MAL 更改为 CCC)但是 JSON 显示没有加载名称为“CCC”的节点。
  • 我没有注意到,你也有标签,所以你也应该为它们传递第二个参数
猜你喜欢
  • 1970-01-01
  • 2013-09-01
  • 2014-12-05
  • 2012-11-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-30
  • 2012-09-11
相关资源
最近更新 更多