Acalme a escala inicial de um layout de força

Eu comecei a brincar com o d3, e acho a curva de aprendizado bastante íngreme. O processo é completamente diferente do que eu estou acostumado, e a matemática é na maioria das vezes muito acima da minha cabeça.

De qualquer forma, meu projeto consiste em um layout de força representando o mapa de integrações entre sistemas. Esta parte funciona muito bem, mas eu tenho uma grande preocupação, que também é representada na demo de layout direcionado à força no site de Michael Bostocks: Quando os nós são iniciados, eles parecem ser renderizados fora da canvas. Depois disso, alguma matemática física séria está assumindo, simulando uma atração gravitacional que envia os nós em um caminho bastante confuso para frente e para trás até que eles se acalmem e resolvam algumas coordenadas aleatórias. Apesar de esses movimentos estarem ficando fracos na primeira vez que a demo é executada, quando você está tentando visualizar o status das interfaces de rede de uma empresa que administra o ponto de vista e os servidores não ficam parados, fica cansativo depois de um tempo.

Tenho certeza de que tenho a configuração de layout correta para este projeto, porque eu quero que os servidores sejam autolayout, eu quero visualizar links entre eles. No entanto, sou ambivalente em relação ao efeito de gravitação.

Eu me pergunto; é possível definir a posição inicial de cada nó manualmente, para que eu possa colocá-los mais perto do centro gravitacional e encurtar um pouco o “tempo de ressalto”?

Todas as respostas acima não entendem a pergunta de Øystein Amundsen.

A única maneira de estabilizar a força sobre ela é configurar node.x e node.ya value. Por favor, note que o nó são os dados de d3.js, não o tipo DOM representado.

Por exemplo, se você carregar

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

para dentro

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

você tem que setar todos .x e .y de todos os elementos no array de nós será assim (no 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) 

então os nós começarão na posição quando force.start (). isso evitaria o caos.

Internamente, sob uso “normal”, o layout de força chama repetidamente seu próprio método tick() forma assíncrona (por meio de um setInterval ou requestAnimationFrame ), até que o layout se estabeleça em uma solução. Nesse ponto, seu valor alpha() é igual ou se aproxima de 0.

Portanto, se você quiser “avançar rapidamente” por meio desse processo de solução, poderá chamar esse método tick() forma síncrona repetidamente até que o alfa do layout atinja um valor que, para seus requisitos específicos, constitua uma solução “próxima o suficiente”. Igual a:

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

Depois que esse código é executado, você pode desenhar seu layout com base no estado dos nós. Ou, se você está desenhando seu layout vinculando ao evento tick (ex. force.on('tick', drawMyLayout) ), você vai querer fazer a binding depois que este código for executado, porque senão você renderiza desnecessariamente layout centenas de vezes de forma síncrona durante o loop while.

JohnS resumiu essa abordagem a uma única function concisa. Veja a resposta dele em algum lugar desta página.

Eu lidei com algo um pouco assim há um tempo atrás. Existem algumas coisas a serem consideradas.

1) Os ticks iterativos estão simulando um sistema que se equilibra. Portanto, não há como evitar a chamada de carrapato quantas vezes forem necessárias antes que o sistema seja resolvido e você tenha seu layout automático. Dito isto, você não precisa atualizar sua visualização a cada tick para que a simulação funcione! As iterações serão muito mais rápidas, na verdade, se você não o fizer. A parte relevante do meu código vai:

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

Isso é executado de forma síncrona e, depois de executar, eu crio os elementos dom para todos os nós e links. Para charts pequenos, isso é muito rápido, mas você descobrirá que há um atraso para charts maiores (mais de 100 nós) - eles são muito mais caros do ponto de vista computacional.

2) Você pode colocar em cache / semear layouts. O layout de força distribuirá uniformemente os nós na boot se nenhuma posição for definida! Portanto, se você tiver certeza de que seus nós tenham configurado os atributos xey, eles serão usados. Em particular, quando estou atualizando um gráfico existente, reutilizarei as posições x e y de um layout anterior.

Você encontrará com um bom layout inicial que precisará de muito menos iterações para alcançar uma configuração estável. (Isto é o que hasCachedLayout rastreia no código acima). NB: Se você estiver reutilizando os mesmos nós do mesmo layout, você também terá que configurar px e py para NaN ou obterá resultados estranhos.

Com base em outras respostas, fiz 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(); } 

Talvez force.friction(0.5) , ou algum outro número menor que o padrão 0.9, ajudasse? Pelo menos, dá uma impressão menos caótica no carregamento da página.

  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(" + dx + "," + dy + ")"; }); } 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; }