附加组搞乱了 d3 强制布局中的所有内容
Append groups mess up everything in d3 force layout
我有一个 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>
但是如果我想添加组(我的最终设计需要背景图像、标签和各种额外的东西),就像这样:
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 的中间并彼此重叠。此外,似乎坐标变换转到图像而不是组,这就是我认为破坏 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>
我很肯定使用组会搞砸一切,但我无法理解如何正确使用它们。
感谢任何帮助。
这是片段形式的完整力量布局:
<!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>
首先,我让第二个 fiddle 工作,链式方法之间有一个额外的 } 和几个逗号:fiddle.
因此,在第一个 fiddle 中,根据我的理解,一切正常:链接和节点按照强制布局的指示移动。在 fiddle 二中,链接继续移动,但是节点,现在带有图像的 g
无论如何都不会移动。
根据我的理解,关键问题是 "Why does a g
node break the force layout?",但似乎还有一些关于每个 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; });
}
tick 函数在每个 tick 被调用,它根据其数据更新每个节点。力模拟不操纵任何视觉元素,它操纵节点数据数组。在初始化时,d3 力为数据数组中的每个节点创建新属性,例如表示速度和位置的属性。所以 d3 正在更新你的数据,而不是你的元素。这就是为什么我们需要一个 tick 函数。
以上不是 d3 中的典型更新模式(但它是 d3-force 的规范)。典型的更新模式通常如下所示:
d3.selectAll(".node")
.data(data)
.attr("...")
.attr("...")...
链条可能会因 exit/enter/merge 选择的调解而分裂
然而,对于 objects,这就是数据数组中的节点,d3 不会复制数据以将其绑定到每个元素,d3 实际上将每个元素链接到数据数组中的一个项目。这意味着对于 node.attr("cx", function(d) {
,d
指的是数据数组中的 linked/bound 更新项,不需要 selection.data()
。
我提到这个是因为它是非典型的,不是众所周知的(在我看来),并且没有在示例或教程中解释为什么部队使用(或可以使用)不同的更新模式。此外,鉴于您的评论 "The coords are determined by the sim and are dynamically recalculated constantly. Which is what puzzles me in the whole thing"
,它可能会造成混淆
什么是node
选择node
应该是g
个元素的选择,但实际上是image
个元素的选择:
node = node.enter()
.append('g') // returns a selection of `g` elements
.append('image') // returns a selection of `image` elements.
...
您选择的元素不是 g
,而是 child image
元素。将它们与其他元素合并可能会导致问题。打破链接,保留 node
作为节点的选择,在本例中是 g
元素。然后我们可以更轻松地向每个节点附加任意数量的 children。
(为此,这里有一个 fiddle 的变化,但我们还没有解决为什么还没有任何变化)。
什么打破了 Tick?
正如我在评论中指出的那样,您已将节点从 circle
更改为 g
,并且您注意到您的节点最初已放置,但未更新。这是因为您需要更改滴答功能。您这样更新节点:
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
但是,节点现在是 g
的选择,并且 g
不由 cx
和 cy
属性定位。让我们将其更改为:
node.attr("transform", function(d) { return "translate("+[d.x,d.y]+")"; })
这是更新后的 fiddle,其中的节点随着每次更新而移动。但是,现在定位需要固定。
定位
现在我们更新每个刻度的节点,每个节点的 g
被翻译成 [0,0] 是该节点的中心。我看到的图像是 25 像素正方形,因此要使图像居中,我们需要为每个图像的 x 和 y 位置使用负 12.5 (fiddle)。
我没有使用 d.x 或 d.y 来定位图像,就像在您的第二个 fiddle 中那样,因为通过定位 g
,我可以相对于节点更容易,我只需要为每个节点更新一个元素,即 g
。否则我必须每次更新所有标签、图像等。
上面括号中的 fiddle 最初也不会定位节点 - 你可以这样做但是 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()
returns 需要输入的每个节点的占位符(与用于 g
parent 时相同。但是,这些占位符不包含任何 g
,因此 node.enter().selectAll("g")
将为空,因此文本不会附加到任何元素。
我们希望每个节点都有文本,并且每个节点都在选择中node
,所以我们只用:
node.append("text")....
这是带有您的标签的 updated fiddle。
您可以通过这种方式将任何其他 children 附加到节点,example。
你不需要为 children 使用 .data() 或任何东西,因为 d3 会给每个 child 相同的数据parent.
即使您在第二个 fiddle 中使用了 node.append()
,node
代表了 image
的选择 - 您不能追加文本到图像 - 因此不会显示任何文本。
为了让答案更加独立,这里是最终结果的一个片段:
<!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>
我有一个 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>
但是如果我想添加组(我的最终设计需要背景图像、标签和各种额外的东西),就像这样:
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 的中间并彼此重叠。此外,似乎坐标变换转到图像而不是组,这就是我认为破坏 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>
我很肯定使用组会搞砸一切,但我无法理解如何正确使用它们。
感谢任何帮助。
这是片段形式的完整力量布局:
<!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>
首先,我让第二个 fiddle 工作,链式方法之间有一个额外的 } 和几个逗号:fiddle.
因此,在第一个 fiddle 中,根据我的理解,一切正常:链接和节点按照强制布局的指示移动。在 fiddle 二中,链接继续移动,但是节点,现在带有图像的 g
无论如何都不会移动。
根据我的理解,关键问题是 "Why does a g
node break the force layout?",但似乎还有一些关于每个 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; });
}
tick 函数在每个 tick 被调用,它根据其数据更新每个节点。力模拟不操纵任何视觉元素,它操纵节点数据数组。在初始化时,d3 力为数据数组中的每个节点创建新属性,例如表示速度和位置的属性。所以 d3 正在更新你的数据,而不是你的元素。这就是为什么我们需要一个 tick 函数。
以上不是 d3 中的典型更新模式(但它是 d3-force 的规范)。典型的更新模式通常如下所示:
d3.selectAll(".node")
.data(data)
.attr("...")
.attr("...")...
链条可能会因 exit/enter/merge 选择的调解而分裂
然而,对于 objects,这就是数据数组中的节点,d3 不会复制数据以将其绑定到每个元素,d3 实际上将每个元素链接到数据数组中的一个项目。这意味着对于 node.attr("cx", function(d) {
,d
指的是数据数组中的 linked/bound 更新项,不需要 selection.data()
。
我提到这个是因为它是非典型的,不是众所周知的(在我看来),并且没有在示例或教程中解释为什么部队使用(或可以使用)不同的更新模式。此外,鉴于您的评论 "The coords are determined by the sim and are dynamically recalculated constantly. Which is what puzzles me in the whole thing"
,它可能会造成混淆什么是node
选择node
应该是g
个元素的选择,但实际上是image
个元素的选择:
node = node.enter()
.append('g') // returns a selection of `g` elements
.append('image') // returns a selection of `image` elements.
...
您选择的元素不是 g
,而是 child image
元素。将它们与其他元素合并可能会导致问题。打破链接,保留 node
作为节点的选择,在本例中是 g
元素。然后我们可以更轻松地向每个节点附加任意数量的 children。
(为此,这里有一个 fiddle 的变化,但我们还没有解决为什么还没有任何变化)。
什么打破了 Tick?
正如我在评论中指出的那样,您已将节点从 circle
更改为 g
,并且您注意到您的节点最初已放置,但未更新。这是因为您需要更改滴答功能。您这样更新节点:
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
但是,节点现在是 g
的选择,并且 g
不由 cx
和 cy
属性定位。让我们将其更改为:
node.attr("transform", function(d) { return "translate("+[d.x,d.y]+")"; })
这是更新后的 fiddle,其中的节点随着每次更新而移动。但是,现在定位需要固定。
定位
现在我们更新每个刻度的节点,每个节点的 g
被翻译成 [0,0] 是该节点的中心。我看到的图像是 25 像素正方形,因此要使图像居中,我们需要为每个图像的 x 和 y 位置使用负 12.5 (fiddle)。
我没有使用 d.x 或 d.y 来定位图像,就像在您的第二个 fiddle 中那样,因为通过定位 g
,我可以相对于节点更容易,我只需要为每个节点更新一个元素,即 g
。否则我必须每次更新所有标签、图像等。
上面括号中的 fiddle 最初也不会定位节点 - 你可以这样做但是 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()
returns 需要输入的每个节点的占位符(与用于 g
parent 时相同。但是,这些占位符不包含任何 g
,因此 node.enter().selectAll("g")
将为空,因此文本不会附加到任何元素。
我们希望每个节点都有文本,并且每个节点都在选择中node
,所以我们只用:
node.append("text")....
这是带有您的标签的 updated fiddle。
您可以通过这种方式将任何其他 children 附加到节点,example。
你不需要为 children 使用 .data() 或任何东西,因为 d3 会给每个 child 相同的数据parent.
即使您在第二个 fiddle 中使用了 node.append()
,node
代表了 image
的选择 - 您不能追加文本到图像 - 因此不会显示任何文本。
为了让答案更加独立,这里是最终结果的一个片段:
<!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>