我正在做一个时间网络图,用饼图作为节点。沿着节点/链接随着时间的推移而变化,饼图也应该随之变化。它可以很好地工作,而不需要合并不断变化的饼图切片。当我合并不断变化的切片时,我得到了这种奇怪的行为,每次(时间)滑块移动时,节点/饼都会从它们的初始位置重新开始,这使得整个事情抖动得相当严重。
每个节点的数据如下所示:
{
"id": "Mike",
"start": "2022-08-09",
"location": [12.34, -56.74],
"received": [{
"giver": "Susan",
"receiver": "Mike",
"user_timestamp": "2022-08-09",
"message": "thanks!",
"location": [3.1415, 9.6535]
}, {
"giver": "Joe",
"receiver": "Mike",
"user_timestamp": "2022-08-11",
"message": "so cool!",
"location": [27.18, 2.818]
}]
}
接收到的数组保存与饼相关的所有数据-每个切片大小相同,并且切片的数量与数组中的元素数量相同。我正在根据user_timestamp
和滑块位置过滤接收到的数组,从而更改饼图切片。我遇到的另一个问题是,当添加切片时,饼图切片没有正确更新…
rangeSlider.on("onchange", (val) => {
currentValue = Math.ceil(timeScale(val));
// this filters entire node/pie
const filteredNodes = userNetworkData.nodes.filter(
(d) => new Date(d.start) <= val
);
// filter the received array in each node
const filteredNodesReceived = filteredNodes.map((d) => {
return {
...d,
received: d.received.filter((r) => new Date(r.user_timestamp) <= val),
};
});
const filteredLinks = userNetworkData.links.filter(
(d) => new Date(d.start) <= val
);
// remove edge if either source or target is not present
const filteredLinksFiltered = filteredLinks.filter(
(d) => filteredNodesReceived.some(o => o.id == d.source.id) && filteredNodesReceived.some(o => o.id == d.target.id)
);
// point to new source, target structure
const filteredLinksMapped = filteredLinksFiltered.map((d) => {
return {
...d,
source: filteredNodesReceived.find(x => x.id==d.source.id),
target: filteredNodesReceived.find(x => x.id==d.target.id)
};
});
update(filteredNodesReceived, filteredLinksMapped);
});
我使用userNetworkData将数据保存在某个静态版本中,这样我就可以在删除数据后将其恢复。也许这说不通我已经尝试在滑块更改时更新userNetworkData.nodes
的每个示例的x,y,vx,vy,但发生了相同的抖动。
filteredLinksMapped是我尝试将链接与节点重新关联(现在在接收到的数组中具有不同数量的元素)。update(nodes,links)
相关部分:
function update(nodes, links) {
node = node
.data(nodes, (d) => d.id)
.join(
(enter) => enter.append("g").call(drag(simulation))
);
paths = node
.selectAll("path")
.data(function (d, i) {
return pie(d.received);
})
.join(
(enter)=>
enter.append("svg:path")
.attr("class", "path")
.attr("d", arc)
.attr("opacity", 1)
// for making each pie slice visible
.attr("stroke", function (d) {
return color(d.data.receiver);
})
.attr("stroke-width", radius * 0.2)
.attr("fill", function (d, i) {
return color(d.data.giver);
})
.attr("cursor", "pointer")
.on("mousemove", function (event, d) {
//tooltips bit
div.transition().duration(200).style("opacity", 0.9);
// this bit puts the tooltip near the slice of the pie chart
div
.html(parse_message(d.data))
.style("left", event.pageX + 20 + "px")
.style("top", event.pageY - 28 + "px");
})
.on("mouseout", function (d) {
div.transition().duration(500).style("opacity", 0);
})
)
link = link
.data(links, (d) => [d.source, d.target])
.join("line")
.attr("stroke-width", radius * 0.3)
.attr("stroke", (d) => color(d.source.id));
simulation.nodes(nodes);
simulation.force("link").links(links,function(d){return d.id;});
simulation.alpha(0.5).tick();
simulation.restart();
ticked();
}
我正在更新之外初始化我的选择和模拟,如下所示:
const simulation = d3.forceSimulation()
.force("charge", d3.forceManyBody())
.force(
"link",
d3.forceLink().id((d) => d.id)
)
// .force("collide", d3.forceCollide().radius(2*radius ).iterations(3))
.force(
"y",
d3.forceY((d) => projection(d.location)[1])
)
.force(
"x",
d3.forceX((d) => projection(d.location)[0])
)
.on("tick", ticked);
let link = svg.append("g").attr("class", "links").selectAll("line");
let node = svg.append("g").attr("class", "nodes").selectAll("g");
请注意,当我在“Map”视图和网络视图之间转换时,我将节点强制移向与其纬度/经度对应的坐标。
不幸的是,我在codepen上遇到了麻烦,我会继续尝试,但希望这就足够了。
1条答案
按热度按时间yc0p9oo01#
问题在于我将
x,y,vx,vy
值从节点选择复制到原始数据(我将使用它来过滤/添加到我的选择)的方式。这是我决定的。第一行只是获取我想要更新的对象的子集。请参阅How to get a subset of a javascript object's properties了解它的工作原理。
最后一行仅替换userNetworkData.nodes中每个示例的值x、y、vx、vy,当它是当前DOM中节点的一部分时。
这是受https://observablehq.com/@d3/temporal-force-directed-graph的启发,但它们的情况(从www.example.com()复制所有数据)之间的区别node.data是,我不能复制接收到的数组,因为时间过滤器正在改变它,我需要保存它的完整副本。