【发布时间】:2020-10-24 21:13:30
【问题描述】:
我尝试创建一个散点图,它使用力模拟在我的数据点周围放置标签。到目前为止,这工作正常(感谢 stackoverflow 和一些博客的良好帮助:))。这是到目前为止的样子...... Scatter-Plot so far
但是,我现在被困在尝试重新排序我的元素,以便每个数据点的圆、线和文本元素在 z 轴上彼此相邻。
我现在的意思:
<g class="circles">
<circle dp-1></circle>
<circle dp-2></circle>
...
</g>
<g class="labels">
<text dp-1></text>
<text dp-2></text>
...
</g>
<g class="links">
<line dp-1></line>
<line dp-2></line>
...
</g>
我想去……
<g id="dp-1">
<circle dp-1></circle>
<text dp-1></text>
<line dp-1></line>
</g>
<g id="dp-2">
<circle dp-2></circle>
<text dp-2></text>
<line dp-2></line>
</g>
<g>
...
我知道在“静态”情况下如何在不使用力模拟的情况下做到这一点。但是,我不知道如何在我的情况下执行此操作,我在标签(节点)和线(链接)上运行力模拟,而不是在圆圈上。
如何在 D3 中正确实现这一点?以下是我的代码中最重要的 sn-ps。 我卡住的要点是我为我的圈子(数据)和节点(forceData)使用了不同的数据数组。后者基本上是一个两倍于数据长度的数组(每个数据点 2 个节点)。
我也不知道怎么做
- 让 D3 绘制一个绑定到两个不同数据的“g”,或者
- 根据数据数组进行力模拟(现在只有节点数组长度的一半)
当然也欢迎解决我的问题的其他想法。 感谢您的任何想法和帮助。
/**
* Updates the chart. To be used when the data stayed the same, but is sliced differently (filter, ...)
*/
public update() {
this.svg.select('.dataPoints')
.selectAll("circle")
.data(this.data,
function (d: any) { return d.category }
)
.join(
function (enter) {
// what is to be done with new items ...
return enter
.append("circle")
.style("opacity", 0)
},
// function (update) { return update },
)
.attr("cx", d => this.xScale()(d.x))
.attr("cy", d => this.yScale()(d.y))
.style("fill", d => this.color(d.color))
.style("stroke-width", this.settings.dataPoints.stroke.width)
.style("stroke-opacity", this.settings.dataPoints.stroke.opacity)
.style("stroke", this.settings.dataPoints.stroke.color)
.transition()
.duration(this.settings.dataPoints.duration)
.style('opacity', 1)
.attr("r", d => this.rScale()(d.r))
if (this.settings.labels.show) {
this.svg.select(".labels")
.call(this.labelPlacement)
}
private labelPlacement = (g) => {
// we need to create our node and link array. We need two nodes per datapoint. One for the point
// itself which has a fixed x and y (fx/fy) and one for the label, which will be floating ...
var forceData = {
'nodes': [],
'links': [],
};
var myXscale = this.xScale()
var myYscale = this.yScale()
this.data.forEach(function (d, i) {
// doing the two nodes per datapoint ...
forceData.nodes.push({
id: d.category,
label: d.label,
fx: myXscale(d.x),
fy: myYscale(d.y)
});
forceData.nodes.push({
id: d.category,
label: d.label,
x: myXscale(d.x),
y: myYscale(d.y),
dataX: myXscale(d.x),
dataY: myYscale(d.y)
});
// and also adding a link between the datapoint and its label ...
forceData.links.push({
source: i * 2,
target: i * 2 + 1,
});
});
// now drawing them labels and links ...
if (this.settings.labels.showLinks) {
var labelLink = this.svg.select('.label-links')
.selectAll("line")
.data(forceData.links, (d: any) => { return (d.source + "-" + d.target) })
.join("line")
.attr("stroke", this.settings.labels.linkStroke.color)
.attr("stroke-width", this.settings.labels.linkStroke.width)
.attr("opacity", this.settings.labels.linkStroke.opacity)
}
var labelNode = this.svg.select('.labels')
.selectAll("text")
.data(forceData.nodes, (d: any) => { return d.id })
.join("text")
.text((d, i) => { return i % 2 == 0 ? "" : TextService.textLimit(d.label, this.settings.labels.maxTextLength) })
.style("fill", this.settings.labels.label.fill)
.style("font-family", this.settings.labels.label.fontFamily)
.style("font-size", this.settings.labels.label.fontSize)
.call(d3.drag()
.on("drag", dragged)
.on("end", dragended)
)
// adding and doing the force simulation ...
if (this.settings.labels.force) {
d3.forceSimulation(forceData.nodes)
.alphaTarget(this.settings.labels.alphaTarget)
.alphaDecay(this.settings.labels.alphaDecay)
.force("charge", d3.forceManyBody().strength(this.settings.labels.chargeStrength))
.force("link", d3.forceLink(forceData.links)
.distance(this.settings.labels.linkDistance)
.strength(this.settings.labels.linkStrength))
.on("tick", ticked);
}
【问题讨论】:
-
您能否发布足够的代码以便我们测试您的演示等?理想情况下,将您的问题作为可重复的最小示例 (stackoverflow.com/help/minimal-reproducible-example) - 您可以在此处设置“sn-p”或添加指向codepen.io 的链接等。由于 D3 是“数据驱动文档”,我认为我们在最少需要查看您的数据(或其简化版本)。我的第一个想法是使用 map/reduce 等数组方法合并/转换数据。
-
嗨,亚历克斯,感谢您的回复。我担心,可能有点难以理解我的要求。幸运的是,我找到了一种通过使用稍微不同的方法来解决问题的方法。我将其作为单独的答案发布。
-
不用担心。如果您想看看我将如何解决它,我现在也在下面发布了我的答案。在我的回答中,我只使用
.append()- 我实现它的方式是通过数据合并/分组,然后以相当常见的方式使用 d3,将我们的分组数据绑定到我们创建的每个“g”元素,然后附加到每个我们想要的形状。总有很多方法可以解决问题 :) 很高兴你找到了一个。
标签: d3.js force-layout