添加节点无法正确链接到现有节点

时间:2015-10-14 14:40:43

标签: javascript d3.js

我有一个利用Force Layout的d3图表。当我在前面有一组节点时,它就可以很好地布局了。具体来说,我的意思是节点保持良好的分离,链接是正确的。

我的问题演示在jsFiddle here上。

我还在下方添加了一个代码片段,其效果与jsFiddle类似。

但是,如果我start有一个节点,然后使用Add Person按钮添加另一个节点,那么您会注意到第一个节点(即使它在链接)不响应,也不能移动。

似乎后者是真正的问题,因为它无法移动。

我尝试过什么

  1. 在图表中添加新人时,在顶部执行force.resume()而不是force...start()结果:这会导致错误,Error: Invalid value for <g> attribute transform="translate(NaN,NaN)"发生,而我还没弄清楚原因。
  2. 在方法结尾处添加force.resume(),在图表中添加新人时,再次添加on('tick' ...。无论force...start()如何,我都会通过执行顶部的resume来结合这一点。 结果:会导致第一个节点再次反弹(有希望),但添加的节点会保留在左上角,就好像它没有连接一样。
  3. &#13;
    &#13;
    var scope = {};
    
    scope.nodes = [];
    scope.links = [];
    
    var width = 960,
        height = 500;
    
    var svg = d3.select("body").append("svg")
        .attr("width", width)
        .attr("height", height);
    
    var force = d3.layout.force()
        .charge(-150)
        .linkDistance(150)
        .size([width, height]);
    
    function renderGraph(resume) {
        force.nodes(scope.nodes)
            .links(scope.links)
            .start();
    
        var link = svg.selectAll(".link")
            .data(scope.links)
            .enter().append("line")
            .attr("class", "link");
    
        var node = svg.selectAll(".node")
            .data(scope.nodes)
            .enter().append("g")
            .attr("class", "node")
            .call(force.drag);
    
        node.append("image")
            .attr("xlink:href", function (d) {
            return d.avatar || 'https://github.com/favicon.ico'
        })
            .attr("x", -56)
            .attr("y", -8)
            .attr("width", 64)
            .attr("height", 64);
    
        node.append("text")
            .attr("dx", 12)
            .attr("dy", ".35em")
            .text(function (d) {
            return d._id === scope.user.profile._id ? 'You' : d.firstName + ' ' + d.lastName
        });
    
        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 + ")";
            });
        });
    }
    
    scope.user = {
        profile: {
            _id: 1,
            firstName: 'Bob',
            lastName: 'Smith'
        }
    };
    scope.nodes.push(scope.user.profile);
    renderGraph();
    
    var b = document.getElementById("addButton");
    b.onclick = addPerson;
    
    function addPerson() {
        scope.nodes.push({
            _id: 2,
            firstName: 'Jane',
            lastName: 'Smith'
        });
        scope.links.push({
            source: 0,
            target: scope.nodes.length - 1
        });
        renderGraph();
    }
    &#13;
    .link {
        stroke: #ccc;
    }
    .node text {
        pointer-events: none;
    }
    &#13;
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
    <button id="addButton">Add Person</button>
    &#13;
    &#13;
    &#13;

2 个答案:

答案 0 :(得分:1)

问题在于:

当你这样做时

var node = svg.selectAll(".node")
        .data(scope.nodes)
        .enter().append("g")
        .attr("class", "node")
        .call(force.drag);

节点具有g元素,并且tick期望选择,即:

var node = svg.selectAll(".node")
        .data(scope.nodes);

应该是:

var link = svg.selectAll(".link")
    .data(scope.links);//this selection is expected in the tick function

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

var node = svg.selectAll(".node")
    .data(scope.nodes);//this selection is expected in the tick function

//attaching text/circle everything pertaining to the node n the g group.
var nodeg = node.enter().append("g")
    .attr("class", "node")
    .call(force.drag);

工作代码here

希望这有帮助!

答案 1 :(得分:1)

我有点慢,但无论如何我都会发布我的答案,因为它与接受的答案略有不同......

每次都不需要将数组重新连接到力布局,所以我将它移到了渲染功能之外,我还通过删除已删除的对象来增加了良好的内务管理,但除此之外,添加的内容不多。 / p>

&#13;
&#13;
var scope = {};

scope.nodes = [];
scope.links = [];

var width = 600,
    height = 190;

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

var force = d3.layout.force()
    .charge(-150)
    .linkDistance(150)
    .size([width, height])
	.nodes(scope.nodes)
    .links(scope.links)
        ;

function renderGraph(resume) {
    force
        .start();

    var link = svg.selectAll(".link")
        .data(scope.links);
    link.enter().append("line")
        .attr("class", "link");
    link.exit().remove();

    var node = svg.selectAll(".node")
        .data(scope.nodes),
    	newNode = node.enter().append("g")
        .attr("class", "node")
        .call(force.drag);
    node.exit().remove();

    newNode.append("image")
        .attr("xlink:href", function (d) {
        return d.avatar || 'https://github.com/favicon.ico'
    })
        .attr("x", -56)
        .attr("y", -8)
        .attr("width", 64)
        .attr("height", 64);

    newNode.append("text")
        .attr("dx", 12)
        .attr("dy", ".35em")
        .text(function (d) {
        return d._id === scope.user.profile._id ? 'You' : d.firstName + ' ' + d.lastName
    });

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

scope.user = {
    profile: {
        _id: 1,
        firstName: 'Bob',
        lastName: 'Smith'
    }
};
scope.nodes.push(scope.user.profile);
renderGraph();

var b = document.getElementById("addButton");
b.onclick = addPerson;

function addPerson() {
    scope.nodes.push({
        _id: 2,
        firstName: 'Jane',
        lastName: 'Smith'
    });
    scope.links.push({
        source: 0,
        target: scope.nodes.length - 1
    });
    renderGraph();
}
&#13;
.link {
    stroke: #ccc;
}
.node text {
    pointer-events: none;
}
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<button id="addButton">Add Person</button>
&#13;
&#13;
&#13;