web-dev-qa-db-fra.com

Comment est calculée la getBBox () SVGRect?

J'ai un élément g qui contient un ou plusieurs éléments path. Comme je l'ai mentionné dans ne autre question , je redimensionne et traduit l'élément g en calculant un attribut transform pour qu'il tienne sur une grille dans une autre partie du canevas.

Le calcul se fait en utilisant la différence entre deux rectangles, la getBBox() de l'élément g et le rectangle autour de la grille.

Voici la question - après avoir effectué la transformation, je mets à jour le contenu de l'élément g et appelle à nouveau getBBox(), sans en supprimant le transform. Le rectangle résultant semble être calculé sans tenir compte du transform. Je m'attendais à ce qu'il reflète le changement. Ce comportement est-il conforme à la spécification SVG? Comment obtenir la boîte englobante du rectangle transformé?

Ceci, BTW, est dans un document HTML 5 fonctionnant dans Firefox 4, si cela fait une différence.

Mise à jour: Apparemment, ce comportement semble assez clairement en violation des spécifications. D'après le texte ici sur w3c :

SVGRect getBBox ()

Renvoie le cadre de délimitation serré dans l'espace utilisateur actuel (c'est-à-dire, après application de l'attribut de transformation, le cas échéant) sur la géométrie de tous les éléments graphiques contenus, à l'exclusion des effets de contour, de découpage, de masquage et de filtrage). Notez que getBBox doit retourner le cadre de délimitation réel au moment où la méthode a été appelée, même si l'élément n'a pas encore été rendu.

Suis-je en train de lire correctement? Si c'est le cas, cela semble être un errata dans l'implémentation SVG que Firefox utilise; Je n'ai pas eu l'occasion d'en essayer d'autre. Je déposerais un rapport de bogue si quelqu'un pouvait me diriger vers où.

38
AlanObject

Le comportement que vous voyez est correct et conforme à la spécification. La transformation est appliquée, puis la bbox est calculée en "unités utilisateur actuelles", c'est-à-dire l'espace utilisateur actuel. Donc, si vous voulez voir le résultat d'une transformation sur l'élément, vous devez regarder la bbox d'un nœud parent ou similaire. C'est un peu déroutant, mais expliqué beaucoup mieux dans la spécification SVG Tiny 1.2 pour SVGLocatable qui contient un certain nombre d'exemples qui clarifient ce qu'il est censé faire.

15
AlexDan

Les gens sont souvent confus par la différence de comportement de getBBox et getBoundingClientRect.

getBBox est une méthode native d'un élément SVG comme équivalent pour trouver le décalage/largeur de client de l'élément HTML DOM. La largeur et la hauteur sont ne changeront jamais même lorsque l'élément est tourné . Il ne peut pas être utilisé pour les éléments DOM HTML.

getBoundingClientRect est commun aux éléments HTML et SVG. La largeur et la hauteur rectangle délimité changeront lorsque l'élément est tourné ou lorsque plusieurs éléments sont regroupés.

19
shibualexis

il y a au moins 2 façons faciles mais un peu hacky de faire ce que vous demandez ... s'il y a des façons plus agréables (moins hacky), je ne les ai pas encore trouvées

EASY HACKy # 1:
a) a mis en place un rect qui correspond à la bbox "non transformée" que group.getBBox () retourne
b) appliquer la "transformation non appliquée" du groupe à ce rect
c) rect.getBBox () devrait maintenant renvoyer la bbox que vous recherchez

EASY HACKY # 2: (testé uniquement en chrome)
a) utilisez element.getBoundingClientRect (), qui renvoie suffisamment d'informations pour que vous puissiez construire la bbox que vous recherchez

6
rmanna

Apparemment getBBox () ne prend pas en compte les transformations.

Je peux vous pointer ici, malheureusement je n'ai pas pu le faire fonctionner: http://tech.groups.yahoo.com/group/svg-developers/message/22891

4
fregante

Les groupes SVG ont une mauvaise pratique - de ne pas accumuler toutes les transformations effectuées. J'ai ma façon de faire face à ce problème. J'utilise mes propres attributs pour stocker les données de transformation actuelles que j'inclus dans toute autre transformation. Utilisez des attributs compatibles XML comme alttext, value, name .... ou simplement x et y pour stocker la valeur accumulée comme attribut.

Exemple:

<g id="group" x="20" y="100" transform="translate(20, 100)">
<g id="subgroup" alttext="45" transform="rotate(45)">
<line...etc...

Par conséquent, lorsque je fais des transformations je prends ces valeurs d'attributs faites à la main, et lorsque je les réécris, j'écris à la fois la transformation et la même valeur avec des attributs que j'ai créés juste pour garder toutes les valeurs accumulées.

Exemple de rotation:

function symbRot(evt) {

  evt.target.ondblclick = function () {

    stopBlur();
    var ptx=symbG.parentNode.lastChild.getAttribute("cx");
    var pty=symbG.parentNode.lastChild.getAttribute("cy");
    var currRot=symbG.getAttributeNS(null, "alttext");

    var rotAng;
    if (currRot == 0) {
      rotAng = 90
    } else if (currRot == 90) {
      rotAng = 180
    } else if (currRot == 180) {
      rotAng = 270
    } else if (currRot == 270) {
      rotAng = 0
    };
    symbG.setAttributeNS(null, "transform", "rotate(" + rotAng + "," + ptx + ", " + pty + ")");
    symbG.setAttributeNS(null, "alttext", rotAng );
  };
}
3
Alex

J'ai créé une fonction d'aide, qui renvoie diverses métriques de l'élément svg (également la bbox de l'élément transformé).

Le code est ici:

SVGElement.prototype.getTransformToElement = 
SVGElement.prototype.getTransformToElement || function(elem) { 
  return elem.getScreenCTM().inverse().multiply(this.getScreenCTM()); 
};

function get_metrics(el) {
    function pointToLineDist(A, B, P) {
        var nL = Math.sqrt((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y));
        return Math.abs((P.x - A.x) * (B.y - A.y) - (P.y - A.y) * (B.x - A.x)) / nL;
    }

    function dist(point1, point2) {
        var xs = 0,
            ys = 0;
        xs = point2.x - point1.x;
        xs = xs * xs;
        ys = point2.y - point1.y;
        ys = ys * ys;
        return Math.sqrt(xs + ys);
    }

    var b = el.getBBox(),
        objDOM = el,
        svgDOM = objDOM.ownerSVGElement;
    // Get the local to global matrix
    var matrix = svgDOM.getTransformToElement(objDOM).inverse(),
        oldp = [[b.x, b.y], [b.x + b.width, b.y], [b.x + b.width, b.y + b.height], [b.x, b.y + b.height]],
        pt, newp = [],
        obj = {},
        i, pos = Number.POSITIVE_INFINITY,
        neg = Number.NEGATIVE_INFINITY,
        minX = pos,
        minY = pos,
        maxX = neg,
        maxY = neg;

    for (i = 0; i < 4; i++) {
        pt = svgDOM.createSVGPoint();
        pt.x = oldp[i][0];
        pt.y = oldp[i][1];
        newp[i] = pt.matrixTransform(matrix);
        if (newp[i].x < minX) minX = newp[i].x;
        if (newp[i].y < minY) minY = newp[i].y;
        if (newp[i].x > maxX) maxX = newp[i].x;
        if (newp[i].y > maxY) maxY = newp[i].y;
    }

    // The next refers to the transformed object itself, not bbox
    // newp[0] - newp[3] are the transformed object's corner
    // points in clockwise order starting from top left corner
    obj.newp = newp; // array of corner points
    obj.width = pointToLineDist(newp[1], newp[2], newp[0]) || 0;
    obj.height = pointToLineDist(newp[2], newp[3], newp[0]) || 0;
    obj.toplen = dist(newp[0], newp[1]);
    obj.rightlen = dist(newp[1], newp[2]);
    obj.bottomlen = dist(newp[2], newp[3]);
    obj.leftlen = dist(newp[3], newp[0]);
    // The next refers to the transformed object's bounding box
    obj.BBx = minX;
    obj.BBy = minY;
    obj.BBx2 = maxX;
    obj.BBy2 = maxY;
    obj.BBwidth = maxX - minX;
    obj.BBheight = maxY - minY;
    return obj;
}

et un exemple fonctionnel complet est ici: http://jsbin.com/acowaq/1

3
Timo Kähkönen