web-dev-qa-db-fra.com

d3.select (this) fonctionne avec la souris, mais pas avec la fonction appelée dans la souris

Je suis nouveau sur javascript et je ne parviens pas à sélectionner l'objet this tout en essayant de faire une sélection d3. J'ai créé l'exemple suivant, avec une fonction que j'appelle et un événement on mousemove:

function changeFont() {
  d3.select(this)
    .attr('font-size', '2em')
}

...
.on('mousemove', function() {
  var mouse = d3.mouse(this);
  var xVal = mouse[0];

  // this would work, but not when its called in a function
  // d3.select(this)
  //  .attr('font-size', '2em')

  // this works
  d3.select(this)
   .attr("opacity", 1)

  // this doesnt
  changeFont()
});

Dans mon script principal non présenté ici, j'organise mon code en écrivant des fonctions qui gèrent chacun des effets mousemove, mouseover, etc. Cependant, à cause de ces fonctions, je rencontre ce problème où je ne peux pas faire d3.select (this) à l'intérieur de cette fonction mouseover ... Avez-vous une idée de ce que je devrais faire différemment? 

Devrais-je passer this en tant que paramètre de ma fonction changeFont ()? Ou devrais-je accéder à this d'une manière différente?

Merci! 

7
Canovice

Bien qu'Andrew answer soit peut-être la meilleure solution si vous prenez la question à la lettre, je voudrais y ajouter mes deux sous. Votre vrai problème ne semble pas être d'obtenir this, mais d'avoir plusieurs fois accès à cet élément pour vous appliquer des manipulations. Étant donné que jongler avec this peut être un problème en JavaScript, il peut être intéressant d’adopter une approche légèrement différente en passant directement la sélection. Cela améliorera également les performances, car il n’est pas nécessaire de sélectionner à nouveau this encore et encore.

Tout d’abord, modifions légèrement votre fonction changeFont() pour accepter un objet de sélection.

function changeFont(selection) {
  selection
    .attr('font-size', '2em');
}

Remarquez comment cela rend la fonction plus généralement applicable car elle ne fait aucune hypothèse sur la sélection transmise. Ce peut être votre d3.select(this), une sélection contenant plusieurs éléments ou tout autre objet de sélection D3. En outre, vous n'avez pas besoin de conserver la précédente étendue this.

Il y a fondamentalement deux manières d'appeler cette fonction.

  1. L'évident passera directement la sélection en tant qu'argument lors de l'appel de la fonction:

    const d3This = d3.select(this);
    changeFont(d3This);
    
  2. Heureusement, il existe un moyen plus élégant de le faire en ayant recours à la fonction selection.call() de D3, qui permet même d’enchaîner les méthodes si vous devez faire plusieurs appels sur la même sélection.

    function changeFont(selection) { selection.attr("font-size", "2em"); }
    function changeFill(selection) { selection.attr("fill", "limegreen"); }
    function changeOpacity(selection) { selection.attr("opacity", "0.1"); }
    
    // ...
    .on("mouseover", function() {
      // Call the functions for this element.
      d3.select(this)
        .call(changeFont)
        .call(changeFill)
        .call(changeOpacity);
    
      // Instead, you could also apply the same pattern to all texts.
      d3.selectAll("text")
        .call(changeFont)
        .call(changeFill)
        .call(changeOpacity);
    
    }
    
9
altocumulus

Voyons ce que this est défini pour chacune de vos approches:

// console.log(this) in inline function:
<svg width="960" height="960"> 

// console.log(this) in function called from inline function:
Window → file:///fileName.html

this est défini par la manière dont une fonction est appelée. D3 définit commodément this comme étant l'élément DOM manipulé à l'aide de .apply sur la fonction passée à selection.attr(), selection.on() etc. Toutefois, cela n'est pas le cas pour les fonctions appelées dans la fonction passée à selection.attr(), selection.on(), etc.

Nous pouvons voir que this est bien l'élément DOM si nous enregistrons this dans la fonction passée à selection.on(). Si this n'est pas explicitement défini, ce sera la fenêtre (sauf si vous utilisez le mode strict, alors ce sera indéfini). Nous pouvons voir dans la fonction imbriquée, this est bien la fenêtre.

answer d'Altocumulus et answer de Gerardo pour éviter le problème this, vous pouvez également passer this en tant qu'argument régulier à la fonction (ce modèle est visible dans certains exemples). Mais si vous voulez simplement pouvoir copier et coller le code de la fonction inline vers une fonction définie séparément, vous pouvez utiliser apply, ce qui préservera this en tant qu'élément en cours de modification:

d3.select("body")
  .append("svg")
  .attr("width", 960)
  .attr("height", 960)
  .on('mousemove', function() {
     var mouse = d3.mouse(this);
     console.log("inline: ", mouse[0]);
     someFunction.apply(this);
});

function someFunction() {
  var mouse = d3.mouse(this);
  console.log("not inline: ", mouse[0]);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Si vous deviez transmettre des paramètres à votre fonction avec cela, vous pouvez toujours utiliser apply:

someFunction.apply(this,[parameterA,parameterB,...]);
function someFunction(parameterA,parameterB) { }

d3.select("body")
  .append("svg")
  .attr("width", 960)
  .attr("height", 960)
  .on('mousemove', function() {
     var mouse = d3.mouse(this);
     console.log("inline: ", mouse[0]);
     someFunction.apply(this,[mouse[0],mouse[1]]);
});

function someFunction(x,y) {
  var mouse = d3.mouse(this);
  console.log("not inline: ", mouse[0],x,y);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Cependant, cette fonction en ligne appelant d'autres fonctions peut n'être qu'un travail supplémentaire. Si vous appelez une fonction de la fonction inline avec only , il suffit alors de transmettre directement la fonction appelée à selection.on(), ce qui préserve this sans aucune étape supplémentaire, car d3 lui applique la valeur attendue (il vous donne également accès à la donnée et à l’index si nécessaire):

d3.select("body")
  .append("svg")
  .attr("width", 960)
  .attr("height", 960)
  .on('mousemove', someFunction)

function someFunction() {
  var mouse = d3.mouse(this);
  console.log(mouse[0]);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Ne placez pas les crochets sur la fonction dans ce cas, nous ne voulons pas renvoyer le résultat de la fonction, nous voulons utiliser la fonction elle-même.


J'ai utilisé apply (Function.prototype.apply()) dans mes exemples, mais vous pouvez également utiliser call (Function.prototype.call()), comme Altocumulus note ci-dessous . L'utilisation de call est assez similaire. Si vous ne transmettez aucun paramètre à la fonction et souhaitez uniquement conserver this, l'utilisation est la même: someFunction.apply(this)/someFunction.call(this). Mais, si les paramètres sont passés, call n'utilise pas de tableau pour les paramètres: 

d3.select("body")
  .append("svg")
  .attr("width", 960)
  .attr("height", 960)
  .on('mousemove', function() {
     var mouse = d3.mouse(this);
     someFunction.call(this,mouse[0],mouse[1]); // first parameter will be `this` for someFunction, rest of parameters follow
});

function someFunction(x,y) {
  var mouse = d3.mouse(this);
  console.log(mouse[0],x,y);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

6
Andrew Reid

Pour compléter, puisque cette question a déjà deux très bonnes réponses: vous pouvez éviter la confusion avec this si vous utilisez les troisième et deuxième arguments combinés. C'est quelque chose que même les développeurs D3 finissent par oublier.

Dans plusieurs méthodes D3, l'élément DOM actuel n'est que l'index actuel du groupe de nœuds. Donc, dans la fonction anonyme ...

.on("mouseover", function(_, i, n) {

... this est juste n[i], que vous pouvez simplement transmettre aux autres fonctions. Le _ correspond ici au premier argument, la donnée: j'utilise _ juste pour suivre la convention qui montre que cet argument n'est pas utilisé.

La bonne chose à propos de cette approche est que vous pouvez même (pour quelque raison que vous ayez) utiliser les fonctions de flèche:

d3.select("body").selectAll(null)
  .data(["foo", "bar", "baz"])
  .enter()
  .append("p")
  .text(String)
  .on("mouseover", (_, i, n) => {
    changeFont(n[i])
  });

function changeFont(element) {
  d3.select(element).style("font-size", "2em")
}
<script src="https://d3js.org/d3.v5.min.js"></script>

Bien sûr, vous ne pouvez pas obtenir l’élément DOM en utilisant this dans la fonction de flèche.

3
Gerardo Furtado