4

我有一个 d3 force sim,如果我要添加节点,如下所示:

node = node.data(nodes, function(d) { return d.id;});
       node.exit().remove();
       node = node.enter().append('circle')
            .attr("class", function(d) {return d.type;})
            .attr("r", 25)
            .merge(node);

一切正常 - 圆圈被添加到正确的位置,呈现的 html 如下所示:

<svg width="1280" height="960">
    <g transform="translate(640,480)">
        <g stroke="#000" stroke-width="1.5">
            <line x1="197.7877989370864" y1="16.96383936157134" x2="113.39655998594978" y2="176.9054238213185"></line>
            <line x1="-99.71642802229279" y1="182.82652731678513" x2="-206.38001140055673" y2="35.62690731557146"></line>
            <line x1="-111.21899770908817" y1="-104.07607869492837" x2="9.724648489851102" y2="-238.28831674029004"></line>
            <line x1="-111.21899770908817" y1="-104.07607869492837" x2="73.66744043019104" y2="-114.11648500001087"></line>
            <line x1="197.7877989370864" y1="16.96383936157134" x2="10.328317030872993" y2="37.5171491536661"></line>
            <line x1="-99.71642802229279" y1="182.82652731678513" x2="10.328317030872993" y2="37.5171491536661"></line>
            <line x1="-111.21899770908817" y1="-104.07607869492837" x2="10.328317030872993" y2="37.5171491536661"></line>
            <line x1="197.7877989370864" y1="16.96383936157134" x2="73.66744043019104" y2="-114.11648500001087"></line>
        </g>
        <g prop="nodes" stroke="#000" stroke-width="1.5">
            <circle fill="some_image.png" class="Net" r="25" cx="197.7877989370864" cy="16.96383936157134"></circle>
            <circle fill="some_image.png" class="Net" r="25" cx="-99.71642802229279" cy="182.82652731678513"></circle>
            <circle fill="some_image.png" class="Net" r="25" cx="-111.21899770908817" cy="-104.07607869492837"></circle>
            <circle fill="some_image.png" class="Inst" r="25" cx="113.39655998594978" cy="176.9054238213185"></circle>
            <circle fill="some_image.png" class="Inst" r="25" cx="-206.38001140055673" cy="35.62690731557146"></circle>
            <circle fill="some_image.png" class="Inst" r="25" cx="9.724648489851102" cy="-238.28831674029004"></circle>
            <circle fill="some_image.png" class="Inst" r="25" cx="73.66744043019104" cy="-114.11648500001087"></circle>
            <circle fill="some_image.png" class="Internet" r="25" cx="10.328317030872993" cy="37.5171491536661"></circle>
        </g>
    </g>
</svg>

JSfiddle 示例

但如果我想添加组(我的最终设计需要背景图像、标签和各种其他东西),如下所示:

node = node.data(nodes, function(d) { return d.id;});
       node.exit().remove();
       node.enter().append('g')
           .attr('class', 'node')
           .append('image')
           .attr('xlink:href', 'some_image.png')
           .append('text')
           .text(function(d){return d.text;})
           ... and so on...

尽管我的代码似乎得到了正确解释(我附加了组,将图像和标签附加到它们),但这些组保持静态,并且它们彼此重叠地保持在 sim 的中间。此外,似乎坐标变换是转到图像而不是组,这就是我认为破坏模拟的原因:

<svg width="1280" height="960">
    <g transform="translate(640,480)">
        <g stroke="#000" stroke-width="1.5">
            <line x1="197.77682810226557" y1="16.981901068622136" x2="113.3585440445384" y2="176.90457630748227"></line>
            <line x1="-99.99450481197604" y1="182.94091641902205" x2="-206.13047480355274" y2="35.36287517221039"></line>
            <line x1="-111.19343747422879" y1="-103.71666033252438" x2="9.543859895654657" y2="-238.10758089494877"></line>
            <line x1="-111.19343747422879" y1="-103.71666033252438" x2="73.69734375869983" y2="-114.13138675745854"></line>
            <line x1="197.77682810226557" y1="16.981901068622136" x2="10.344170477990337" y2="37.84621823186521"></line>
            <line x1="-99.99450481197604" y1="182.94091641902205" x2="10.344170477990337" y2="37.84621823186521"></line>
            <line x1="-111.19343747422879" y1="-103.71666033252438" x2="10.344170477990337" y2="37.84621823186521"></line>
            <line x1="197.77682810226557" y1="16.981901068622136" x2="73.69734375869983" y2="-114.13138675745854"></line>
        </g>
    <g prop="nodes" stroke="#000" stroke-width="1.5">
        <g class="node"><image xlink:href="some_image.png" x="0" y="0" height="72" width="72" style="z-index: 3;"></image></g>
        <g class="node"><image xlink:href="some_image.png" x="-7.373688780783198" y="6.754902942615239" height="72" width="72" style="z-index: 3;"></image></g>
        <g class="node"><image xlink:href="some_image.png" x="1.2363864559502138" y="-14.087985964343622" height="72" width="72" style="z-index: 3;"></image></g>
        <g class="node"><image xlink:href="some_image.png" x="10.538470205147267" y="13.745568221620495" height="72" width="72" style="z-index: 3;"></image></g>
        <g class="node"><image xlink:href="some_image.png" x="-19.694269706308575" y="-3.4836390075862327" height="72" width="72" style="z-index: 3;"></image></g>
        <g class="node"><image xlink:href="some_image.png" x="18.866941955758957" y="-12.001604111035421" height="72" width="72" style="z-index: 3;"></image></g>
        <g class="node"><image xlink:href="some_image.png" x="-6.358980820385529" y="23.65509169134563" height="72" width="72" style="z-index: 3;"></image></g>
        <g class="node"><image xlink:href="some_image.png" x="-12.194453649142762" y="-23.479678451778437" height="72" width="72" style="z-index: 3;"></image></g>
        </g>
    </g>
</svg>

JSfiddle 示例

我非常肯定组的使用会搞砸一切,但我无法理解如何正确使用它们。

感谢任何帮助。

这是片段形式的完整布局:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Parse tester</title>
    <script src="https://d3js.org/d3.v4.min.js" type="text/javascript"></script>
</head>
<body>
<script>
    var nodes = [
    {id:0 , label:'branch1' , name:'branch1'},
    {id:1 , label:'branch2' , name:'branch2'},
    {id:2 , label:'branch3' , name:'branch3'},
    {id:3 , label:'leaf1' , name:'leaf1'},
    {id:4 , label:'leaf2' , name:'leaf2'},
    {id:5 , label:'leaf3' , name:'leaf3'},
    {id:6 , label:'center' , name:'center'},
    {id:7 , label:'leaf23' , name:'leaf23'}
    ];
    var links = [
    {source:0 ,target:3, distance:150, weight:1},
    {source:1 ,target:4, distance:150, weight:1},
    {source:2 ,target:5, distance:150, weight:1},
    {source:7 ,target:0, distance:150, weight:1},
    {source:7 ,target:1, distance:150, weight:1},
    {source:7 ,target:2, distance:150, weight:1},
    {source:1 ,target:6, distance:150, weight:1},
    {source:2 ,target:6, distance:150, weight:1}
    ];

    //D3 stuff
    var width=640, height = 480;

    // add a SVG to the body for our viz
    var svg=d3.select('body').append('svg')
        .attr('width', width)
        .attr('height', height);

    var simulation = d3.forceSimulation(nodes)
        .force("charge", d3.forceManyBody().strength(-1000))
        .force("link", d3.forceLink(links).distance(200))
        .force("x", d3.forceX())
        .force("y", d3.forceY())
        .alphaTarget(1)
        .on("tick", ticked);

    var g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"),
        link = g.append("g").attr("stroke", "#000").attr("stroke-width", 1.5).selectAll(".link"),
        node = g.append("g").attr("stroke", "#000").attr("stroke-width", 1.5).selectAll(".node");
    
    restart();
        function restart() {

            // Apply the general update pattern to the nodes.
            node = node.data(nodes, function(d) { return d.id;});
            node.exit().remove();
            node = node.enter()
            			.append('g')
            			.append('image')
                  .attr('xlink:href', 'http://i.imgur.com/Rx4N3wh.png')
                  .attr('width',25)
                  .attr('height',25)
                  .attr('x', function (d) {return d.x;})
                  .attr('y', function (d) {return d.y;})
                  .merge(node);
            
            node.enter().selectAll('g').append('text')
            .attr('text-anchor', 'middle')
            .attr('dy', '.35em')
            .attr('y', -40)
            .text(function (d) {
                return d.label
            });
            
            // Apply the general update pattern to the links.
            link = link.data(links, function(d) { return d.source.id + "-" + d.target.id; });
            link.exit().remove();
            link = link.enter().append("line").merge(link);

            // Update and restart the simulation.
            simulation.nodes(nodes);
            simulation.force("link").links(links);
            simulation.alpha(1).restart();
        }
//*/


    function ticked() {
        node.attr("cx", function(d) { return d.x; })
            .attr("cy", function(d) { return 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; });
    }

</script>

4

1 回答 1

1

首先,我得到了第二个小提琴,在链式方法之间有一个额外的 } 和几个逗号:fiddle

所以,在第一个小提琴中,根据我的理解,一切正常:链接和节点按照力布局的指示移动。在小提琴二中,链接继续移动,但现在g带有图像的节点不会移动任何东西。

据我了解,关键问题是“为什么g节点会破坏力布局?” 但似乎还有一些关于每个g节点中的刻度函数和嵌套元素的潜在问题。

强制刻度功能和更新模式

让我们看看您对两者都使用的刻度函数:

function ticked() {
    node.attr("cx", function(d) { return d.x; })
        .attr("cy", function(d) { return 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; });
}

滴答函数在每个滴答声中被调用,它根据每个节点的数据更新每个节点。力模拟不操纵视觉元素,它操纵节点数据数组。在初始化时,d3 force 为数据数组中的每个节点创建新属性,例如表示速度和位置的属性。所以 d3 正在更新您的数据,而不是您的元素。这就是为什么我们需要一个刻度函数。

现在上面不是 d3 中的典型更新模式(但它是 d3-force 的规范)。典型的更新模式通常如下所示:

d3.selectAll(".node")
 .data(data)
 .attr("...")
 .attr("...")...

链可以通过中间退出/进入/合并选择进行拆分

然而,对于对象,即数据数组中的节点,d3 不会复制数据以将其绑定到每个元素,d3 实际上将每个元素链接到数据数组中的一个项目。这意味着node.attr("cx", function(d) {,d指的是数据数组中链接/绑定的更新项,不需要selection.data().

我提到这一点是因为它是非典型的,不为人所知(在我看来),并且没有在示例或教程中解释为什么强制使用(或可以使用)不同的更新模式。此外,鉴于您的评论“坐标由 sim 确定并不断动态重新计算。这让我感到困惑”,这可能是一个混乱的根源

什么是node

选择node应该是g元素的选择,但实际上是image元素的选择:

node = node.enter()      
   .append('g')      // returns a selection of `g` elements
   .append('image')  // returns a selection of `image` elements.
   ...

您选择的元素不是gs,而是子image元素。将它们与其他元素合并可能会导致问题。分解链接,保留node节点的选择,在这种情况下是您的g元素。然后,我们可以更轻松地将任意数量的子节点附加到每个节点。

(为了它,这里有一个关于这种变化的小提琴,但我们还没有解决为什么什么都没有移动)。

什么打破了蜱虫?

正如我在评论中指出的那样,您已将节点从 a 更改circle为 a g,并且您注意到您的节点最初是放置的,但不会更新。这是因为您需要更改刻度函数。您可以这样更新节点:

    node.attr("cx", function(d) { return d.x; })
        .attr("cy", function(d) { return d.y; });  

但是,node 现在是gs 的选择,并且gs 不是由cxandcy属性定位的。让我们将其更改为:

node.attr("transform", function(d) { return "translate("+[d.x,d.y]+")"; })

这是一个更新的小提琴,其中节点随每个 udpate 移动。但是,现在需要固定定位。

定位

现在我们在每个刻度上更新节点,并且每个节点g都被平移,使得 [0,0] 是该节点的中心。我看到的图像是 25 像素正方形,因此要使图像居中,我们需要为每个图像的 x 和 y 位置使用负 12.5(小提琴)。

我没有使用 dx 或 dy 像在第二小提琴中那样定位图像,因为通过定位g,我可以更容易地定位相对于节点的所有内容,并且我只需要在每个刻度更新每个节点的一个元素,g. 否则,我必须每次更新所有标签、图像等。

上面括号中的小提琴最初也不会定位节点-您可以这样做,但是a)不定位它们更容易,b)您必须非常敏锐地看到它们在第一次滴答之前放错了位置-但是有些人是非常老鹰眼,所以将它们放在进入时没有害处(为了简洁起见,我没有这样做)。

为什么标签不显示

到目前为止,我已经将标签代码保留为残留代码块(我一开始没有看到它),但现在我们可以仔细看看:

      node.enter().selectAll('g').append('text')
        .attr('text-anchor', 'middle')
        .attr('dy', '.35em')
        .attr('y', -40)
        .text(function (d) {
            return d.label
        });

node.enter()为需要输入的每个节点返回占位符(与用于g父节点时相同。但是,这些占位符不包含任何gs,因此node.enter().selectAll("g")将为空,因此文本不会附加到任何元素。

我们希望每个节点都有文本,并且每个节点都在 selectionnode中,所以我们只需使用:

node.append("text")....

这是您的标签的更新小提琴

您可以通过这种方式将任何其他子节点附加到节点,例如.

您不需要为孩子使用 .data() 或任何东西,因为 d3 将为每个孩子提供与其父母相同的数据。

即使您node.append()在第二个小提琴中使用,也node代表了images 的选择-并且您不能将文本附加到图像-因此不会显示任何文本。

为了让答案更加独立,这里是最终结果的一个片段:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Parse tester</title>
    <script src="https://d3js.org/d3.v4.min.js" type="text/javascript"></script>
</head>
<body>
<script>
    var nodes = [
    {id:0 , label:'branch1' , name:'branch1'},
    {id:1 , label:'branch2' , name:'branch2'},
    {id:2 , label:'branch3' , name:'branch3'},
    {id:3 , label:'leaf1' , name:'leaf1'},
    {id:4 , label:'leaf2' , name:'leaf2'},
    {id:5 , label:'leaf3' , name:'leaf3'},
    {id:6 , label:'center' , name:'center'},
    {id:7 , label:'leaf23' , name:'leaf23'}
    ];
    var links = [
    {source:0 ,target:3, distance:150, weight:1},
    {source:1 ,target:4, distance:150, weight:1},
    {source:2 ,target:5, distance:150, weight:1},
    {source:7 ,target:0, distance:150, weight:1},
    {source:7 ,target:1, distance:150, weight:1},
    {source:7 ,target:2, distance:150, weight:1},
    {source:1 ,target:6, distance:150, weight:1},
    {source:2 ,target:6, distance:150, weight:1}
    ];

    //D3 stuff
    var width=640, height = 480;

    // add a SVG to the body for our viz
    var svg=d3.select('body').append('svg')
        .attr('width', width)
        .attr('height', height);

    var simulation = d3.forceSimulation(nodes)
        .force("charge", d3.forceManyBody().strength(-1000))
        .force("link", d3.forceLink(links).distance(200))
        .force("x", d3.forceX())
        .force("y", d3.forceY())
        .alphaTarget(1)
        .on("tick", ticked);

    var g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"),
        link = g.append("g").attr("stroke", "#000").attr("stroke-width", 1.5).selectAll(".link"),
        node = g.append("g").attr("stroke", "#000").attr("stroke-width", 1.5).selectAll(".node");
    
    restart();
        function restart() {

            // Apply the general update pattern to the nodes.
            node = node.data(nodes, function(d) { return d.id;});
            node.exit().remove();
            node = node.enter()
            			.append('g')
                  .attr("class","node")
                  .merge(node)
                  
                  
            node.append('image')
                  .attr('xlink:href', 'http://i.imgur.com/Rx4N3wh.png')
                  .attr('width',25)
                  .attr('height',25)
                  .attr('x', -12.5)
                  .attr('y', -12.5)
                  
            
            node.append('text')
            .attr('text-anchor', 'middle')
            .attr('dy', '.35em')
            .attr('y', -40)
            .text(function (d) {
                return d.label
            });
            
            node.append("rect")
              .attr("x", -12.5)
              .attr("y", -12.5)
              .attr("width",25)
              .attr("height",25)
              .attr("stroke-width", 4)
              .attr("stroke","steelblue")
              .attr("fill","none")
            
            // Apply the general update pattern to the links.
            link = link.data(links, function(d) { return d.source.id + "-" + d.target.id; });
            link.exit().remove();
            link = link.enter().append("line").merge(link);

            // Update and restart the simulation.
            simulation.nodes(nodes);
            simulation.force("link").links(links);
            simulation.alpha(1).restart();
        }
//*/


    function ticked() {
        node.attr("transform", function(d) { return "translate("+[d.x,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; });
    }

</script>

于 2018-06-20T06:49:21.020 回答