2015-12-07 9 views
6

Ho creato una visualizzazione diretta della forza D3 con etichette di testo lungo i collegamenti. L'unico problema che sto incontrando è che queste etichette appaiono sottosopra quando i collegamenti sono a sinistra del loro nodo di origine. Esempio qui:Mostrare il testo del collegamento D3 con il lato destro verso l'alto

enter image description here

Il codice dove mi posiziono il percorso e il testo appare in questo modo:

var nodes = flatten(data); 
var links = d3.layout.tree().links(nodes); 

var path = vis.selectAll('path.link') 
    .data(links, function(d) { 
    return d.target.id; 
    }); 

path.enter().insert('svg:path') 
    .attr({ 
    class: 'link', 
    id: function(d) { 
     return 'text-path-' + d.target.id; 
    }, 
    'marker-end': 'url(#end)' 
    }) 
    .style('stroke', '#ccc'); 

var linkText = vis.selectAll('g.link-text').data(links); 

linkText.enter() 
    .append('text') 
    .append('textPath') 
     .attr('xlink:href', function(d) { 
     return '#text-path-' + d.target.id; 
     }) 
     .style('text-anchor', 'middle') 
     .attr('startOffset', '50%') 
     .text(function(d) {return d.target.customerId}); 

So che sarà necessario determinare in qualche modo l'angolo di corrente di ogni percorso e quindi impostare le posizione di testo di conseguenza, ma non sono sicuro di come.

Ecco un link ad un blocco sulla base di questo numero: http://blockbuilder.org/MattDionis/5f966a5230079d9eb9f4

La risposta qui sotto mi ha preso circa il 90% della strada. Ecco ciò che il mio visualizzazione originale sarà simile con il testo più di un numero di cifre paio:

enter image description here

... e qui è quello che sembra utilizzare i suggerimenti nella risposta qui sotto:

enter image description here

Così mentre il testo ora è "sul lato destro", non segue più l'arco.

+0

Vedere https://stackoverflow.com/questions/8663844/add-text-label-onto-links-in-d3-force-directed-graph –

risposta

6

Gli archi disegnati sono tali che la loro tangente nel mezzo è esattamente la direzione della linea di base del testo, ed è anche colineare con il vettore che separa i due nodi dell'albero.

Possiamo usarlo per risolvere il problema.

È necessario un po 'di matematica. In primo luogo, definiamo una funzione che restituisce l'angolo di un vettore v rispetto all'asse orizzontale:

function xAngle(v) { 
    return Math.atan(v.y/v.x) + (v.x < 0 ? Math.PI : 0); 
} 

Quindi, ad ogni tick, facciamo ruotare il testo in posizione da meno l'angolo di linea di base.In primo luogo, alcune funzioni di utilità:

function isFiniteNumber(x) { 
    return typeof x === 'number' && (Math.abs(x) < Infinity); 
} 

function isVector(v) { 
    return isFiniteNumber(v.x) && isFiniteNumber(v.y); 
} 

e poi, nella funzione tick, aggiungere

linkText.attr('transform', function (d) { 
    // Checks just in case, especially useful at the start of the sim 
    if (!(isVector(d.source) && isVector(d.target))) { 
     return ''; 
    } 

    // Get the geometric center of the text element 
    var box = this.getBBox(); 
    var center = { 
     x: box.x + box.width/2, 
     y: box.y + box.height/2 
    }; 

    // Get the tangent vector 
    var delta = { 
     x: d.target.x - d.source.x, 
     y: d.target.y - d.source.y 
    }; 

    // Rotate about the center 
    return 'rotate(' 
     + (-180/Math.PI*xAngle(delta)) 
     + ' ' + center.x 
     + ' ' + center.y 
     + ')'; 
    }); 
}); 

edit: pic aggiunto:

example result

modificare 2 Con linee rette anziché archi curvi (semplicemente <text> invece di <textPath> all'interno di <text>), è possibile sostituire la parte della funzione tick che riguarda linkText con questo:

linkText.attr('transform', function(d) { 
    if (!(isVector(d.source) && isVector(d.target))) { 
     return ''; 
    } 

    // Get the geometric center of this element 
    var box = this.getBBox(); 
    var center = { 
     x: box.x + box.width/2, 
     y: box.y + box.height/2 
    }; 

    // Get the direction of the link along the X axis 
    var dx = d.target.x - d.source.x; 

    // Flip the text if the link goes towards the left 
    return dx < 0 
     ? ('rotate(180 ' 
      + center.x 
      + ' ' + center.y 
      + ')') 
     : ''; 
}); 

e questo è quello che si ottiene:

rotated text

Notate come il il testo viene capovolto mentre il collegamento passa dal puntare più a destra a puntare più a sinistra.

Il problema con questo è che il testo finisce sotto il collegamento. Che può essere fissato come segue:

linkText.attr('transform', function(d) { 
    if (!(isVector(d.source) && isVector(d.target))) { 
     return ''; 
    } 

    // Get the geometric center of this element 
    var box = this.getBBox(); 
    var center = { 
     x: box.x + box.width/2, 
     y: box.y + box.height/2 
    }; 

    // Get the vector of the link 
    var delta = { 
     x: d.target.x - d.source.x, 
     y: d.target.y - d.source.y 
    }; 

    // Get a unitary vector orthogonal to delta 
    var norm = Math.sqrt(delta.x * delta.x + delta.y * delta.y); 
    var orth = { 
     x: delta.y/norm, 
     y: -delta.x/norm 
    }; 

    // Replace this with your ACTUAL font size 
    var fontSize = 14; 

    // Flip the text and translate it beyond the link line 
    // if the link goes towards the left 
    return delta.x < 0 
     ? ('rotate(180 ' 
      + center.x 
      + ' ' + center.y 
      + ') translate(' 
      + (orth.x * fontSize) + ' ' 
      + (orth.y * fontSize) + ')') 
     : ''; 
}); 

e ora il risultato è simile al seguente:

enter image description here

Come si può vedere, il testo sta piacevolmente sulla parte superiore della linea, anche quando il collegamento punta verso sinistra.

Infine, per risolvere il problema mantenendo gli archi e avendo il testo rivolto verso l'alto curvo lungo l'arco, ritengo che sia necessario creare due elementi <textPath>. Uno per andare da source a target e uno per andare nella direzione opposta. Dovresti usare il primo quando il link va verso destra (delta.x >= 0) e il secondo quando il link va verso sinistra (delta.x < 0) e penso che il risultato sarebbe più bello e il codice non sarebbe necessariamente più complicato dell'originale , solo con un po 'più di logica aggiunta.

+0

Grazie! Questa è stata una spiegazione fantastica e mi ha ottenuto circa il 90% del modo in cui ci sono. Ho aggiunto un paio di altre immagini alla mia domanda originale per evidenziare il problema residuo. Sto puntando ad avere l'etichetta di testo su ciascun arco. – MattDionis

+0

Pensavo di aver visto una risposta per quanto riguarda il mio secondo problema da te ieri, ma sembra che se ne sia andato ora. Sto ancora lavorando su questo, disposto a cambiare i collegamenti curvi a linee rette se questo lo rende più facile da risolvere in quanto si trattava solo di una piccola decisione progettuale. – MattDionis

+0

@MattDionis Beh, sono lieto che tu abbia detto che non ti importa del testo che si curva, perché quel requisito avrebbe reso la soluzione più difficile da raggiungere. Il codice diventa molto più semplice se si consente al testo di essere lineare, e per questo intendo anche più semplice della prima soluzione di cui sopra, perché non è necessaria alcuna rotazione. Basta capovolgere il testo quando il vettore 'delta' va verso sinistra (' delta.x <0'). Scriverò il codice quando posso. – jrsala

Problemi correlati