【问题标题】:Partial forces on nodes in D3.jsD3.js 中节点上的部分力
【发布时间】:2016-09-19 14:12:33
【问题描述】:

我想分别对节点的几个子部分应用几个力(forceX 和 forceY)。

为了更便于说明,我将此 JSON 作为节点的数据:

[{
    "word": "expression",
    "theme": "Thème 6",
    "radius": 3
}, {
    "word": "théorie",
    "theme": "Thème 4",
    "radius": 27
}, {
    "word": "relativité",
    "theme": "Thème 5",
    "radius": 27
}, {
    "word": "renvoie",
    "theme": "Thème 3",
    "radius": 19
},
....
]

我想要的是将一些力专门应用于具有“Thème 1”作为主题属性的节点,或其他力用于“Thème 2”值等......

我一直在查看源代码以检查是否可以将模拟节点的子部分分配给力,但我没有找到。

我得出的结论是,我必须实现几个辅助d3.simulation(),并且只应用它们各自的节点子部分来处理我之前提到的力。 这是我想在 d3 伪代码中做的事情:

mainSimulation = d3.forceSimulation()
                        .nodes(allNodes)
                        .force("force1", aD3Force() )
                        .force("force2", anotherD3Force() )
cluster1Simulation = d3.forceSimulation()
                        .nodes(allNodes.filter( d => d.theme === "Thème 1"))
                        .force("subForce1", forceX( .... ) )
cluster2Simulation = d3.forceSimulation()
                        .nodes(allNodes.filter( d => d.theme === "Thème 2"))
                        .force("subForce2", forceY( .... ) )

但考虑到计算,我认为这根本不是最优的。

是否可以在模拟节点的子部分上施加力而无需创建其他模拟?

实施高积云的第二种解决方案:

我尝试过这样的解决方案:

var forceInit;
Emi.nodes.centroids.forEach( (centroid,i) => {

    let forceX = d3.forceX(centroid.fx);
    let forceY = d3.forceY(centroid.fy);
    if (!forceInit) forceInit = forceX.initialize;  
    let newInit = nodes => { 
        forceInit(nodes.filter(n => n.theme === centroid.label));
    };
    forceX.initialize = newInit;
    forceY.initialize = newInit;

    Emi.simulation.force("X" + i, forceX);
    Emi.simulation.force("Y" + i, forceY);      
});

我的质心数组可能会改变,这就是为什么我必须实现一种动态方式来实现我的子力。 虽然,我最终通过模拟滴答声出现了这个错误:

09:51:55,996 TypeError: nodes.length is undefined
- force() d3.v4.js:10819
- tick/<() d3.v4.js:10559
- map$1.prototype.each() d3.v4.js:483
- tick() d3.v4.js:10558
- step() d3.v4.js:10545
- timerFlush() d3.v4.js:4991
- wake() d3.v4.js:5001

我得出的结论是,过滤后的数组没有分配给 节点,我不知道为什么。 PS:我检查了 console.log:nodes.filter(...) 确实返回了一个填充数组,所以这不是问题的根源。

【问题讨论】:

  • 请提供相关源代码供审核。
  • 对不起,我虽然有文字解释就足够了^^我用一些伪代码编辑了主要帖子!
  • @elTaan 我很高兴,可能回答有助于解决您的问题。但是,您不应该将自己的解决方案编辑到您的问题中,因为它只是一个问题。这只会让未来的读者感到困惑。如果您自己找到了解决方案,您可能会改为self-answer 您的问题。你应该考虑accepting任何你认为最适合你的问题的答案;这很可能是您自己的答案。
  • 好的,谢谢你的提示!我接受了你的回答,因为你的 sn-p 代码正在工作,尽管我的不是:D

标签: javascript d3.js


【解决方案1】:

要仅对部分节点施加力,您基本上必须选择:

  1. 发挥自己的力量,这并不像听起来那么困难,因为

    力只是一个修改节点位置或速度的函数;

——或者,如果你想坚持标准部队——

  1. 创建一个标准力并覆盖其force.initialize() 方法,这将

    将节点数组分配给这个力。

    通过过滤节点并仅分配您感兴趣的节点,您可以控制力应作用于哪些节点:

    // Custom implementation of a force applied to only every second node
    var pickyForce = d3.forceY(height);
    
    // Save the default initialization method
    var init = pickyForce.initialize; 
    
    // Custom implementation of .initialize() calling the saved method with only
    // a subset of nodes
    pickyForce.initialize = function(nodes) {
        // Filter subset of nodes and delegate to saved initialization.
        init(nodes.filter(function(n,i) { return i%2; }));  // Apply to every 2nd node
    }
    

以下 sn-p 通过使用节点子集初始化 d3.forceY 来演示第二种方法。在整组随机分布的圆中,只有每秒一个会施加力,从而移动到底部。

var width = 600;
var height = 500;
var nodes = d3.range(500).map(function() {
  return {
    "x": Math.random() * width,
    "y": Math.random() * height 
  };
});

var circle = d3.select("body")
  .append("svg")
    .attr("width", width)
    .attr("height", height)
  .selectAll("circle")
  .data(nodes)
  .enter().append("circle")
    .attr("r", 3)
    .attr("fill", "black");

// Custom implementation of a force applied to only every second node
var pickyForce = d3.forceY(height).strength(.025);

// Save the default initialization method
var init = pickyForce.initialize; 

// Custom implementation of initialize call the save method with only a subset of nodes
pickyForce.initialize = function(nodes) {
    init(nodes.filter(function(n,i) { return i%2; }));  // Apply to every 2nd node
}

var simulation = d3.forceSimulation()
		.nodes(nodes)
    .force("pickyCenter", pickyForce)
    .on("tick", tick);
    
function tick() {
  circle
    .attr("cx", function(d) { return d.x; })
    .attr("cy", function(d) { return d.y; })
}
&lt;script src="https://d3js.org/d3.v4.js"&gt;&lt;/script&gt;

【讨论】:

  • 太棒了!我必须个性化您的第二个解决方案,尽管它会出现错误。我编辑了帖子来解释它!
  • 重写初始化方法是个好主意。谢谢!
  • 最近使用隔离力的 mbostock 示例(覆盖 initialize 方法):bl.ocks.org/mbostock/b1f0ee970299756bc12d60aedf53c13b
【解决方案2】:

我修复了我自己的高积云第二个解决方案的实现:

就我而言,我必须为每个质心创建两个力。 似乎我们不能为所有 forceX 和 forceY 函数共享相同的初始化程序。

我必须为循环上的每个质心创建局部变量:

Emi.nodes.centroids.forEach((centroid, i) => {

    let forceX = d3.forceX(centroid.fx);
    let forceY = d3.forceY(centroid.fy);
    let forceXInit = forceX.initialize;
    let forceYInit = forceY.initialize;
    forceX.initialize = nodes => {
        forceXInit(nodes.filter(n => n.theme === centroid.label))
    };
    forceY.initialize = nodes => {
        forceYInit(nodes.filter(n => n.theme === centroid.label))
    };

    Emi.simulation.force("X" + i, forceX);
    Emi.simulation.force("Y" + i, forceY);
});

【讨论】:

    猜你喜欢
    • 2023-03-20
    • 2020-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-11
    • 1970-01-01
    • 1970-01-01
    • 2021-10-03
    相关资源
    最近更新 更多