47 votes

Calmer le tic-tac initial d'une disposition de force

Je viens de commencer à m'initier à la D3 et je trouve la courbe d'apprentissage assez raide. Le processus est complètement différent de ce à quoi je suis habitué, et les mathématiques me dépassent largement.

Quoi qu'il en soit, mon projet consiste en une mise en page de la force représentant la carte des intégrations entre les systèmes. Cette partie fonctionne très bien, mais j'ai un souci majeur, qui est également représenté dans la démo de la mise en page dirigée par la force sur le site de Michael Bostocks : Lorsque les noeuds sont initiés, ils semblent être rendus hors de la toile. Ensuite, de sérieuses mathématiques physiques prennent le relais, simulant une attraction gravitationnelle qui envoie les nœuds sur un chemin plutôt confus d'avant en arrière jusqu'à ce qu'ils se calment et se fixent sur des coordonnées aléatoires. Bien que ces mouvements soient super cool la première fois que la démo est exécutée, lorsque vous essayez de visualiser l'état des interfaces réseau du point de vue d'un administrateur de l'entreprise et que les serveurs ne veulent pas rester immobiles, cela devient fatigant au bout d'un moment.

Je suis sûr d'avoir la bonne configuration de mise en page pour ce projet, car je veux que les serveurs se mettent en page automatiquement et je veux visualiser les liens entre eux. Je suis cependant ambivalent en ce qui concerne l'effet de gravitation.

Je me demande s'il est possible de définir manuellement la position initiale de chaque nœud, afin de les rapprocher du centre gravitationnel et de raccourcir un peu le "temps de rebond".

36voto

Chen Hung Han Points 96

Toutes les réponses ci-dessus ont mal compris la question d'Øystein Amundsen.

La seule façon de stabiliser la force au démarrage est de définir une valeur appropriée pour node.x et node.y. Veuillez noter que le nœud est la donnée de d3.js, et non le type DOM représenté.

Par exemple, si vous chargez

nodes = [{"id": a}, {"id": b}, {"id": c}]

en

d3.layout.force().nodes(nodes)

vous devez définir tous les .x et .y de tous les éléments du tableau de nœuds ce sera comme ceci ( dans 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)

alors les noeuds commenceront à la position lorsque force.start(). Cela permettrait d'éviter le chaos.

30voto

meetamit Points 7645

En interne, dans le cadre d'une utilisation "normale", la disposition de la force appelle de manière répétée sa propre tick() de manière asynchrone (via une setInterval ou requestAnimationFrame ), jusqu'à ce qu'une solution soit trouvée. À ce stade, son alpha() est égale ou s'approche de 0.

Ainsi, si vous voulez "avancer" dans ce processus de solution, vous pouvez appeler de manière synchrone cette fonction tick() jusqu'à ce que l'alpha de la mise en page atteigne une valeur qui, pour vos besoins spécifiques, constitue une solution "suffisamment proche". Par exemple :

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

Après l'exécution de ce code, vous pouvez dessiner votre disposition en fonction de l'état des nœuds. Ou, si vous dessinez votre mise en page en vous liant à l'événement tick (par ex. force.on('tick', drawMyLayout) ), vous devrez effectuer la liaison suivante après ce code s'exécute, parce que sinon vous rendez inutilement la mise en page des centaines de fois de manière synchrone pendant l'exécution de la while boucle.

JohnS a ramené cette approche à une seule fonction concise. Voir sa réponse quelque part sur cette page.

11voto

Superboggly Points 2943

J'ai eu affaire à quelque chose d'un peu similaire il y a quelque temps. Il y a quelques éléments à prendre en compte.

1) Les tics itératifs simulent un système qui arrive à l'équilibre. Il n'y a donc aucun moyen d'éviter d'appeler le tick autant de fois que nécessaire avant que le système ne se stabilise et que vous ayez votre auto-layout. Cela dit, vous n'avez pas besoin de mettre à jour votre visualisation à chaque tic pour que la simulation fonctionne ! Les itérations iront beaucoup plus vite, en fait, si vous ne le faites pas. La partie pertinente de mon code est la suivante :

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

Cette opération s'exécute de manière synchrone et, une fois qu'elle est terminée, je crée les éléments dom pour tous les nœuds et les liens. Pour les petits graphes, cela fonctionne assez rapidement, mais vous constaterez qu'il y a un retard pour les graphes plus grands (100+ nœuds) - ils sont simplement beaucoup plus coûteux en calcul.

2) Vous pouvez mettre en cache / ensemencer les mises en page. La disposition de force distribuera uniformément les noeuds lors de l'initialisation. si aucune position n'est définie ! Ainsi, si vous vous assurez que vos nœuds ont des attributs x et y, ceux-ci seront utilisés. En particulier, lorsque je mets à jour un graphique existant, je réutilise les positions x et y d'une disposition précédente.

Vous constaterez qu'avec une bonne mise en page initiale, vous aurez besoin d'une lot moins d'itérations pour atteindre une configuration stable. (C'est ce que hasCachedLayout suit dans le code ci-dessus). NB : Si vous réutilisez les mêmes noeuds de la même disposition, vous devrez également vous assurer de mettre px et py à NaN ou vous obtiendrez des résultats bizarres.

8voto

JohnS Points 3434

Sur la base d'autres réponses, j'ai élaboré cette méthode :

function forwardAlpha(layout, alpha, max) {
  alpha = alpha || 0;
  max = max || 1000;
  var i = 0;
  while(layout.alpha() > alpha && i++ < max) layout.tick();
}

4voto

Anton Points 1

Peut-être force.friction(0.5) ou un autre chiffre inférieur à la valeur par défaut de 0,9, serait-il utile ? Au moins, cela donne une impression moins chaotique au chargement de la page.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X