d3.js - framework - Calmar el tic de inicial de un diseño de fuerza
jquery d3 (6)
En base a otras respuestas, hice este método:
function forwardAlpha(layout, alpha, max) {
alpha = alpha || 0;
max = max || 1000;
var i = 0;
while(layout.alpha() > alpha && i++ < max) layout.tick();
}
Acabo de comenzar a incursionar con d3 y encontrar la curva de aprendizaje bastante empinada. El proceso es completamente diferente de lo que estoy acostumbrado, y las matemáticas están muy por encima de mi cabeza.
De todos modos, mi proyecto consiste en un diseño de fuerza que representa un mapa de integraciones entre sistemas. Esta parte funciona increíblemente bien, pero tengo una gran preocupación, que también está representada en la demostración de diseño de fuerza dirigida en el sitio de Michael Bostock: Cuando los nodos se inician, parecen estar fuera del lienzo. Después de esto, algunas matemáticas físicas serias se están haciendo cargo, simulando una atracción gravitatoria que envía a los nodos en un camino confuso hacia adelante y hacia atrás hasta que se calman y se asientan en algunas coordenadas al azar. Aunque estos movimientos son sorprendentes la primera vez que se ejecuta la demostración, cuando está tratando de ver el estado de las interfaces de red desde el punto de vista de una empresa que administra y los servidores simplemente no se quedan quietos, después de un tiempo se vuelve tedioso.
Estoy seguro de que tengo la configuración de diseño correcta para este proyecto, porque quiero que los servidores se descarguen automáticamente, quiero visualizar enlaces entre ellos. Sin embargo, soy ambivalente con respecto al efecto de gravitación.
Me pregunto; ¿Es posible establecer manualmente la posición inicial de cada nodo, de modo que pueda acercarlos más al centro gravitacional y acortar un poco el "tiempo de rebote"?
Hace un tiempo lidié con algo así. Hay algunas cosas a considerar.
1) Los tics iterativos simulan un sistema que llega al equilibrio. Así que no hay forma de evitar llamar a marcar tantas veces como sea necesario antes de que el sistema se instale y usted tenga su diseño automático. Dicho esto, no es necesario que actualice su visualización cada tic para que la simulación funcione. Las iteraciones irán mucho más rápido, de hecho, si no lo haces. La parte relevante de mi código va:
var iters = 600; // You can get decent results from 300 if you are pressed for time
var thresh = 0.001;
if(!hasCachedLayout || optionsChanged || minorOptionsChanged) {
force.start(); // Defaults to alpha = 0.1
if(hasCachedLayout) {
force.alpha(optionsChanged ? 0.1 : 0.01);
}
for (var i = iters; i > 0; --i) {
force.tick();
if(force.alpha() < thresh) {
//console.log("Reached " + force.alpha() + " for " + data.nodes.length + " node chart after " + (iters - i) + " ticks.");
break;
}
}
force.stop();
}
Esto se ejecuta de forma sincrónica y, una vez ejecutado, creo los elementos dom para todos los nodos y enlaces. Para los gráficos pequeños, esto funciona bastante rápido, pero encontrará que hay una demora para los gráficos más grandes (más de 100 nodos); simplemente son mucho más costosos desde el punto de vista computacional.
2) Puede almacenar en caché / diseños de semillas. El diseño de la fuerza distribuirá uniformemente los nodos tras la inicialización si no se establece ninguna posición. Entonces, si se asegura de que sus nodos hayan configurado los atributos xey, estos se usarán. En particular, cuando estoy actualizando un gráfico existente, volveré a utilizar las posiciones xey de un diseño anterior.
Encontrará que con un buen diseño inicial necesitará muchas menos iteraciones para alcanzar una configuración estable. (Esto es lo que has aprendido en las pistas de CachedLayout en el código anterior). NB: si estás reutilizando los mismos nodos del mismo diseño, entonces también deberás asegurarte de configurar px y py en NaN o obtendrás resultados extraños.
Internamente, bajo el uso "normal", el diseño de fuerza llama repetidamente a su propio método tick()
forma asíncrona (a través de un setInterval
o requestAnimationFrame
), hasta que el diseño se asiente en una solución. En ese punto, su valor alpha()
es igual o se aproxima a 0.
Entonces, si desea "avanzar rápidamente" a través de este proceso de solución, puede llamar sincrónicamente a ese método tick()
una y otra vez hasta que el alfa del diseño alcance un valor que, para sus requisitos específicos, constituya una solución "suficientemente cercana". Al igual que:
var force = d3.layout.force(),
safety = 0;
while(force.alpha() > 0.05) { // You''ll want to try out different, "small" values for this
force.tick();
if(safety++ > 500) {
break;// Avoids infinite looping in case this solution was a bad idea
}
}
if(safety < 500) {
console.log(''success??'');
}
Después de ejecutar este código, puede dibujar su diseño en función del estado de los nodos. O bien, si está dibujando su diseño mediante el enlace al evento tick (es decir, force.on(''tick'', drawMyLayout)
), querrá hacer el enlace después de que se ejecute este código, porque de lo contrario representará innecesariamente el diseño cientos de veces sincrónicamente durante el ciclo while.
JohnS ha reducido este enfoque a una única función concisa. Vea su respuesta en algún lugar de esta página.
Tal vez force.friction(0.5)
, o algún otro número más bajo que el 0.9 predeterminado, ayudaría? Al menos da una impresión menos caótica al cargar la página.
Todas las respuestas anteriores malentendieron la pregunta de Øystein Amundsen.
La única forma de estabilizar la fuerza al comenzar es establecer node.x y node.ya el valor correcto. Tenga en cuenta que el nodo son los datos de d3.js, no el tipo de DOM representado.
Por ejemplo, si carga
nodes = [{"id": a}, {"id": b}, {"id": c}]
dentro
d3.layout.force().nodes(nodes)
tienes que establecer todos los .x y .y de todos los elementos en una matriz de nodos así será (en coffeescript)
nodes = [{"id": a}, {"id": b}, {"id": c}]
for i in [0..2]
nodes[i].x = 500 #the initial x position of node
nodes[i].y = 300 #the initial y position of node
d3.layout.force().nodes(nodes).links(links)
entonces los nodos comenzarán en la posición cuando force.start (). esto evitaría el caos.
var width = 960,
height = 500;
var fill = d3.scale.category20();
var force = d3.layout.force()
.size([width, height])
.nodes([{}]) // initialize with a single node
.linkDistance(30)
.charge(-60)
.on("tick", tick);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.on("mousedown", mousedown);
svg.append("rect")
.attr("width", width)
.attr("height", height);
var nodes = force.nodes(),
links = force.links(),
node = svg.selectAll(".node"),
link = svg.selectAll(".link");
// var cursor = svg.append("circle")
// .attr("r", 30)
// .attr("transform", "translate(-100,-100)")
// .attr("class", "cursor");
restart();
function mousedown() {
var point = d3.mouse(this),
node = {
x: width / 2,
y: height / 2,
"number": Math.floor(Math.random() * 100)
},
n = nodes.push(node);
// add links to any nearby nodes
/* nodes.forEach(function(target) {
var x = target.x - node.x,
y = target.y - node.y;
if (Math.sqrt(x * x + y * y) < 30) {
links.push({source: node, target: target});
}
});
*/
restart();
}
function tick() {
link.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
function restart() {
link = link.data(links);
link.enter().insert("line", ".node")
.attr("class", "link");
node = node.data(nodes);
// node.enter().insert("circle", ".cursor")
// .attr("class", "node")
// .attr("r", 5)
// .call(force.drag);
var nodeEnter = node.enter().insert("svg:g", ".cursor")
.attr("class", "node")
.call(force.drag);
nodeEnter.append("svg:circle")
.attr("r", 5)
nodeEnter.append("svg:text")
.attr("class", "textClass")
.attr("x", 14)
.attr("y", ".31em")
.text(function(d) {
return d.number;
});
force.start();
}
rect {
fill: none;
pointer-events: all;
}
.node {
fill: #000;
}
.cursor {
fill: none;
stroke: brown;
pointer-events: none;
}
.link {
stroke: #999;
}
.textClass {
stroke: #323232;
font-family: "Lucida Grande", "Droid Sans", Arial, Helvetica, sans-serif;
font-weight: normal;
stroke-width: .5;
font-size: 14px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Un ejemplo de lo que podrías estar buscando. Establece los atributos xey de nuevos nodos antes de insertarlos en el diseño. La ubicación deseada es el centro del elemento svg.