Aktualisieren des SVG-Element-Z-Index mit D3

80

Was ist ein effektiver Weg, um ein SVG-Element mithilfe der D3-Bibliothek an die Spitze der Z-Ordnung zu bringen?

Mein spezielles Szenario ist ein Kreisdiagramm, das hervorhebt (durch Hinzufügen eines strokezu path), wenn sich die Maus über einem bestimmten Teil befindet. Der Codeblock zum Generieren meines Diagramms ist unten:

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)
            .classed("top", true);
            //.style("z-index", 1);
    })
    .on("mouseout", function(d) {
        d3.select(this)
            .attr("stroke-width", 0)
            .classed("top", false);
            //.style("z-index", -1);
    });

Ich habe ein paar Optionen ausprobiert, aber bisher kein Glück. Mit style("z-index")und ruft classedbeide nicht funktioniert hat .

Die "Top" -Klasse ist in meinem CSS wie folgt definiert:

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

Die fillAussage soll sicherstellen, dass ich wusste, dass sie richtig ein- und ausgeschaltet wurde. Es ist.

Ich habe gehört, dass die Verwendung sorteine Option ist, aber ich bin mir nicht sicher, wie sie implementiert werden soll, um das "ausgewählte" Element nach oben zu bringen.

AKTUALISIEREN:

Ich habe meine spezielle Situation mit dem folgenden Code behoben, der der SVG des mouseoverEreignisses einen neuen Bogen hinzufügt , um ein Highlight anzuzeigen.

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();
    });
Böser Schrankaffe
quelle

Antworten:

52

Eine der vom Entwickler vorgestellten Lösungen lautet: "Verwenden Sie den Sortieroperator von D3, um die Elemente neu zu ordnen." (siehe https://github.com/mbostock/d3/issues/252 )

In diesem Licht könnte man die Elemente sortieren, indem man ihre Daten oder Positionen vergleicht, wenn sie datenlose Elemente wären:

.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
  });
})
Zukunft
quelle
Dies funktioniert nicht nur in der Z-Reihenfolge, sondern stört auch kein Ziehen, das gerade mit dem Erhöhen des Elements begonnen hat.
Oliver Bock
3
es funktioniert auch nicht bei mir. a, b scheint nur die Daten zu sein, nicht das d3-Auswahlelement oder das DOM-Element
nkint
In der Tat, wie bei @nkint, hat es beim Vergleichen a.idund nicht funktioniert d.id. Die Lösung besteht darin, aund direkt zu vergleichen d.
Frieden macht reichlich
Dies ist für eine große Anzahl von untergeordneten Elementen nicht ausreichend. Es ist besser, das erhabene Element in einem nachfolgenden Element erneut zu rendern <g>.
Tribalvibes
1
Alternativ kann das svg:useElement dynamisch auf das erhabene Element zeigen. Eine Lösung finden Sie unter stackoverflow.com/a/6289809/292085 .
Tribalvibes
91

Wie in den anderen Antworten erläutert, hat SVG keine Vorstellung von einem Z-Index. Stattdessen bestimmt die Reihenfolge der Elemente im Dokument die Reihenfolge in der Zeichnung.

Abgesehen von der manuellen Neuanordnung der Elemente gibt es für bestimmte Situationen eine andere Möglichkeit:

Bei der Arbeit mit D3 gibt es häufig bestimmte Elementtypen, die immer über anderen Elementtypen gezeichnet werden sollten .

Beispielsweise sollten beim Erstellen von Diagrammen Verknüpfungen immer unter Knoten platziert werden. Im Allgemeinen müssen einige Hintergrundelemente normalerweise unter allem anderen platziert werden, während einige Hervorhebungen und Überlagerungen darüber platziert werden sollten.

Wenn Sie in einer solchen Situation sind, habe ich festgestellt, dass das Erstellen übergeordneter Gruppenelemente für diese Elementgruppen der beste Weg ist. In SVG können Sie das gElement dafür verwenden. Wenn Sie beispielsweise Links haben, die immer unter Knoten platziert werden sollten, gehen Sie wie folgt vor:

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

Wenn Sie nun Ihre Links und Knoten malen, wählen Sie Folgendes aus (die Selektoren beginnen mit der #Referenz der Element-ID):

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

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

Jetzt werden alle Links immer strukturell vor allen Knotenelementen angehängt. Somit zeigt die SVG alle Links unter allen Knoten an, egal wie oft und in welcher Reihenfolge Sie Elemente hinzufügen oder entfernen. Natürlich unterliegen alle Elemente desselben Typs (dh innerhalb desselben Containers) weiterhin der Reihenfolge, in der sie hinzugefügt wurden.

notan3xit
quelle
1
Ich stieß darauf mit der Idee im Kopf, dass ich einen Punkt nach oben verschieben könnte, wenn mein eigentliches Problem darin bestand, meine Elemente in Gruppen zu organisieren. Wenn Ihr "echtes" Problem darin besteht, einzelne Elemente zu verschieben, ist die in der vorherigen Antwort beschriebene Sortiermethode ein guter Weg.
John David Five
Genial! Danke, notan3xit, du hast meinen Tag gerettet! Nur um eine Idee für andere hinzuzufügen - Wenn Sie Elemente in einer anderen Reihenfolge als der gewünschten Sichtbarkeitsreihenfolge hinzufügen müssen, können Sie einfach Gruppen in der richtigen Sichtbarkeitsreihenfolge erstellen (noch bevor Sie ihnen Elemente hinzufügen) und dann Elemente an diese anhängen Gruppen in beliebiger Reihenfolge.
Olga Gnatenko
1
Dies ist der richtige Ansatz. Offensichtlich, wenn Sie einmal daran denken.
Dave
Für mich musste svg.select ('# links') ... durch d3.select ('# links') ... ersetzt werden, um zu funktionieren. Vielleicht hilft das anderen.
Austausch
26

Da SVG keinen Z-Index hat, sondern die Reihenfolge der DOM-Elemente verwendet, können Sie ihn nach vorne bringen, indem Sie:

this.parentNode.appendChild(this);

Sie können dann zB insertBefore verwenden , um es wieder anzulegenmouseout . Dies setzt jedoch voraus, dass Sie in der Lage sind, den Geschwisterknoten anzuvisieren, in den Ihr Element zuvor eingefügt werden soll.

DEMO: Sehen Sie sich diese JSFiddle an

Swenedo
quelle
Dies wäre eine großartige Lösung, wenn Firefox keine :hoverStile rendern könnte . Ich denke auch nicht, dass die Einfügefunktion am Ende für diesen Anwendungsfall erforderlich wäre.
John Slegers
@swenedo, Ihre Lösung funktioniert hervorragend für mich und bringt das Element in den Vordergrund. Um es nach hinten zu bringen, bin ich ein bisschen gestapelt und weiß nicht, wie ich es richtig schreiben soll. Können Sie bitte ein Beispiel posten, wie Sie das Objekt nach hinten bringen können? Vielen Dank.
Mike B.
1
@ MikeB. Klar, ich habe gerade meine Antwort aktualisiert. Ich erkannte, dass die insertFunktion von d3 wahrscheinlich nicht der beste Weg ist, weil sie ein neues Element erzeugt. Wir möchten nur ein vorhandenes Element verschieben, daher verwende ich insertBeforestattdessen in diesem Beispiel.
Swenedo
Ich habe versucht, diese Methode zu verwenden, aber leider arbeite ich nicht in IE11. Haben Sie eine Problemumgehung für diesen Browser?
Antonio Giovanni Schiavone
13

SVG führt keinen Z-Index durch. Die Z-Reihenfolge wird durch die Reihenfolge der SVG-DOM-Elemente in ihrem Container bestimmt.

Soweit ich das beurteilen konnte (und ich habe dies in der Vergangenheit einige Male versucht), bietet D3 keine Methoden zum Trennen und erneuten Anbringen eines einzelnen Elements, um es nach vorne zu bringen oder so weiter.

Es gibt eine .order()Methode , mit der die Knoten neu gemischt werden, um der Reihenfolge zu entsprechen, in der sie in der Auswahl angezeigt werden. In Ihrem Fall müssen Sie ein einzelnes Element nach vorne bringen. Technisch gesehen können Sie also die Auswahl mit dem gewünschten Element im Vordergrund neu positionieren (oder sich am Ende nicht erinnern, welches das oberste ist) und es dann aufrufen order().

Sie können auch d3 für diese Aufgabe überspringen und einfaches JS (oder jQuery) verwenden, um das einzelne DOM-Element erneut einzufügen.

Meetamit
quelle
5
Beachten Sie, dass es in einigen Browsern Probleme beim Entfernen / Einfügen von Elementen in einem Mouseover-Handler gibt. Dies kann dazu führen, dass Mouseout-Ereignisse nicht ordnungsgemäß gesendet werden. Ich würde daher empfehlen, dies in allen Browsern zu versuchen, um sicherzustellen, dass es wie erwartet funktioniert.
Erik Dahlström
Würde eine Änderung der Reihenfolge nicht auch das Layout meines Kreisdiagramms ändern? Ich beschäftige mich auch mit jQuery, um zu lernen, wie man Elemente wieder einfügt.
Evil Closet Monkey
Ich habe meine Frage mit meiner ausgewählten Lösung aktualisiert, bei der der Kern der ursprünglichen Frage nicht verwendet wird. Wenn Sie etwas wirklich Schlechtes an dieser Lösung sehen, freue ich mich über Kommentare! Vielen Dank für den Einblick in die ursprüngliche Frage.
Evil Closet Monkey
Scheint ein vernünftiger Ansatz zu sein, hauptsächlich dank der Tatsache, dass die überlagerte Form keine Füllung hat. Wenn ja, würde ich erwarten, dass es das Mausereignis "stiehlt", sobald es erscheint. Und ich denke, @ ErikDahlströms Punkt bleibt bestehen: Dies könnte in einigen Browsern (hauptsächlich in IE9) immer noch ein Problem sein. Für zusätzliche "Sicherheit" können Sie .style('pointer-events', 'none')den überlagerten Pfad hinzufügen . Leider macht diese Eigenschaft nichts im IE, aber immer noch ....
Treffen
@EvilClosetMonkey Hi ... Diese Frage wird oft aufgerufen, und ich glaube, dass die Antwort von futurend unten hilfreicher ist als meine. Überlegen Sie sich also vielleicht, ob Sie die akzeptierte Antwort in die der Zukunft ändern möchten, damit sie höher erscheint.
Treffen
4

Ich habe die Lösung von futurend in meinen Code implementiert und es hat funktioniert, aber mit der großen Anzahl von Elementen, die ich verwendet habe, war es sehr langsam. Hier ist die alternative Methode mit jQuery, die für meine spezielle Visualisierung schneller funktioniert hat. Es hängt davon ab, ob die gewünschten SVGs eine gemeinsame Klasse haben (in meinem Beispiel wird die Klasse in meinem Datensatz als d.key vermerkt). In meinem Code gibt es eine <g>mit der Klasse "Standorte", die alle SVGs enthält, die ich neu organisiere.

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

Wenn Sie also mit der Maus über einen bestimmten Datenpunkt fahren, findet der Code alle anderen Datenpunkte mit SVG-DOM-Elementen mit dieser bestimmten Klasse. Anschließend werden die diesen Datenpunkten zugeordneten SVG-DOM-Elemente entfernt und wieder eingefügt.

lk145
quelle
4

Wollte die Antwort von @ notan3xit erweitern, anstatt eine ganz neue Antwort zu schreiben (aber ich habe nicht genug Ruf).

Eine andere Möglichkeit, das Problem der Elementreihenfolge zu lösen, besteht darin, beim Zeichnen "Einfügen" anstelle von "Anhängen" zu verwenden. Auf diese Weise werden die Pfade immer zusammen vor den anderen SVG-Elementen platziert (dies setzt voraus, dass Ihr Code bereits die Eingabe () für Links vor der Eingabe () für die anderen SVG-Elemente ausführt).

d3 API einfügen: https://github.com/mbostock/d3/wiki/Selections#insert

Ali
quelle
1

Ich habe ewig gebraucht, um herauszufinden, wie ich die Z-Reihenfolge in einem vorhandenen SVG optimieren kann. Ich brauchte es wirklich im Kontext von d3.brush mit Tooltip-Verhalten. Damit die beiden Funktionen gut zusammenarbeiten ( http://wrobstory.github.io/2013/11/D3-brush-and-tooltip.html ), muss der d3.brush der erste in Z-Reihenfolge sein (1. wird auf die Leinwand gezeichnet und dann von den restlichen SVG-Elementen abgedeckt) und erfasst alle Mausereignisse, unabhängig davon, was sich darüber befindet (mit höheren Z-Indizes).

Die meisten Forumkommentare besagen, dass Sie zuerst den d3.brush in Ihren Code und dann Ihren SVG-Zeichnungscode einfügen sollten. Für mich war dies jedoch nicht möglich, da ich eine externe SVG-Datei geladen habe. Sie können den Pinsel jederzeit einfach hinzufügen und die Z-Reihenfolge später ändern mit:

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

Im Kontext eines d3.brush-Setups sieht es so aus:

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

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

Hoffe das hilft!

mskr182
quelle
0

Version 1

Theoretisch sollte das Folgende gut funktionieren.

Der CSS-Code:

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

Dieser CSS-Code fügt dem ausgewählten Pfad einen Strich hinzu.

Der JS-Code:

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

Dieser JS-Code entfernt zuerst den Pfad aus dem DOM-Baum und fügt ihn dann als letztes untergeordnetes Element seines übergeordneten Elements hinzu. Dadurch wird sichergestellt, dass der Pfad über allen anderen Kindern desselben Elternteils gezeichnet wird.

In der Praxis funktioniert dieser Code in Chrome einwandfrei, in einigen anderen Browsern jedoch nicht. Ich habe es in Firefox 20 auf meinem Linux Mint-Computer versucht und konnte es nicht zum Laufen bringen. Irgendwie kann Firefox die :hoverStile nicht auslösen , und ich habe keinen Weg gefunden, dies zu beheben.


Version 2

Also habe ich mir eine Alternative ausgedacht. Es mag ein bisschen "schmutzig" sein, aber zumindest funktioniert es und es erfordert nicht das Schleifen aller Elemente (wie einige der anderen Antworten).

Der CSS-Code:

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

Anstatt den :hoverPseudoselektor zu verwenden, verwende ich eine .hoverKlasse

Der JS-Code:

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

Beim Mouseover füge ich die .hoverKlasse meinem Pfad hinzu. Beim Mouseout entferne ich es. Wie im ersten Fall entfernt der Code auch den Pfad aus dem DOM-Baum und fügt ihn dann als letztes untergeordnetes Element seines übergeordneten Elements hinzu.

John Slegers
quelle
0

Sie können dies bei Mauszeiger tun. Sie können es nach oben ziehen.

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

Wenn Sie nach hinten drücken möchten

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