2016-07-07 47 views
6

Utilizzando un layout force-directed in d3, come si fa a rendere la distanza del collegamento una priorità, mantenendo comunque un layout grafico piacevole?d3 force directed layout - link distance priority

Se specifico distanze di collegamento dinamico, ma mantenere la carica di default, le mie distanze grafico sono trasformato un po 'dalla funzione di carica, e non sono più accurate distanze:

enter image description here

Tuttavia, se tolgo la carica, il grafico si presenta così:

enter image description here

Qualche consiglio apprezzato!

+2

vedi qui likDistance e linkStrength http://bl.ocks.org/sathomas/774d02a21dc1c714def8 questo dovrebbe aiutarti. – Cyril

+1

Grazie a Cyril per la spina. Puoi anche leggere il testo completo del mio libro, incluso il capitolo su D3, sul mio sito web: [http://jsDataV.is] (http://jsDataV.is) –

+0

Spero tu abbia visto questo https: // github.com/d3/d3-force/blob/master/README.md#link_distance per v4 puoi dare una distanza di collegamento http://plnkr.co/edit/12D55owSNuDnSH0hNfWu?p = info ma non sono sicuro se stai cercando quanto sopra. – Cyril

risposta

3

Se ho capito bene, credo che ci sia una soluzione potenziale.

Per ottenere la distanza del collegamento precisa, è necessario impostare le forze di carica e di collisione a zero, ma come suggerisce l'immagine, i nodi non sono distanziati in modo da rappresentare altri nodi, solo quei nodi che condividono collegamenti con. Poiché d3.force inizializza nodi che non hanno valori x, y in una disposizione di fillotassi, i collegamenti e i nodi saranno raggruppati in modi non previsti. Ma, se si applica una forza repulsiva durante la simulazione, la spaziatura viene migliorata ma le distanze sono distorte.

La soluzione possibile è utilizzare inizialmente una forza di repulsione perché è necessario separare i nodi in cluster riconoscibili basati su collegamenti. Quindi, dopo che sono stati separati, ridurre la forza repulsiva a nulla in modo che l'unica forza applicata sia in relazione alla distanza di collegamento desiderata.

Ciò richiede di modificare le forze nella funzione di spunta man mano che il grafico si evolve. Ciò richiede anche che tutte le distanze di collegamento siano compatibili l'una con l'altra (un triangolo di nodi non può avere due angoli separati da 100 pixel e l'angolo rimanente collegato agli altri due di 10 pixel).

Qualcosa di simile potrebbe funzionare all'interno della funzione segno di spunta in situazioni semplici:

var alpha = this.alpha(); // starts at 1 by default, simulation ends at zero 

var chargeStrength; // a multiplier for charge strength 

if (alpha > 0.2) { 
    chargeStrength = (alpha - 0.2/0.8); // decrease for the first portion of the simulation 
} 
else { 
    chargeStrength = 0; // leave at zero and give the link distance force time to work without competing forces 
} 

Per visualizzazioni più complesse, si potrebbe concedere più tempo per il raffreddamento diminuendo alphaDecay, o aumentarlo per quelle più semplici.

Ho fatto un semplice esempio qui, alla fine della visualizzazione le distanze sono registrate (ho aumentato alphaDecay nello snippet qui sotto per velocizzarlo al costo di precisione, ma è comunque abbastanza buono) e referenziato con le distanze desiderate

var graph = { 
 
    nodes: d3.range(15).map(Object), 
 
    links: [ 
 
    {source: 0, target: 1, distance: 20 }, 
 
    {source: 0, target: 2, distance: 40}, 
 
    {source: 0, target: 3, distance: 80}, 
 
    {source: 1, target: 4, distance: 20}, 
 
    {source: 1, target: 5, distance: 40}, 
 
    {source: 1, target: 6, distance: 80}, 
 
    {source: 2, target: 7, distance: 12}, 
 
    {source: 2, target: 8, distance: 8}, 
 
    {source: 2, target: 9, distance: 6}, 
 
    {source: 3, target: 10, distance: 10}, 
 
    {source: 3, target: 11, distance: 10}, 
 
    {source: 3, target: 12, distance: 2}, 
 
\t {source: 3, target: 13, distance: 2}, 
 
\t {source: 3, target: 14, distance: 2} 
 
    ] 
 
}; 
 

 
var svg = d3.select("svg"), 
 
    width = +svg.attr("width"), 
 
    height = +svg.attr("height"); 
 

 
var color = d3.scaleOrdinal(d3.schemeCategory20); 
 

 
var simulation = d3.forceSimulation() 
 
    .force("charge", d3.forceManyBody().strength(-30)) 
 
\t .force("link", d3.forceLink().distance(function(d) { return d.distance }).strength(2)) 
 
    .force("center", d3.forceCenter(width/2, height/2)) 
 
\t .force("collide",d3.forceCollide().strength(0).radius(0)) 
 
\t .alphaDecay(0.03) 
 
    .velocityDecay(0.4); 
 
\t 
 
\t 
 
\t 
 
    var link = svg.append("g") 
 
     .attr("class", "links") 
 
    .selectAll("line") 
 
    .data(graph.links) 
 
    .enter().append("line") 
 
     .attr("stroke-width", 1); 
 

 
    var node = svg.append("g") 
 
    .attr("class", "nodes") 
 
    .selectAll("circle") 
 
    .data(graph.nodes) 
 
    .enter().append("circle") 
 
    .attr("r", 3) 
 
\t 
 
simulation 
 
     .nodes(graph.nodes) 
 
     .on("tick", ticked); 
 

 
    simulation.force("link") 
 
     .links(graph.links); 
 

 
    
 
    
 
\t 
 
    function ticked() { 
 
\t 
 
\t var alpha = this.alpha(); 
 
\t var chargeStrength; 
 

 
    if (alpha > 0.2) { 
 
\t \t chargeStrength = (alpha - 0.2/0.8); 
 
\t } 
 
\t else { 
 
\t \t chargeStrength = 0; 
 
\t } 
 

 
\t this.force("charge", d3.forceManyBody().strength(-30 * chargeStrength)) 
 
\t 
 
\t 
 
    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("cx", function(d) { return d.x; }) 
 
     .attr("cy", function(d) { return d.y; }); 
 
\t \t 
 
\t // validate: 
 
\t if (alpha < 0.001) { 
 
\t \t link.each(function(d,i) { 
 
\t \t 
 
\t \t \t var a = d.source.x - d.target.x; 
 
\t \t \t var b = d.source.y - d.target.y; 
 
\t \t  var c = Math.pow(a*a + b*b, 0.5); 
 
\t \t \t 
 
\t \t \t console.log("specified length: " + graph.links[i].distance + ", realized distance: " + c); 
 
\t \t }) 
 
\t } 
 
    }
.links line { 
 
    stroke: #999; 
 
    stroke-opacity: 0.6; 
 
} 
 

 
.nodes circle { 
 
    stroke: #fff; 
 
    stroke-width: 1.5px; 
 
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script> 
 
<svg width="500" height="300"></svg>

A seconda della complessità del grafico, potrebbe essere necessario adattare tempo di raffreddamento, la forza forza repulsiva e come modificarlo come alpha raffredda, velocityDecay (potenzialmente modificandolo nel funzione tick), e/o la forza della distanza stessa.

+0

Questa è una bella soluzione! Non mi è mai venuto in mente di modificare le forze in fuga. Lo terrò sicuramente presente poiché potrebbe rivelarsi utile anche in altre applicazioni. La taglia è stata ben meritata! – altocumulus

+0

L'unica cosa che non riesco a capire, però, è il fatto che si sta eseguendo l'override della forzatura '' link ''nella configurazione iniziale della simulazione. Poiché verrà utilizzata solo l'ultima forza per il nome, ciò sembra in qualche modo ridondante. Potresti far luce su questo? – altocumulus

+0

Questa è una supervisione piuttosto brutta da parte mia, non so perché l'ho fatto. Grazie per segnalarlo. –

Problemi correlati