web-dev-qa-db-fra.com

Mise à jour de l'indice Z des éléments SVG avec D3

Quel est un moyen efficace d'amener un élément SVG au sommet de l'ordre z, en utilisant la bibliothèque D3?

Mon scénario spécifique est un graphique à secteurs qui met en surbrillance (en ajoutant une stroke à la path) lorsque la souris survole un élément donné. Le bloc de code pour générer mon graphique est ci-dessous:

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

J'ai essayé quelques options, mais pas de chance jusqu'à présent. Utiliser style("z-index") et appeler classed ne fonctionnaient pas tous les deux.

La classe "top" est définie comme suit dans mon CSS:

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

La déclaration fill est là pour vous assurer que je savais qu’elle s’allumait/éteignait correctement. Il est.

J'ai entendu dire que sort est une option, mais je ne vois pas comment cela serait implémenté pour amener l'élément "sélectionné" au sommet.

METTRE À JOUR:

J'ai corrigé ma situation particulière avec le code suivant, qui ajoute un nouvel arc au SVG sur l'événement mouseover pour afficher une surbrillance.

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();
    });
67
Evil Closet Monkey

L'une des solutions présentées par le développeur est la suivante: "utilisez l'opérateur de tri de D3 pour réorganiser les éléments". (voir https://github.com/mbostock/d3/issues/252 )

Dans cette optique, on pourrait trier les éléments en comparant leurs données ou leurs positions s’il s’agissait d’éléments sans données:

.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
  });
})
46
futurend

Comme expliqué dans les autres réponses, SVG n'a pas la notion d'index z. Au lieu de cela, l'ordre des éléments dans le document détermine l'ordre dans le dessin.

En plus de réorganiser les éléments manuellement, il existe un autre moyen pour certaines situations:

En travaillant avec D3, vous avez souvent certains types d’éléments qui doivent toujours être superposés aux autres types d’éléments.

Par exemple, lors de la mise en forme de graphiques, les liens doivent toujours être placés sous les nœuds. Plus généralement, certains éléments d'arrière-plan doivent généralement être placés en dessous de tout le reste, tandis que certains points saillants et superpositions doivent être placés en haut.

Si vous rencontrez ce type de situation, j'ai constaté que la meilleure solution consiste à créer des éléments de groupe parent pour ces groupes d'éléments. En SVG, vous pouvez utiliser l'élément g pour cela. Par exemple, si vous avez des liens qui doivent toujours être placés sous les nœuds, procédez comme suit:

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

Maintenant, lorsque vous peignez vos liens et noeuds, sélectionnez comme suit (les sélecteurs commençant par # font référence à l'id d'élément):

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

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

Désormais, tous les liens seront toujours ajoutés structurellement avant tous les éléments de nœud. Ainsi, le SVG affiche tous les liens situés sous tous les nœuds, peu importe la fréquence et l'ordre dans lesquels vous ajoutez ou supprimez des éléments. Bien entendu, tous les éléments du même type (c’est-à-dire dans le même conteneur) seront toujours soumis à l’ordre dans lequel ils ont été ajoutés.

81
notan3xit

Comme SVG n'a pas d'index Z mais utilise l'ordre des éléments DOM, vous pouvez le mettre au premier plan en:

this.parentNode.appendChild(this);

Vous pouvez alors par exemple utilisez insertBefore pour le remettre sur mouseout. Cela nécessite toutefois que vous puissiez cibler le noeud frère de votre élément doit être inséré auparavant.

DEMO: Regardez ça JSFiddle

20
swenedo

SVG ne fait pas z-index. L'ordre Z est dicté par l'ordre des éléments DOM SVG dans leur conteneur.

Autant que je sache (et j'ai déjà essayé cela plusieurs fois dans le passé), D3 ne fournit pas de méthodes permettant de détacher et de rattacher un élément unique afin de l'amener au premier plan ou quoi. 

Il existe une méthode .order() qui remanie les nœuds pour qu'ils correspondent à l'ordre dans lequel ils apparaissent dans la sélection. Dans votre cas, vous devez mettre un seul élément au premier plan. Donc, techniquement, vous pouvez recourir à la sélection avec l'élément souhaité devant (ou à la fin, vous ne pouvez pas vous souvenir lequel est le plus haut), puis appeler order().

Vous pouvez également ignorer d3 pour cette tâche et utiliser JS (ou jQuery) pour réinsérer cet élément DOM unique.

13
meetamit

La réponse simple est d'utiliser les méthodes de commande d3. En plus de d3.select ('g'). Order (), il existe .lower () et .raise () dans la version 4. Cela change la présentation de vos éléments. Veuillez consulter la documentation pour plus d'informations - https://github.com/d3/d3/blob/master/API.md#selections-d3-selection

6
evanjmg

Voulait développer ce que @ notan3xit avait répondu plutôt que d'écrire une toute nouvelle réponse (mais je n'ai pas assez de réputation).

Une autre façon de résoudre le problème d'ordre des éléments consiste à utiliser "insérer" plutôt que "ajouter" lors du dessin. Ainsi, les chemins seront toujours placés ensemble avant les autres éléments svg (cela suppose que votre code effectue déjà enter () pour les liens avant enter () pour les autres éléments svg).

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

4
Ali

J'ai implémenté la solution de futurend dans mon code et cela a fonctionné, mais avec le grand nombre d'éléments que j'utilisais, c'était très lent. Voici la méthode alternative utilisant jQuery qui a fonctionné plus rapidement pour ma visualisation particulière. Cela repose sur les svgs que vous voulez avoir en haut d'une classe en commun (dans mon exemple, la classe est notée dans mon jeu de données comme d.key). Dans mon code, il y a un <g> avec la classe "emplacements" qui contient tous les SVG que je réorganise.

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

Ainsi, lorsque vous survolez un point de données particulier, le code recherche tous les autres points de données contenant des éléments SVG DOM avec cette classe particulière. Ensuite, il détache et réinsère les éléments SVG DOM associés à ces points de données.

4
lk145

Il m'a fallu beaucoup de temps pour trouver comment modifier l'ordre Z dans un SVG existant. J'en avais vraiment besoin dans le contexte de d3.brush avec le comportement des info-bulles. Pour que les deux fonctions fonctionnent bien (/ http://wrobstory.github.io/2013/11/D3-brush-and-tooltip.html ), vous devez utiliser le d3.brush pour être le premier. dans l'ordre Z (le premier à être dessiné sur la toile, puis recouvert par le reste des éléments SVG) et il capturera tous les événements de souris, peu importe ce qui se trouve dessus (avec des indices Z plus élevés).

La plupart des commentaires de forum disent que vous devriez d'abord ajouter le d3.brush dans votre code, puis votre code "de dessin" SVG. Mais pour moi, ce n'était pas possible car j'ai chargé un fichier SVG externe. Vous pouvez facilement ajouter le pinceau à tout moment et modifier l’ordre Z ultérieurement:

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

Dans le contexte d'une configuration d3.brush, cela ressemblera à:

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 de la fonction d3.js insert (): https://github.com/mbostock/d3/wiki/Selections#insert

J'espère que cela t'aides!

1
mskr182

Version 1

En théorie, ce qui suit devrait bien fonctionner.

Le code CSS:

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

Ce code CSS ajoutera un trait au chemin sélectionné.

Le code JS:

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

Ce code JS supprime d'abord le chemin de l'arborescence DOM, puis l'ajoute en tant que dernier enfant de son parent. Cela garantit que le chemin est tracé au-dessus de tous les autres enfants du même parent.

En pratique, ce code fonctionne correctement dans Chrome mais se rompt dans certains autres navigateurs. Je l'ai essayé dans Firefox 20 sur ma machine Linux Mint et je n'ai pas réussi à le faire fonctionner. D'une manière ou d'une autre, Firefox ne parvient pas à déclencher les styles :hover et je n'ai pas trouvé de moyen de résoudre ce problème.


Version 2

Alors je suis venu avec une alternative. C'est peut-être un peu "sale", mais au moins cela fonctionne et il ne nécessite pas de boucler tous les éléments (comme certaines des autres réponses).

Le code CSS:

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

Au lieu d'utiliser le pseudoselecteur :hover, j'utilise une classe .hover

Le code JS:

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

Au passage de la souris, j'ajoute la classe .hover à mon chemin. À la sortie de souris, je le supprime . Comme dans le premier cas, le code supprime également le chemin de l'arborescence DOM, puis l'ajoute au dernier enfant de son parent.

0
John Slegers

Vous pouvez faire comme ceci Sur la souris, vous pouvez le tirer en haut.

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

Si vous voulez pousser en arrière 

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