Aggiornamento di SVG Element Z-Index con D3

Qual è un modo efficace per portare un elemento SVG all’inizio dell’ordine z, usando la libreria D3?

Il mio scenario specifico è un grafico a torta che evidenzia (aggiungendo un stroke al path ) quando il mouse si trova su un dato pezzo. Il blocco di codice per generare il mio grafico è qui sotto:

 svg.selectAll("path") .data(d) .enter().append("path") .attr("d", arc) .attr("class", "arc") .attr("fill", function(d) { return color(d.name); }) .attr("stroke", "#fff") .attr("stroke-width", 0) .on("mouseover", function(d) { d3.select(this) .attr("stroke-width", 2) .classd("top", true); //.style("z-index", 1); }) .on("mouseout", function(d) { d3.select(this) .attr("stroke-width", 0) .classd("top", false); //.style("z-index", -1); }); 

Ho provato alcune opzioni, ma finora non ho avuto fortuna. Usando lo style("z-index") e le chiamate classd entrambe non funzionavano.

La class “top” è definita come segue nel mio CSS:

 .top { fill: red; z-index: 100; } 

La dichiarazione di fill è lì per assicurarmi che sapevo che si stava accendendo / spegnendo correttamente. È.

Ho sentito che usare sort è un’opzione, ma non sono chiaro su come sarebbe implementato per portare l’elemento “selezionato” in cima.

AGGIORNARE:

Ho risolto la mia situazione particolare con il seguente codice, che aggiunge un nuovo arco mouseover sull’evento mouseover per mostrare un momento saliente.

 svg.selectAll("path") .data(d) .enter().append("path") .attr("d", arc) .attr("class", "arc") .style("fill", function(d) { return color(d.name); }) .style("stroke", "#fff") .style("stroke-width", 0) .on("mouseover", function(d) { svg.append("path") .attr("d", d3.select(this).attr("d")) .attr("id", "arcSelection") .style("fill", "none") .style("stroke", "#fff") .style("stroke-width", 2); }) .on("mouseout", function(d) { d3.select("#arcSelection").remove(); }); 

Una delle soluzioni presentate dallo sviluppatore è: “usa l’operatore di ordinamento di D3 per riordinare gli elementi”. (consultare https://github.com/mbostock/d3/issues/252 )

In questa luce, si potrebbero ordinare gli elementi confrontando i loro dati, o posizioni se fossero elementi dataless:

 .on("mouseover", function(d) { svg.selectAll("path").sort(function (a, b) { // select the parent and sort the path's if (a.id != d.id) return -1; // a is not the hovered element, send "a" to the back else return 1; // a is the hovered element, bring "a" to the front }); }) 

Come spiegato nelle altre risposte, SVG non ha una nozione di z-index. Invece, l’ordine degli elementi nel documento determina l’ordine nel disegno.

Oltre a riordinare gli elementi manualmente, esiste un altro modo per determinate situazioni:

Lavorando con D3 hai spesso determinati tipi di elementi che devono essere sempre disegnati sopra altri tipi di elementi .

Ad esempio, quando si creano grafici, i collegamenti devono essere sempre posizionati sotto i nodes. Più in generale, alcuni elementi di sfondo di solito devono essere collocati al di sotto di tutto il resto, mentre alcuni punti salienti e sovrapposizioni devono essere posizionati sopra.

Se si dispone di questo tipo di situazione, ho trovato che la creazione di elementi del gruppo principale per questi gruppi di elementi è il modo migliore per andare. In SVG, puoi usare l’elemento g per quello. Ad esempio, se si dispone di collegamenti che devono essere sempre posizionati sotto i nodes, attenersi alla seguente procedura:

 svg.append("g").attr("id", "links") svg.append("g").attr("id", "nodes") 

Ora, quando dipingi i tuoi collegamenti e i tuoi nodes, seleziona come segue (i selettori che iniziano con # riferimento all’id dell’elemento):

 svg.select("#links").selectAll(".link") // add data, attach elements and so on svg.select("#nodes").selectAll(".node") // add data, attach elements and so on 

Ora, tutti i collegamenti verranno sempre aggiunti strutturalmente prima di tutti gli elementi del nodo. Pertanto, SVG mostrerà tutti i link sotto tutti i nodes, non importa quanto spesso e in quale ordine si aggiungono o rimuovono elementi. Ovviamente, tutti gli elementi dello stesso tipo (cioè all’interno dello stesso contenitore) saranno comunque soggetti all’ordine in cui sono stati aggiunti.

Dato che SVG non ha Z-index ma usa l’ordine degli elementi DOM, puoi portarlo in primo piano:

 this.parentNode.appendChild(this); 

È quindi ansible ad esempio utilizzare insertBefore per rimetterlo sul mouseout . Ciò tuttavia richiede che tu possa essere in grado di indirizzare il nodo fratello / sorella che il tuo elemento dovrebbe essere inserito prima.

DEMO: dai un’occhiata a questo JSFiddle

SVG non fa z-index. L’ordine Z è dettato dall’ordine degli elementi del DOM SVG nel loro contenitore.

Per quanto ho potuto vedere (e ho provato questo un paio di volte in passato), D3 non fornisce metodi per staccare e ricolbind un singolo elemento per portarlo in primo piano o cosa no.

Esiste un metodo .order() , che rimescola i nodes in modo che corrispondano all’ordine in cui appaiono nella selezione. Nel tuo caso, devi portare un singolo elemento in primo piano. Quindi, tecnicamente, potresti ricorrere alla selezione con l’elemento desiderato davanti (o alla fine, non ricordo quale sia il più in alto), e quindi chiamare l’ order() su di esso.

Oppure, puoi saltare d3 per questa attività e usare JS (o jQuery) normale per reinserire quel singolo elemento DOM.

Ho implementato la soluzione di futurend nel mio codice e ha funzionato, ma con il gran numero di elementi che stavo usando, è stato molto lento. Ecco il metodo alternativo che utilizza jQuery che ha funzionato più velocemente per la mia particolare visualizzazione. Si basa sullo svg che vuoi avere in cima una class in comune (nel mio esempio la class è annotata nel mio set di dati come d.key). Nel mio codice c’è un con la class “posizioni” che contiene tutti gli SVG che sto riorganizzando.

 .on("mouseover", function(d) { var pts = $("." + d.key).detach(); $(".locations").append(pts); }); 

Quindi, quando si passa su un particolare punto dati, il codice trova tutti gli altri punti dati con elementi SVG DOM con quella particolare class. Quindi scollega e reinserisce gli elementi DOM SVG associati a tali punti dati.

Volevo espandere ciò che @ notan3xit ha risposto piuttosto che scrivere una risposta completamente nuova (ma non ho abbastanza reputazione).

Un altro modo per risolvere il problema di ordine degli elementi consiste nell’utilizzare “insert” anziché “append” quando si disegna. In questo modo i percorsi saranno sempre collocati insieme prima degli altri elementi svg (questo presuppone che il tuo codice faccia già il comando enter () per i collegamenti prima di enter () per gli altri elementi svg).

d3 insert api: https://github.com/mbostock/d3/wiki/Selections#insert

La semplice risposta è usare i metodi di ordinazione d3. Oltre a d3.select (‘g’). Order (), c’è .lower () e .raise () nella versione 4. Questo cambia il modo in cui appaiono i tuoi elementi. Si prega di consultare i documenti per maggiori informazioni – https://github.com/d3/d3/blob/master/API.md#selections-d3-selection

Mi ci sono voluti anni per trovare come modificare l’ordine Z in un SVG esistente. Ne avevo davvero bisogno nel contesto di d3.brush con il comportamento del tooltip. Per fare in modo che le due funzioni funzionino bene insieme ( http://wrobstory.github.io/2013/11/D3-brush-and-tooltip.html ), è necessario che d3.brush sia il primo in ordine Z (Il primo deve essere disegnato sulla canvas, quindi coperto dal resto degli elementi SVG) e catturerà tutti gli eventi del mouse, indipendentemente da cosa ci sia sopra (con indici Z più alti).

La maggior parte dei commenti del forum dice che dovresti aggiungere prima d3.brush nel tuo codice, quindi il tuo codice “disegno” SVG. Ma per me non è stato ansible perché ho caricato un file SVG esterno. Puoi facilmente aggiungere il pennello in qualsiasi momento e modificare l’ordine Z in un secondo momento con:

 d3.select("svg").insert("g", ":first-child"); 

Nel contesto di una configurazione di d3.brush, sarà simile a:

 brush = d3.svg.brush() .x(d3.scale.identity().domain([1, width-1])) .y(d3.scale.identity().domain([1, height-1])) .clamp([true,true]) .on("brush", function() { var extent = d3.event.target.extent(); ... }); d3.select("svg").insert("g", ":first-child"); .attr("class", "brush") .call(brush); 

API della funzione d3.js insert (): https://github.com/mbostock/d3/wiki/Selections#insert

Spero che questo ti aiuti!

Versione 1

In teoria, il seguente dovrebbe funzionare bene.

Il codice CSS:

 path:hover { stroke: #fff; stroke-width : 2; } 

Questo codice CSS aggiungerà un tratto al percorso selezionato.

Il codice JS:

 svg.selectAll("path").on("mouseover", function(d) { this.parentNode.appendChild(this); }); 

Questo codice JS rimuove dapprima il percorso dall’albero DOM e quindi lo aggiunge come ultimo figlio del suo genitore. Questo assicura che il percorso sia disegnato sopra tutti gli altri figli dello stesso genitore.

In pratica, questo codice funziona bene in Chrome ma si interrompe in alcuni altri browser. L’ho provato in Firefox 20 sulla mia macchina Linux Mint e non riuscivo a farlo funzionare. In qualche modo, Firefox non riesce a triggersre gli stili :hover e non ho trovato un modo per risolvere questo problema.


Versione 2

Quindi ho trovato un’alternativa. Potrebbe essere un po ” sporco ‘, ma almeno funziona e non richiede il loop su tutti gli elementi (come alcune delle altre risposte).

Il codice CSS:

 path.hover { stroke: #fff; stroke-width : 2; } 

Invece di usare :hover pseudoselector, io uso una class .hover

Il codice JS:

 svg.selectAll(".path") .on("mouseover", function(d) { d3.select(this).classd('hover', true); this.parentNode.appendChild(this); }) .on("mouseout", function(d) { d3.select(this).classd('hover', false); }) 

Al .hover del .hover , aggiungo la class .hover al mio percorso. Al passaggio del mouse, lo rimuovo. Come nel primo caso, il codice rimuove anche il percorso dall’albero DOM e quindi lo aggiunge come ultimo figlio del suo genitore.

Puoi farlo così sopra al mouse Puoi tirarlo verso l’alto.

 d3.selection.prototype.bringElementAsTopLayer = function() { return this.each(function(){ this.parentNode.appendChild(this); }); }; d3.selection.prototype.pushElementAsBackLayer = function() { return this.each(function() { var firstChild = this.parentNode.firstChild; if (firstChild) { this.parentNode.insertBefore(this, firstChild); } }); 

};

 nodes.on("mouseover",function(){ d3.select(this).bringElementAsTopLayer(); }); 

Se vuoi spingere indietro

 nodes.on("mouseout",function(){ d3.select(this).pushElementAsBackLayer(); });