web-dev-qa-db-fra.com

Calmez le tick initial d'une disposition de force

Je viens de commencer à essayer le d3 et je trouve la courbe d'apprentissage assez abrupte. Le processus est complètement différent de ce à quoi je suis habitué, et les mathématiques sont surtout bien au-dessus de ma tête.

Quoi qu'il en soit, mon projet consiste en une disposition de force représentant une carte des intégrations entre les systèmes. Cette partie fonctionne très bien, mais j'ai une préoccupation majeure, qui est également représentée dans la démo de mise en page forcée sur le site de Michael Bostocks: lorsque les nœuds sont lancés, ils semblent être rendus hors de la toile. Après cela, des mathématiques physiques sérieuses prennent le relais, simulant une attraction gravitationnelle qui envoie les nœuds sur un chemin assez déroutant d'avant en arrière jusqu'à ce qu'ils se calment et s'installent sur des coordonnées aléatoires. Bien que ces mouvements soient cool la première fois que la démo est exécutée, lorsque vous essayez de visualiser l'état des interfaces réseau d'une entreprise, il admine le point de vue et que les serveurs ne restent pas immobiles, cela devient fastidieux après un certain temps.

Je suis sûr que j'ai la configuration de mise en page correcte pour ce projet, car je veux que les serveurs se mettent en page automatiquement, je veux visualiser les liens entre eux. Je suis cependant ambivalent quant à l'effet gravitationnel.

Je me demande; est-il possible de régler manuellement la position initiale de chaque nœud, afin de pouvoir les rapprocher du centre de gravité et de raccourcir un peu le "temps de rebond"?

47
Øystein Amundsen

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

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

Par exemple, si vous chargez

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

dans

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

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

alors les nœuds commenceront à la position où force.start (). cela éviterait le chaos.

35
chh

En interne, sous une utilisation "normale", la mise en page forcée appelle à plusieurs reprises sa propre méthode tick() de manière asynchrone (via un setInterval ou requestAnimationFrame), jusqu'à ce que la mise en page se fixe sur une solution. À ce stade, sa valeur alpha() est égale ou se rapproche de 0.

Donc, si vous voulez "avancer rapidement" à travers ce processus de solution, vous pouvez appeler cette méthode tick() de manière synchrone encore et encore jusqu'à ce que l'alpha de la mise en page atteigne une valeur qui, pour vos besoins spécifiques, constitue un "assez proche" " Solution. Ainsi:

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

Une fois ce code exécuté, 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 (c'est-à-dire force.on('tick', drawMyLayout)), vous voudrez faire la liaison après ce code s'exécute, car sinon vous ' ll rendra inutilement la disposition des centaines de fois synchrone pendant la boucle while.

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

30
meetamit

J'ai traité quelque chose comme ça il y a quelque temps. Il y a quelques éléments à considérer.

1) Les tiques itératives simulent un système qui arrive à l'équilibre. Il n'y a donc aucun moyen d'éviter d'appeler tick autant de fois que nécessaire avant que le système ne s'installe et que vous ayez votre mise en page automatique. Cela dit, vous n'avez pas besoin de mettre à jour votre visualisation à chaque tick 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 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();
}

Cela s'exécute de manière synchrone et une fois exécuté, je crée les éléments dom pour tous les nœuds et liens. Pour les petits graphiques, cela fonctionne assez rapidement, mais vous constaterez qu'il y a un délai pour les graphiques plus gros (plus de 100 nœuds) - ils sont tout simplement beaucoup plus chers en termes de calcul.

2) Vous pouvez mettre en cache/amorcer les dispositions. La disposition des forces répartira uniformément les nœuds lors de l'initialisation si aucune position n'est définie! Donc, si vous vous assurez que vos nœuds ont défini l'attribut x et y, ils seront utilisés. En particulier, lorsque je mets à jour un graphique existant, je réutiliserai 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'un 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 nœuds sous la même disposition, vous devrez également vous assurer de définir px et py sur NaN ou vous obtiendrez des résultats étranges.

11
Superboggly

Sur la base d'autres réponses, j'ai fait 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();
}
8
Ali Shakiba

Peut-être que force.friction(0.5), ou un autre nombre inférieur à la valeur par défaut 0,9, pourrait aider? Au moins, cela donne une impression moins chaotique lors du chargement de la page.

4
Anton
        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 exemple de ce que vous cherchez peut-être. Il définit les attributs x & y des nouveaux nœuds avant de les insérer dans la présentation. L'emplacement souhaité est le centre de l'élément svg.

0
timebandit