我试图创建一个散点图,使用强制模拟将标签放置在我的数据点周围。到目前为止,这还不错(感谢对堆栈溢出和一些博客的良好帮助:)。这是到目前为止的样子. 散点图
然而,我现在不得不重新排序我的元素,以便每个数据点的圆、线和文本元素在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中正确地实现这一点?下面是我代码中最重要的片段。我遇到的主要问题是,对于我的圆圈(数据)和节点(forceData),我使用不同的数据数组。后者基本上是一个数组,长度是数据的两倍(每个数据点有两个节点)。
我也不知道该怎么做
当然,解决我的问题的其他想法也是受欢迎的。谢谢你的想法和帮助。
/**
* 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);
}发布于 2020-07-05 09:39:53
谢谢大家的阅读。我又过了一个不眠之夜,终于解决了这个问题:)
我跳过了"g“元素,因为它们并不是真正必要的,而是试图获得这样的结构:
<line dp-1></line>
<circle dp-1></circle>
<line dp-2></line>
<circle dp-2></line>
...为此,我可以使用两个单独的数据绑定和数据数组(这是使用力模拟所必需的)。实现校正排序(行-圆-线-圆-.)我使用了"d3.insert“,而不是用动态计算的”前面“元素来追加。下面是代码中最重要的部分。希望这最终能对某人有所帮助。
问候
// Drawing the data ...
public update() {
this.dataPoints
.selectAll("circle")
.data(this.data,
function (d: any) { return d.category }
)
.join(
enter => enter.append("circle")
.style('opacity', 0)
)
.call(this.drawData)
if (this.settings.labels.showLinks && this.settings.labels.showLinks) {
this.dataPoints
.selectAll("line")
.data(this.forceData.links)
.join(
enter => enter.insert('line', (d, i) => {
console.log("JOIN", d)
return document.getElementById('dP_' + d.id)
})
// .style('opacity', 0)
)
.call(this.drawLabelLine)
}
}
/**
* Draws the data circles ...
* @param circle
*/
private drawData = (circle) => {
circle
.attr("id", (d, i) => { return 'dP_' + d.category })
.attr("class", "dataPoint")
.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))
.attr("cx", d => this.xScale()(d.x))
.attr("cy", d => this.yScale()(d.y))
}
/**
* draws the lines to connect labels to data points
* @param g
*/
private drawLabelLine = (line) => {
line
.attr("class", "label-link")
.attr("stroke", this.settings.labels.linkStroke.color)
.attr("stroke-width", this.settings.labels.linkStroke.width)
.attr("opacity", this.settings.labels.linkStroke.opacity)
}
// 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);
}发布于 2020-07-05 11:14:19
由于您没有包含您的数据,所以我可以在数据方面给出一种高层次的解决方法:
本质上,将这3个数组合并到一个对象中,按照“color”属性(可以是任何属性)进行归约。然后将每个圆圈、线条和文本附加到我们为每种颜色创建的每一个'g‘元素中。
注意:links数组没有任何意义,因为我们可以从circles和labels数组中获得它们,因此有x1、x2、y1和y2值。此外,如果可能的话,您可以从一开始就像我的combinedData一样定义数据。
const circles = [
{shape: "circle", color: "green", x: 2, y: 2, r: 0.5},
{shape: "circle", color: "blue", x: 4, y: 4, r: 1},
{shape: "circle", color: "red", x: 8, y: 8, r: 1.5},
];
const links = [
{shape: "line", color: "green", x1: 2, y1: 2, x2: 1, y2: 1},
{shape: "line", color: "blue", x1: 4, y1: 4, x2: 2, y2: 6},
{shape: "line", color: "red", x1: 8, y1: 8, x2: 9, y2: 4},
];
const labels = [
{shape: "text", color: "green", x: 1, y: 1, text: "A"},
{shape: "text", color: "blue", x: 2, y: 6, text: "B"},
{shape: "text", color: "red", x: 9, y: 4, text: "C"},
];
const combinedData = [...circles, ...links, ...labels].reduce((aggObj, item) => {
if (!aggObj[item.color]) aggObj[item.color] = {};
aggObj[item.color][item.shape] = item;
return aggObj;
}, {});
//console.log(combinedData);
const groups = d3.select('svg').selectAll('g')
.data(Object.entries(combinedData))
.enter()
.append('g')
.attr('class', ([k,v]) => k);
groups
.append('circle')
.attr('fill', ([k,v]) => v.circle.color)
.attr('r', ([k,v]) => v.circle.r)
.attr('cx', ([k,v]) => v.circle.x)
.attr('cy', ([k,v]) => v.circle.y)
groups
.append('line')
.attr('stroke', ([k,v]) => v.line.color)
.attr('stroke-width', 0.1)
.attr('x1', ([k,v]) => v.line.x1)
.attr('y1', ([k,v]) => v.line.y1)
.attr('x2', ([k,v]) => v.line.x2)
.attr('y2', ([k,v]) => v.line.y2)
groups
.append('rect')
.attr('fill', "#cfcfcf")
.attr('x', ([k,v]) => v.text.x - 0.6)
.attr('y', ([k,v]) => v.text.y - 0.6)
.attr('width', 1.1)
.attr('height', 1.1)
groups
.append('text')
.attr('alignment-baseline', "middle")
.attr('text-anchor', "middle")
.attr('fill', ([k,v]) => v.text.color)
.attr('font-size', 1)
.attr('x', ([k,v]) => v.text.x)
.attr('y', ([k,v]) => v.text.y)
.text(([k,v]) => v.text.text)<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="100%" viewbox="0 0 12 12">
</svg>
分组要素:

扩大:
<svg width="100%" viewBox="0 0 12 12">
<g class="green">
<circle fill="green" r="0.5" cx="2" cy="2"></circle>
<line stroke="green" stroke-width="0.1" x1="2" y1="2" x2="1" y2="1"></line>
<rect fill="#cfcfcf" x="0.4" y="0.4" width="1.1" height="1.1"></rect>
<text alignment-baseline="middle" text-anchor="middle" fill="green" font-size="1" x="1" y="1">A</text>
</g>
<g class="blue">
<circle fill="blue" r="1" cx="4" cy="4"></circle>
<line stroke="blue" stroke-width="0.1" x1="4" y1="4" x2="2" y2="6"></line>
<rect fill="#cfcfcf" x="1.4" y="5.4" width="1.1" height="1.1"></rect>
<text alignment-baseline="middle" text-anchor="middle" fill="blue" font-size="1" x="2" y="6">B</text>
</g>
<g class="red">
<circle fill="red" r="1.5" cx="8" cy="8"></circle>
<line stroke="red" stroke-width="0.1" x1="8" y1="8" x2="9" y2="4"></line>
<rect fill="#cfcfcf" x="8.4" y="3.4" width="1.1" height="1.1"></rect>
<text alignment-baseline="middle" text-anchor="middle" fill="red" font-size="1" x="9" y="4">C</text>
</g>
</svg>产出(粗略例子):

https://stackoverflow.com/questions/62727373
复制相似问题