D3.js中的临时网络,节点(不正确)重新启动位置导致抖动

k5hmc34c  于 2023-06-06  发布在  其他
关注(0)|答案(1)|浏览(138)

我正在做一个时间网络图,用饼图作为节点。沿着节点/链接随着时间的推移而变化,饼图也应该随之变化。它可以很好地工作,而不需要合并不断变化的饼图切片。当我合并不断变化的切片时,我得到了这种奇怪的行为,每次(时间)滑块移动时,节点/饼都会从它们的初始位置重新开始,这使得整个事情抖动得相当严重。
每个节点的数据如下所示:

{
"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上遇到了麻烦,我会继续尝试,但希望这就足够了。

yc0p9oo0

yc0p9oo01#

问题在于我将x,y,vx,vy值从节点选择复制到原始数据(我将使用它来过滤/添加到我的选择)的方式。这是我决定的。

const node_pos_vel = node.data().map(d=> (({ x,y,vx,vy,id }) => ({ x,y,vx,vy,id}))(d))
const node_pos_vel_map = new Map(node_pos_vel.map(d => [d.id, d]));
userNetworkData.nodes = userNetworkData.nodes.map(d => Object.assign(d, node_pos_vel_map.get(d.id)));

第一行只是获取我想要更新的对象的子集。请参阅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是,我不能复制接收到的数组,因为时间过滤器正在改变它,我需要保存它的完整副本。

相关问题