D3.js 节点上的偏力

elT*_*aan 2 javascript d3.js

我想分别对节点的几个子部分施加几个力(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
},
....
]
Run Code Online (Sandbox Code Playgroud)

我想要的是将一些力专门应用于具有“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( .... ) )
Run Code Online (Sandbox Code Playgroud)

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

是否可以在无需创建其他模拟的情况下对模拟节点的子部分施加力?

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

我试过这样的解决方案:

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);      
});
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

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

alt*_*lus 5

要仅对节点的子集施加力,您基本上必须选择:

  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
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

// 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
}
Run Code Online (Sandbox Code Playgroud)
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; })
}
Run Code Online (Sandbox Code Playgroud)

  • 最近带有隔离力的 mbostock 示例(覆盖 `initialize` 方法):https://bl.ocks.org/mbostock/b1f0ee970299756bc12d60aedf53c13b (2认同)