web-dev-qa-db-fra.com

Comment obtenir les coordonnées absolues d'un objet à l'intérieur d'un groupe <g>?

Cela peut être une FAQ, alors n'hésitez pas à me diriger vers une autre réponse. Le sujet est difficile à rechercher.

Si je veux utiliser d3.js pour obtenir un attribut qui est explicitement déclaré dans un objet SVG, ou que j'y ai explicitement mis en utilisant D3, je peux facilement obtenir la valeur de l'attribut en utilisant d3.select. Par exemple, cela imprime 300:

...
<circle id="mycircle" r="10" cx="100" cy="200">
...
d3.select("#mycircle").attr("cx", 300);
console.log(d3.select("#mycircle").attr("cx"));

Que se passe-t-il si je ne définis pas explicitement la valeur de l'attribut, mais qu'elle est implicitement "définie" à partir d'un <g> groupe? Ou: comment utiliser le code pour savoir où un <g> le groupe est centré? Je voudrais un moyen de déterminer où dans le système de coordonnées absolu du <svg> objecter les choses à l'intérieur du <g> sont. Si je savais où le <g> était, comment il est orienté dans l'espace, etc., je pouvais comprendre où se trouvent les points à l'intérieur. Comment puis je faire ça?

BigBadaboom remarque dans un commentaire sur une réponse à cette question que ce qui est hérité n'est pas une paire de coordonnées, mais un attribut transform. Je peux donc sélectionner un <g> et obtenez la valeur de l'attribut transform:

console.log(d3.select("#mygroup").attr("transform"));

qui imprime, par exemple:

"rotation (-125,93) traduction (0, -25)"

Dois-je analyser cela pour savoir où le <g> est situé dans le système de coordonnées absolu?

18
Mars

D'autres ici ont déjà mentionné SVGLocatable.getBBox() qui est utile pour saisir la boîte englobante d'un élément en termes de son propre système de coordonnées local. Malheureusement, comme vous l'avez remarqué, cela ne prend en compte aucune des transformations effectuées sur l'élément ou sur ses éléments parents.

Il y a quelques autres fonctions disponibles qui vous aideront une tonne lors de ces transformations.

SVGLocatable.getScreenCTM() vous donne un SVGMatrix représentant les transformations nécessaires pour convertir des coordonnées de la fenêtre aux coordonnées locales de votre élément. C'est génial car il prendra en compte les transformations appliquées à l'élément auquel il est appelé et toutes les transformations appliquées aux éléments parents. Malheureusement, il prend également en compte l'emplacement exact de l'élément à l'écran, ce qui signifie que si vous avez du contenu avant votre document svg, ou même juste quelques marges autour, la matrice renvoyée inclura cet espace comme traduction.

Element.getBoundingClientRect() vous permettra de prendre en compte cet espace. Si vous appelez cette fonction sur le document SVG lui-même, vous pouvez découvrir combien le SVG est décalé à l'écran.

Ensuite, tout ce que vous avez à faire est de combiner les deux lorsque vous souhaitez convertir entre les systèmes de coordonnées. [~ # ~] ici [~ # ~] est quelques bonnes informations sur le fonctionnement d'un SVGMatrix . La chose importante à savoir pour le moment est qu'un SVGMatrix est un objet avec six propriétés a, b, c, d, e et f qui représentent une transformation comme suit:

svg matrix equations

Disons que vous avez une variable svgDoc qui est une référence au document svg ( pas une sélection d3, mais l'élément lui-même). Ensuite, vous pouvez créer une fonction qui se convertira au système de coordonnées d'un élément svg elem comme suit.

function convertCoords(x,y) {

  var offset = svgDoc.getBoundingClientRect();

  var matrix = elem.getScreenCTM();

  return {
    x: (matrix.a * x) + (matrix.c * y) + matrix.e - offset.left,
    y: (matrix.b * x) + (matrix.d * y) + matrix.f - offset.top
  };
}

Supposons ensuite que vous vouliez placer un point au milieu de elem, vous pouvez faire quelque chose comme ceci:

var bbox = elem.getBBox(),
    middleX = bbox.x + (bbox.width / 2),
    middleY = bbox.y + (bbox.height / 2);

var absoluteCoords = convertCoords(middleX, middleY);

var dot = svg.append('circle')
  .attr('cx', absoluteCoords.x)
  .attr('cy', absoluteCoords.y)
  .attr('r', 5);

Bien sûr, vous voudrez probablement généraliser la fonction convertCoords afin de pouvoir passer dans l'élément cible, mais j'espère que cela vous fera avancer dans la bonne direction. Bonne chance!

Une meilleure implémentation serait une fabrique qui génère une fonction de conversion pour tout élément et contexte de document svg donné:

function makeAbsoluteContext(element, svgDocument) {
  return function(x,y) {
    var offset = svgDocument.getBoundingClientRect();
    var matrix = element.getScreenCTM();
    return {
      x: (matrix.a * x) + (matrix.c * y) + matrix.e - offset.left,
      y: (matrix.b * x) + (matrix.d * y) + matrix.f - offset.top
    };
  };
}

Cela pourrait être utilisé comme suit étant donné les mêmes elem et svgDoc que l'exemple naïf:

var bbox = elem.getBBox(),
    middleX = bbox.x + (bbox.width / 2),
    middleY = bbox.y + (bbox.height / 2);

// generate a conversion function
var convert = makeAbsoluteContext(elem, svgDoc);

// use it to calculate the absolute center of the element
var absoluteCenter = convert(middleX, middleY);

var dot = svg.append('circle')
  .attr('cx', absoluteCenter.x)
  .attr('cy', absoluteCenter.y)
  .attr('r', 5);
36
jshanley

D3 a une fonction intégrée pour analyser les transformations svg: d3.transform

Vous pouvez l'utiliser pour obtenir le tableau de traduction ([x, y]) de la transformation, c'est-à-dire:

var transformText = d3.select("#mygroup").attr("transform");
var translate = d3.transform(transformText).translate;  //returns [0,-25]
8
Josh

Pour obtenir les limites d'un élément SVG, vous avez deux options:

  1. getBBox() qui fonctionne sur tous les éléments (graphiques) SVG. Il obtient la boîte englobante de l'élément dans l'espace de coordonnées local. Si l'élément a un attribut transform, cela affectera la bbox, mais si l'élément parent a un transform, il ne sera pas reflété dans la bbox retournée.

    http://www.w3.org/TR/SVG/types.html#InterfaceSVGLocatable

  2. getBoundingClientRect() qui est une fonction d'élément HTML, mais fonctionne également pour les éléments SVG. Il renvoie les limites de l'élément dans l'espace d'écran (après que toutes les transformations ont été appliquées).

    https://developer.mozilla.org/en-US/docs/Web/API/Element.getBoundingClientRect

6
Paul LeBeau

L'excellente réponse de @ Jshanley est en fait très facilement implémentée en JavaScript brut (ou n'importe quel framework) en utilisant la transformation matricielle de SVGPoint.

/**
* Get a new XY point in SVG-Space, where X and Y are relative to an existing element.  Useful for drawing lines between elements, for example

* X : the new X with relation to element, 5 would be '5' to the right of element's left boundary.  element.width would be the right Edge.
* Y : the new Y coordinate, same principle applies
* svg: the parent SVG DOM element
* element: the SVG element which we are using as a base point.
*/
function getRelativeXY(x, y, svg, element){
  var p = svg.createSVGPoint();
  var ctm = element.getCTM();
  p.x = x;
  p.y = y;
  return p.matrixTransform(ctm);
}

Voir aussi: coordonnées rectangulaires après transformation

Pour trouver les bords de votre cercle, par exemple:

var leftTangent = getRelativeXY(circle.cx-circle.r, circle.y, svg, circle);
var rightTangent = getRelativeXY(circle.cx+circle.r, circle.y, svg, circle);
var topTangent= getRelativeXY(circle.cx, circle.y-circle.r, svg, circle); 
var bottomTangent= getRelativeXY(circle.cx, circle.y+ circle.r, svg, circle);
var deadCenter= getRelativeXY(circle.cx, circle.y, svg, circle);

Certes, ce n'est pas intéressant avec un cercle simple, mais une fois que le cercle a été déplacé ou étiré, c'est un excellent outil pour obtenir les coordonnées.

Spécifications de W

le tutoriel de Microsoft plus facile à comprendre

6
InfernalRapture

Belle démo ici: https://codepen.io/netsi1964/pen/pWjwgP

point = point.matrixTransform(svg.getScreenCTM().inverse())
2