将新节点添加到强制定向布局 [英] Adding new nodes to Force-directed layout

查看:143
本文介绍了将新节点添加到强制定向布局的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Stack Overflow的第一个问题,所以跟我一起!我是d3j的新手,但一直惊讶于别人能够完成它...和几乎令人惊讶的是,我已经能够与自己做了很少的进展!显然,我不是在填充东西,所以我希望这里的亲切的灵魂可以给我的光。



我的目的是做一个可重用的javascript函数,




  • 在指定的DOM元素中创建空白强制定向图

  • 允许您


我已经使用了:





此演示包含最终帮助我解决问题的键。



输入()上添加多个对象可以通过分配 enter()到一个变量,然后附加到。这是有道理的。第二个关键部分是节点和链接数组必须基于 force() - 否则图形和模型将失去同步,因为节点被删除和添加。



这是因为如果创建了一个新的数组,它将缺少以下属性



    <节点数组中节点的从零开始的索引。
  • x - 当前节点位置的x坐标。

  • y -

  • px - 上一个节点位置的x坐标。

  • py - y坐标

  • fixed - 一个布尔值,表示节点位置是否被锁定。

  • weight - 节点权重;

这些属性不是对 force.nodes() ,但如果这些不存在,则它们将随机地由 force.start()



任何人都好奇,工作代码看起来像这样:

 < script type =text / javascript 

function myGraph(el){

//在图形对象上添加和删除元素
this.addNode = function(id){
nodes .push({id:id});
update();
}

this.removeNode = function(id){
var i = 0;
var n = findNode(id);
while(i if((links [i] ['source'] === n)||(links [i] ['target'] == n ))links.splice(i,1);
else i ++;
}
var index = findNodeIndex(id);
if(index!== undefined){
node.splice(index,1);
update();
}
}

this.addLink = function(sourceId,targetId){
var sourceNode = findNode(sourceId);
var targetNode = findNode(targetId);

if((sourceNode!== undefined)&&(targetNode!== undefined)){
links.push({source:sourceNode,target:targetNode });
update();
}
}

var findNode = function(id){
for(var i = 0; i< nodes.length; i ++){
if(nodes [i] .id === id)
return nodes [i]
};
}

var findNodeIndex = function(id){
for(var i = 0; i< nodes.length; i ++){
if i] .id === id)
return i
};
}

//在指定的元素中设置D3可视化
var w = $(el).innerWidth(),
h = $(el)。 innerHeight();

var vis = this.vis = d3.select(el).append(svg:svg)
.attr(width,w)
.attr height,h);

var force = d3.layout.force()
.gravity(.05)
.distance(100)
.charge(-100)
.size([w,h]);

var nodes = force.nodes(),
links = force.links();

var update = function(){

var link = vis.selectAll(line.link)
.data(links,function(d){ return d.source.id + - + d.target.id;});

link.enter()。insert(line)
.attr(class,link);

link.exit()。remove();

var node = vis.selectAll(g.node)
.data(nodes,function(d){return d.id;});

var nodeEnter = node.enter()。append(g)
.attr(class,node)
.call(force.drag);

nodeEnter.append(image)
.attr(class,circle)
.attr(xlink:href,https:// d3nwyuy0nl342s .cloudfront.net / images / icons / public.png)
.attr(x,-8px)
.attr(y,-8px)
.attr(width,16px)
.attr(height,16px);

nodeEnter.append(text)
.attr(class,nodetext)
.attr(dx,12)
.attr (dy,.35em)
.text(function(d){return d.id});

node.exit()。remove();

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 +,+ dy +);});
});

//重新启动强制布局。
force.start();
}

//一切顺利
update();
}

graph = new myGraph(#graph);

//你可以从控制台做到这一点,你喜欢...
graph.addNode(Cause);
graph.addNode(Effect);
graph.addLink(Cause,Effect);
graph.addNode(A);
graph.addNode(B);
graph.addLink(A,B);

< / script>


First question on Stack Overflow, so bear with me! I am new to d3.js, but have been consistently amazed by what others are able to accomplish with it... and almost as amazed by how little headway I've been able to make with it myself! Clearly I'm not grokking something, so I hope that the kind souls here can show me the light.

My intention is to make a reusable javascript function which simply does the following:

  • Creates a blank force-directed graph in a specified DOM element
  • Allows you to add and delete labeled, image-bearing nodes to that graph, specifying connections between them

I've taken http://bl.ocks.org/950642 as a starting point, since that's essentially the kind of layout I want to be able to create:

Here's what my code looks like:

<!DOCTYPE html>
<html>
<head>
    <script type="text/javascript" src="jquery.min.js"></script>
    <script type="text/javascript" src="underscore-min.js"></script>
    <script type="text/javascript" src="d3.v2.min.js"></script>
    <style type="text/css">
        .link { stroke: #ccc; }
        .nodetext { pointer-events: none; font: 10px sans-serif; }
        body { width:100%; height:100%; margin:none; padding:none; }
        #graph { width:500px;height:500px; border:3px solid black;border-radius:12px; margin:auto; }
    </style>
</head>
<body>
<div id="graph"></div>
</body>
<script type="text/javascript">

function myGraph(el) {

    // Initialise the graph object
    var graph = this.graph = {
        "nodes":[{"name":"Cause"},{"name":"Effect"}],
        "links":[{"source":0,"target":1}]
    };

    // Add and remove elements on the graph object
    this.addNode = function (name) {
        graph["nodes"].push({"name":name});
        update();
    }

    this.removeNode = function (name) {
        graph["nodes"] = _.filter(graph["nodes"], function(node) {return (node["name"] != name)});
        graph["links"] = _.filter(graph["links"], function(link) {return ((link["source"]["name"] != name)&&(link["target"]["name"] != name))});
        update();
    }

    var findNode = function (name) {
        for (var i in graph["nodes"]) if (graph["nodes"][i]["name"] === name) return graph["nodes"][i];
    }

    this.addLink = function (source, target) {
        graph["links"].push({"source":findNode(source),"target":findNode(target)});
        update();
    }

    // set up the D3 visualisation in the specified element
    var w = $(el).innerWidth(),
        h = $(el).innerHeight();

    var vis = d3.select(el).append("svg:svg")
        .attr("width", w)
        .attr("height", h);

    var force = d3.layout.force()
        .nodes(graph.nodes)
        .links(graph.links)
        .gravity(.05)
        .distance(100)
        .charge(-100)
        .size([w, h]);

    var update = function () {

        var link = vis.selectAll("line.link")
            .data(graph.links);

        link.enter().insert("line")
            .attr("class", "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; });

        link.exit().remove();

        var node = vis.selectAll("g.node")
            .data(graph.nodes);

        node.enter().append("g")
            .attr("class", "node")
            .call(force.drag);

        node.append("image")
            .attr("class", "circle")
            .attr("xlink:href", "https://d3nwyuy0nl342s.cloudfront.net/images/icons/public.png")
            .attr("x", "-8px")
            .attr("y", "-8px")
            .attr("width", "16px")
            .attr("height", "16px");

        node.append("text")
            .attr("class", "nodetext")
            .attr("dx", 12)
            .attr("dy", ".35em")
            .text(function(d) { return d.name });

        node.exit().remove();

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

        // Restart the force layout.
        force
          .nodes(graph.nodes)
          .links(graph.links)
          .start();
    }

    // Make it all go
    update();
}

graph = new myGraph("#graph");

// These are the sort of commands I want to be able to give the object.
graph.addNode("A");
graph.addNode("B");
graph.addLink("A", "B");

</script>
</html>

Every time I add a new node, it re-labels all of the existing nodes; these pile on top of each other and things start to get ugly. I understand why this is: because when I call the update() function function upon adding a new node, it does a node.append(...) to the entire data set. I can't figure out how to do this for only the node I'm adding... and I can only apparently use node.enter() to create a single new element, so that doesn't work for the additional elements I need bound to the node. How can I fix this?

Thank you for any guidance that you're able to give on any of this issue!

Edited because I quickly fixed a source of several other bugs that were previously mentioned

解决方案

After many long hours of being unable to get this working, I finally stumbled across a demo that I don't think is linked any of the documentation: http://bl.ocks.org/1095795:

This demo contained the keys which finally helped me crack the problem.

Adding multiple objects on an enter() can be done by assigning the enter() to a variable, and then appending to that. This makes sense. The second critical part is that the node and link arrays must be based on the force() -- otherwise the graph and model will go out of synch as nodes are deleted and added.

This is because if a new array is constructed instead, it will lack the following attributes:

  • index - the zero-based index of the node within the nodes array.
  • x - the x-coordinate of the current node position.
  • y - the y-coordinate of the current node position.
  • px - the x-coordinate of the previous node position.
  • py - the y-coordinate of the previous node position.
  • fixed - a boolean indicating whether node position is locked.
  • weight - the node weight; the number of associated links.

These attributes are not strictly needed for the call to force.nodes(), but if these are not present, then they would be randomly initialised by force.start() on the first call.

If anybody is curious, the working code looks like this:

<script type="text/javascript">

function myGraph(el) {

    // Add and remove elements on the graph object
    this.addNode = function (id) {
        nodes.push({"id":id});
        update();
    }

    this.removeNode = function (id) {
        var i = 0;
        var n = findNode(id);
        while (i < links.length) {
            if ((links[i]['source'] === n)||(links[i]['target'] == n)) links.splice(i,1);
            else i++;
        }
        var index = findNodeIndex(id);
        if(index !== undefined) {
            nodes.splice(index, 1);
            update();
        }
    }

    this.addLink = function (sourceId, targetId) {
        var sourceNode = findNode(sourceId);
        var targetNode = findNode(targetId);

        if((sourceNode !== undefined) && (targetNode !== undefined)) {
            links.push({"source": sourceNode, "target": targetNode});
            update();
        }
    }

    var findNode = function (id) {
        for (var i=0; i < nodes.length; i++) {
            if (nodes[i].id === id)
                return nodes[i]
        };
    }

    var findNodeIndex = function (id) {
        for (var i=0; i < nodes.length; i++) {
            if (nodes[i].id === id)
                return i
        };
    }

    // set up the D3 visualisation in the specified element
    var w = $(el).innerWidth(),
        h = $(el).innerHeight();

    var vis = this.vis = d3.select(el).append("svg:svg")
        .attr("width", w)
        .attr("height", h);

    var force = d3.layout.force()
        .gravity(.05)
        .distance(100)
        .charge(-100)
        .size([w, h]);

    var nodes = force.nodes(),
        links = force.links();

    var update = function () {

        var link = vis.selectAll("line.link")
            .data(links, function(d) { return d.source.id + "-" + d.target.id; });

        link.enter().insert("line")
            .attr("class", "link");

        link.exit().remove();

        var node = vis.selectAll("g.node")
            .data(nodes, function(d) { return d.id;});

        var nodeEnter = node.enter().append("g")
            .attr("class", "node")
            .call(force.drag);

        nodeEnter.append("image")
            .attr("class", "circle")
            .attr("xlink:href", "https://d3nwyuy0nl342s.cloudfront.net/images/icons/public.png")
            .attr("x", "-8px")
            .attr("y", "-8px")
            .attr("width", "16px")
            .attr("height", "16px");

        nodeEnter.append("text")
            .attr("class", "nodetext")
            .attr("dx", 12)
            .attr("dy", ".35em")
            .text(function(d) {return d.id});

        node.exit().remove();

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

        // Restart the force layout.
        force.start();
    }

    // Make it all go
    update();
}

graph = new myGraph("#graph");

// You can do this from the console as much as you like...
graph.addNode("Cause");
graph.addNode("Effect");
graph.addLink("Cause", "Effect");
graph.addNode("A");
graph.addNode("B");
graph.addLink("A", "B");

</script>

这篇关于将新节点添加到强制定向布局的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆