Построение от начала Герардо,
Я думаю, что одним из ключевых моментов, чтобы избежать чрезмерной энтропии, является определение затухания скорости - это поможет избежать превышения заданного местоположения.Слишком медленно, вы не получите увеличения плотности, когда поток останавливается слишком быстро, и у вас есть узлы, которые слишком перепутаны или превышают их пункт назначения, колебаясь между слишком большим и слишком коротким.
A многиесила тела здесь полезна - она может удерживать узлы на расстоянии (а не от силы столкновения), причем отталкивание между узлами компенсируется позиционными силами для каждого кластера.Ниже я использовал две точки центрирования и свойство узла, чтобы определить, какая из них используется.Эти силы должны быть довольно слабыми - сильные силы довольно легко приводят к коррекции.
Вместо того, чтобы использовать таймер, я использую функцию Simulation.find () каждый тик, чтобы выбрать один узел из одного кластера иПереключите, какой центр он привлекает.После 1000 тиков симуляция, приведенная ниже, остановится:
var canvas = d3.select("canvas");
var width = +canvas.attr("width");
var height = +canvas.attr("height");
var context = canvas.node().getContext('2d');
// Key variables:
var nodes = [];
var strength = -0.25; // default repulsion
var centeringStrength = 0.01; // power of centering force for two clusters
var velocityDecay = 0.15; // velocity decay: higher value, less overshooting
var outerRadius = 250; // new nodes within this radius
var innerRadius = 100; // new nodes outside this radius, initial nodes within.
var startCenter = [250,250]; // new nodes/initial nodes center point
var endCenter = [710,250]; // destination center
var n = 200; // number of initial nodes
var cycles = 1000; // number of ticks before stopping.
// Create a random node:
var random = function() {
var angle = Math.random() * Math.PI * 2;
var distance = Math.random() * (outerRadius - innerRadius) + innerRadius;
var x = Math.cos(angle) * distance + startCenter[0];
var y = Math.sin(angle) * distance + startCenter[1];
return {
x: x,
y: y,
strength: strength,
migrated: false
}
}
// Initial nodes:
for(var i = 0; i < n; i++) {
nodes.push(random());
}
var simulation = d3.forceSimulation()
.force("charge", d3.forceManyBody().strength(function(d) { return d.strength; } ))
.force("x1",d3.forceX().x(function(d) { return d.migrated ? endCenter[0] : startCenter[0] }).strength(centeringStrength))
.force("y1",d3.forceY().y(function(d) { return d.migrated ? endCenter[1] : startCenter[1] }).strength(centeringStrength))
.alphaDecay(0)
.velocityDecay(velocityDecay)
.nodes(nodes)
.on("tick", ticked);
var tick = 0;
function ticked() {
tick++;
if(tick > cycles) this.stop();
nodes.push(random()); // create a node
this.nodes(nodes); // update the nodes.
var migrating = this.find((Math.random() - 0.5) * 50 + startCenter[0], (Math.random() - 0.5) * 50 + startCenter[1], 10);
if(migrating) migrating.migrated = true;
context.clearRect(0,0,width,height);
nodes.forEach(function(d) {
context.beginPath();
context.fillStyle = d.migrated ? "steelblue" : "orange";
context.arc(d.x,d.y,3,0,Math.PI*2);
context.fill();
})
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<canvas width="960" height="500"></canvas>
Вот вид блока (фрагмент будет лучше полной страницы, параметры предназначены для него).Начальные узлы формируются в том же кольце, что и более поздние узлы (поэтому на начальном этапе есть некоторая путаница, но это легко исправить).На каждом тике создается один узел и делается одна попытка перенести узел рядом с серединой на другую сторону - таким образом создается поток (в отличие от любого случайного узла).
Для жидкостейлучше всего использовать несвязанные узлы (я использовал их для моделирования ветра) - связанные узлы идеальны для структурированных материалов, таких как сетки или ткани.И, как и Герардо, я также фанат работ Надии, но в будущем мне придется следить за работой Ширли.