如何将一个节点从一个模拟中移动到另一个模拟中

huangapple go评论80阅读模式
英文:

How to move a node from one simulation to another

问题

我试图将节点从一个forceSimulation移动到另一个,我希望节点会离开一个群集并漂移到另一个群集。然而,一旦模拟稳定成一个稳定的模式,力就停止作用了。

以下是代码:

const width = 600;
const height = 200;

var nodes;
var simulations;

function loaded() {
  d3.select("svg")
    .attr("width", width)
    .attr("height", height)
  test();
}

function test() {

  nodes = [];
  nodes[0] = d3.range(50).map(function() {
    return {
      radius: 4,
      color: "blue"
    };
  })

  nodes[1] = d3.range(50).map(function() {
    return {
      radius: 4,
      color: "red"
    };
  })

  simulations = [null, null];

  update(0);
  update(1);

  setTimeout(startMigration, 1000);

}

function update(index) {

  simulations[index] = d3.forceSimulation(nodes[index])
    .force('charge', d3.forceManyBody().strength(10))
    .force('x', d3.forceX().x(function(d) {
      return width * (index + 1) / 3;
    }))
    .force('y', d3.forceY().y(function(d) {
      return height / 2;
    }))
    .force('collision', d3.forceCollide().radius(function(d) {
      return d.radius;
    }))
    .on('tick', ticked);

  function ticked() {
    var u = d3.select('svg')
      .selectAll('.circle' + index)
      .data(nodes[index])
      .join('circle')
      .attr('class', 'circle' + index)
      .attr('r', function(d) {
        return d.radius;
      })
      .style('fill', function(d) {
        return d.color;
      })
      .attr('cx', function(d) {
        return d.x;
      })
      .attr('cy', function(d) {
        return d.y;
      });
  }

}

function startMigration() {
  setInterval(function() {
    if (nodes[1].length > 10) {
      nodes[0].push(nodes[1].pop());

      d3.select('svg')
        .selectAll('.circle0')
        .data(nodes[0]);
      simulations[0].nodes(nodes[0]);

      d3.select('svg')
        .selectAll('.circle1')
        .data(nodes[1]);
      simulations[1].nodes(nodes[1]);

    }
  }, 250);
}

nodes是两个数据集的数组,simulations是两个模拟的数组。

以下是我创建forceSimulation的地方:

function update(index) {
	
	simulations[index] = d3.forceSimulation(nodes[index])
		.force('charge', d3.forceManyBody().strength(10))
		.force('x', d3.forceX().x(function(d) {
			return width * (index + 1) / 3;
		}))
		.force('y', d3.forceY().y(function(d) {
			return height/2;
		}))
		.force('collision', d3.forceCollide().radius(function(d) {
			return d.radius;
		}))
		.on('tick', ticked);

	function ticked() {
		var u = d3.select('svg')
			.selectAll('.circle' + index)
			.data(nodes[index])
			.join('circle')
			.attr('class', 'circle' + index)
			.attr('r', function(d) {
				return d.radius;
			})
			.style('fill', function(d) {
				//return colorScale(d.category);
				return d.color;
			})
			.attr('cx', function(d) {
				return d.x;
			})
			.attr('cy', function(d) {
				return d.y;
			});
	}

}

以下是定期在模拟开始后1000ms调用的代码,用于将节点从一个模拟移动到另一个模拟:

nodes[0].push(nodes[1].pop());
			
d3.select('svg')
   .selectAll('.circle0')
   .data(nodes[0]);
simulations[0].nodes(nodes[0]);
			
d3.select('svg')
   .selectAll('.circle1')
   .data(nodes[1]);
simulations[1].nodes(nodes[1]);

我希望的效果是节点会离开红色群集并漂移到蓝色群集。为什么它们会减速并停止?问题会随着初始加载后的等待时间变得更糟。我感觉有关forceSimulation的一些基本内容我没有理解。我本以为它们会尽可能接近它们所属模拟的(forceXforceY)。此外,代码段中的所有步骤是否都是必要的?我是否需要重新分配数据给两个选择,以及重新分配节点给两个模拟?

英文:

I'm trying to move nodes from one forceSimulation to another, and I'm expecting the nodes to leave one cluster and drift over to another. However, it seems that once the simulation has settled into a stable pattern the forces stop being applied.

Here's the codepen:
Migrating Nodes

And a snippet:

<!-- begin snippet: js hide: true console: true babel: false -->

<!-- language: lang-js -->

const width = 600;
const height = 200;
var nodes;
var simulations;
function loaded() {
d3.select(&quot;svg&quot;)
.attr(&quot;width&quot;, width)
.attr(&quot;height&quot;, height)
test();
}
function test() {
nodes = [];
nodes[0] = d3.range(50).map(function() {
return {
radius: 4,
color: &quot;blue&quot;
};
})
nodes[1] = d3.range(50).map(function() {
return {
radius: 4,
color: &quot;red&quot;
};
})
simulations = [null, null];
update(0);
update(1);
setTimeout(startMigration, 1000);
}
function update(index) {
simulations[index] = d3.forceSimulation(nodes[index])
.force(&#39;charge&#39;, d3.forceManyBody().strength(10))
.force(&#39;x&#39;, d3.forceX().x(function(d) {
return width * (index + 1) / 3;
}))
.force(&#39;y&#39;, d3.forceY().y(function(d) {
return height / 2;
}))
.force(&#39;collision&#39;, d3.forceCollide().radius(function(d) {
return d.radius;
}))
.on(&#39;tick&#39;, ticked);
function ticked() {
var u = d3.select(&#39;svg&#39;)
.selectAll(&#39;.circle&#39; + index)
.data(nodes[index])
.join(&#39;circle&#39;)
.attr(&#39;class&#39;, &#39;circle&#39; + index)
.attr(&#39;r&#39;, function(d) {
return d.radius;
})
.style(&#39;fill&#39;, function(d) {
//return colorScale(d.category);
return d.color;
})
.attr(&#39;cx&#39;, function(d) {
return d.x;
})
.attr(&#39;cy&#39;, function(d) {
return d.y;
});
}
}
function startMigration() {
setInterval(function() {
if (nodes[1].length &gt; 10) {
nodes[0].push(nodes[1].pop());
d3.select(&#39;svg&#39;)
.selectAll(&#39;.circle0&#39;)
.data(nodes[0]);
simulations[0].nodes(nodes[0]);
d3.select(&#39;svg&#39;)
.selectAll(&#39;.circle1&#39;)
.data(nodes[1]);
simulations[1].nodes(nodes[1]);
}
}, 250);
}

<!-- language: lang-html -->

&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot; /&gt;
&lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;ie=edge&quot; /&gt;
&lt;script src=&quot;https://d3js.org/d3.v5.min.js&quot;&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body onload=&quot;loaded()&quot;&gt;
&lt;div id=&#39;layout&#39;&gt;
&lt;div id=&#39;container&#39;&gt;
&lt;svg id=&quot;graph&quot; /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;

<!-- end snippet -->

nodes is an array of two datasets, and simulations is an array of two simulations.

Here is where I create the forceSimulations:

function update(index) {
simulations[index] = d3.forceSimulation(nodes[index])
.force(&#39;charge&#39;, d3.forceManyBody().strength(10))
.force(&#39;x&#39;, d3.forceX().x(function(d) {
return width * (index + 1) / 3;
}))
.force(&#39;y&#39;, d3.forceY().y(function(d) {
return height/2;
}))
.force(&#39;collision&#39;, d3.forceCollide().radius(function(d) {
return d.radius;
}))
.on(&#39;tick&#39;, ticked);
function ticked() {
var u = d3.select(&#39;svg&#39;)
.selectAll(&#39;.circle&#39; + index)
.data(nodes[index])
.join(&#39;circle&#39;)
.attr(&#39;class&#39;, &#39;circle&#39; + index)
.attr(&#39;r&#39;, function(d) {
return d.radius;
})
.style(&#39;fill&#39;, function(d) {
//return colorScale(d.category);
return d.color;
})
.attr(&#39;cx&#39;, function(d) {
return d.x;
})
.attr(&#39;cy&#39;, function(d) {
return d.y;
});
}
}

A this is the code that starts getting called periodically 1000ms after the simulation starts to move nodes from one simulation to the other:

nodes[0].push(nodes[1].pop());
d3.select(&#39;svg&#39;)
.selectAll(&#39;.circle0&#39;)
.data(nodes[0]);
simulations[0].nodes(nodes[0]);
d3.select(&#39;svg&#39;)
.selectAll(&#39;.circle1&#39;)
.data(nodes[1]);
simulations[1].nodes(nodes[1]);

The effect I was hoping for is that nodes would leave the red cluster and drift over to join the blue cluster. Why do they slow down and stop? The problem gets worse the longer I wait after the initial load. I get the feeling there's something fundamental about forceSimulation I'm not understanding. I would have thought that they would get as close as they could to the (forceX, forceY) of the simulation to which they belong.

A secondary question is whether all the steps in the code snippet above are necessary. Do I need to reassign data to both selections, and reassign nodes to both simulations?

答案1

得分: 2

许多力模拟与用户交互在拖动操作期间通过 simulation.alpha(n).restart() '重新启动' 模拟。请参考此 示例

您可以在 setMigration 函数中无需用户交互来保持模拟动画更新(保持它 '保持温暖'),直到足够多的圆从 '红色' 移动到 '蓝色'。要添加的两行是:

simulations[0].alpha(0.15).restart();
simulations[1].alpha(0.15).restart();

如果您增加 0.15(最多为 1),您将看到两个模拟都更 '兴奋',因为迁移发生。0.15 对我来说似乎是一个愉快的视觉体验。这似乎可以在没有 .restart() 的情况下工作,所以您也可以根据启动 setMigration 的时间长短进行实验。

对于您的第二个问题 - 由于每个模拟都初始化为 nodes[0]nodes[1],所以可以将 d3.select('svg').selectAll('.circleN') 注释掉,因为您已经通过 nodes[0].push(nodes[1].pop()) 更改了数组内容。

以下是工作示例:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->
const width = 600;
const height = 200;

var nodes;
var simulations;

function loaded() {
  d3.select("svg")
    .attr("width", width)
    .attr("height", height);
  test();
}

function test() {

  nodes = [];
  nodes[0] = d3.range(50).map(function() {
    return {
      radius: 4,
      color: "blue"
    };
  })

  nodes[1] = d3.range(50).map(function() {
    return {
      radius: 4,
      color: "red"
    };
  })

  simulations = [null, null];

  update(0);
  update(1);

  setTimeout(startMigration, 1000);

}

function update(index) {

  simulations[index] = d3.forceSimulation(nodes[index])
    .force('charge', d3.forceManyBody().strength(10))
    .force('x', d3.forceX().x(function(d) {
      return width * (index + 1) / 3;
    }))
    .force('y', d3.forceY().y(function(d) {
      return height / 2;
    }))
    .force('collision', d3.forceCollide().radius(function(d) {
      return d.radius;
    }))
    .on('tick', ticked);

  function ticked() {
    var u = d3.select('svg')
      .selectAll('.circle' + index)
      .data(nodes[index])
      .join('circle')
      .attr('class', 'circle' + index)
      .attr('r', function(d) {
        return d.radius;
      })
      .style('fill', function(d) {
        //return colorScale(d.category);
        return d.color;
      })
      .attr('cx', function(d) {
        return d.x;
      })
      .attr('cy', function(d) {
        return d.y;
      });
  }

}

function startMigration() {
  setInterval(function() {
    if (nodes[1].length > 10) {
      nodes[0].push(nodes[1].pop());

      //d3.select('svg')
      //  .selectAll('.circle0')
      //  .data(nodes[0]);
      simulations[0].nodes(nodes[0]);

      //d3.select('svg')
      //  .selectAll('.circle1')
      //  .data(nodes[1]);
      simulations[1].nodes(nodes[1]);

      // reheat the simulations
      simulations[0].alpha(0.15).restart();
      simulations[1].alpha(0.15).restart();

    }
  }, 250);
}

<!-- language: lang-html -->

<html lang="en">

<head>
  <meta charset="UTF-8" />

  <meta http-equiv="X-UA-Compatible" content="ie=edge" />

  <script src="https://d3js.org/d3.v5.min.js"></script>
</head>

<body onload="loaded()">
  <div id='layout'>
    <div id='container'>
      <svg id="graph" />
    </div>
  </div>

</body>

</html>

<!-- end snippet -->
英文:

Lots of force simulations with user interaction 'reheat' the simulation during drag operations with simulation.alpha(n).restart(). See this observable for example.

You can do this without user interaction in setMigration function to keep the simulation animation updated (keep it 'warm' so to speak) until sufficient circles have moved from 'red' to 'blue'. The two lines to add are:

simulations[0].alpha(0.15).restart();
simulations[1].alpha(0.15).restart();

If you increase 0.15 (upto 1) you will see that both simulations are more 'excitable' as the migration occurs. 0.15 seemed like a pleasant visual experience for me. This seems to work without the .restart() so you can play around with that too likely depending on how long you wait to start setMigration.

For your second question - since each simulation was initialised to nodes[0] and nodes[1] then the d3.select(&#39;svg&#39;).selectAll(&#39;.circleN&#39;) can be commented out because you already changed the array content with nodes[0].push(nodes[1].pop()).

Working example below:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

const width = 600;
const height = 200;
var nodes;
var simulations;
function loaded() {
d3.select(&quot;svg&quot;)
.attr(&quot;width&quot;, width)
.attr(&quot;height&quot;, height)
test();
}
function test() {
nodes = [];
nodes[0] = d3.range(50).map(function() {
return {
radius: 4,
color: &quot;blue&quot;
};
})
nodes[1] = d3.range(50).map(function() {
return {
radius: 4,
color: &quot;red&quot;
};
})
simulations = [null, null];
update(0);
update(1);
setTimeout(startMigration, 1000);
}
function update(index) {
simulations[index] = d3.forceSimulation(nodes[index])
.force(&#39;charge&#39;, d3.forceManyBody().strength(10))
.force(&#39;x&#39;, d3.forceX().x(function(d) {
return width * (index + 1) / 3;
}))
.force(&#39;y&#39;, d3.forceY().y(function(d) {
return height / 2;
}))
.force(&#39;collision&#39;, d3.forceCollide().radius(function(d) {
return d.radius;
}))
.on(&#39;tick&#39;, ticked);
function ticked() {
var u = d3.select(&#39;svg&#39;)
.selectAll(&#39;.circle&#39; + index)
.data(nodes[index])
.join(&#39;circle&#39;)
.attr(&#39;class&#39;, &#39;circle&#39; + index)
.attr(&#39;r&#39;, function(d) {
return d.radius;
})
.style(&#39;fill&#39;, function(d) {
//return colorScale(d.category);
return d.color;
})
.attr(&#39;cx&#39;, function(d) {
return d.x;
})
.attr(&#39;cy&#39;, function(d) {
return d.y;
});
}
}
function startMigration() {
setInterval(function() {
if (nodes[1].length &gt; 10) {
nodes[0].push(nodes[1].pop());
//d3.select(&#39;svg&#39;)
//  .selectAll(&#39;.circle0&#39;)
//  .data(nodes[0]);
simulations[0].nodes(nodes[0]);
//d3.select(&#39;svg&#39;)
//  .selectAll(&#39;.circle1&#39;)
//  .data(nodes[1]);
simulations[1].nodes(nodes[1]);
// reheat the simulations
simulations[0].alpha(0.15).restart();
simulations[1].alpha(0.15).restart();
}
}, 250);
}

<!-- language: lang-html -->

&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot; /&gt;
&lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;ie=edge&quot; /&gt;
&lt;script src=&quot;https://d3js.org/d3.v5.min.js&quot;&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body onload=&quot;loaded()&quot;&gt;
&lt;div id=&#39;layout&#39;&gt;
&lt;div id=&#39;container&#39;&gt;
&lt;svg id=&quot;graph&quot; /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;

<!-- end snippet -->

huangapple
  • 本文由 发表于 2023年1月8日 22:59:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/75048783.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定