D3.js中节点上的局部力 [英] Partial forces on nodes in D3.js

查看:231
本文介绍了D3.js中节点上的局部力的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



为了更好的说明,我把这个JSON作为节点的数据:

  [{
word:expression,
theme:Thème6 ,
radius:3
},{
word:théorie,
theme:Thème4 :27
},{
word:relativité,
theme:Thème5,
radius:27
}
word:renvoie,
theme:Thème3,
radius:19
},
.... $ b]

我想要的是将一些力专门应用于具有Thème1作为主题属性或其他力量为Thème2值,等等...



我一直在源代码中查看,以检查我们是否可以分配



我得出结论,我必须实现几个辅助 d3的模型节点的子部分,但我没有找到它。 simulation()并且仅应用它们各自的节点子部分以便处理我前面提到的力。
这是我在d3伪代码中所做的:

  mainSimulation = d3.forceSimulation b .nodes(allNodes)
.force(force1,aD3Force())
.force(force2,anotherD3Force())
cluster1Simulation = d3.forceSimulation()
.nodes(allNodes.filter(d => d.theme ===Thème1))
.force(subForce1,forceX(....))
cluster2Simulation = d3 .forceSimulation()
.nodes(allNodes.filter(d => d.theme ===Thème2))
.force(subForce2,forceY

但我认为这不是最优的考虑计算。



有可能对模拟节点的子部分施加力,而不必创建其他模拟?



实现altocumulus的第二个解决方案:



我试过这个解决方案:

  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 => theme === centroid.label));
};
forceX.initialize = newInit;
forceY.initialize = newInit;

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

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

  09:51:55,996 TypeError:nodes。长度未定义
- force()d3.v4.js:10819
- tick /<()d3.v4.js:10559
- map $ 1.prototype.each .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(...)返回一个填充数组,所以这不是问题的起源。

解决方案

要仅对节点的子集应用一个力,你基本上有以下选项:


  1. 实现你自己的力,这不是它可能听起来那么困难,因为


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



– force–


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


    将节点数组分配给此力。


    通过过滤节点并仅分配您感兴趣的节点,您可以控制力应该对哪些节点执行操作: p>

      //仅应用于每个第二个节点的自定义实现
    var pickyForce = d3.forceY(height);

    //保存默认的初始化方法
    var init = pickyForce.initialize;

    //自定义执行.initialize()只使用
    调用保存的方法//节点的子集
    pickyForce.initialize = function(nodes){
    //过滤节点的子集并委托保存的初始化。
    init(nodes.filter(function(n,i){return i%2;})); // Apply to each 2nd node
    }


以下代码段演示了通过使用节点子集初始化 d3.forceY 的第二种方法。



 pre> 

 < script src =https://d3js.org/d3.v4.js> ;< / script>  


I want to apply several forces (forceX and forceY) respectively to several subparts of nodes.

To be more explanatory, I have this JSON as data for my nodes:

[{
    "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
},
....
]

What I want is to apply some forces exclusively to the nodes that have "Thème 1" as a theme attribute, or other forces for the "Thème 2" value, etc ...

I have been looking in the source code to check if we can assign a subpart of the simulation's nodes to a force, but I haven't found it.

I concluded that I would have to implement several secondary d3.simulation() and only apply their respective subpart of nodes in order to handle the forces I mentioned earlier. Here's what I thought to do in d3 pseudo-code :

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( .... ) )

But I think it's not optimal at all considering the computation.

Is it possible to apply a force on a subpart of the simulation's nodes without having to create other simulations ?

Implementing altocumulus' second solution :

I tried this solution like this :

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);      
});

My centroids array may change, that's why I had to implement a dynamic way to implement my sub-forces. Though, i end up having this error through the simulation ticks :

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

I concluded that the filtered array is not assigned to nodes, and I can't figure why. PS : I checked with a console.log : nodes.filter(...) does return an filled array, so this is not the problem's origin.

解决方案

To apply a force to only subset of nodes you basically have to options:

  1. Implement your own force, which is not as difficult as it may sound, because

    A force is simply a function that modifies nodes’ positions or velocities;

–or, if you want to stick to the standard forces–

  1. Create a standard force and overwrite its force.initialize() method, which will

    Assigns the array of nodes to this force.

    By filtering the nodes and assigning only those you are interested in, you can control on which nodes the force should act upon:

    // 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
    }
    

The following snippet demonstrates the second approach by initializing a d3.forceY with a subset of nodes. From the entire set of randomly distributed circles only every second one will have the force applied and will thereby be moved to the bottom.

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; })
}

<script src="https://d3js.org/d3.v4.js"></script>

这篇关于D3.js中节点上的局部力的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆