web-dev-qa-db-fra.com

Remplacement de d3.transform dans D3 v4

Dans D3.js v4, la méthode d3.transform a été supprimée, sans aucune indication sur la manière de la remplacer. Quelqu'un sait-il comment remplacer l'instruction D3.js v3 suivante?

d3.transform(String).translate;

Edit 2016-10-07: Pour une approche plus générale, voir addenda ci-dessous.


Selon le changelog, il est parti. Il existe cependant une fonction dans transform/decompose.js qui effectue les calculs pour un usage interne. Malheureusement, il n'est pas exposé pour un usage externe.

Cela dit, cela se fait facilement, même sans utiliser aucun D3:

function getTranslation(transform) {
  // Create a dummy g for calculation purposes only. This will never
  // be appended to the DOM and will be discarded once this function 
  // returns.
  var g = document.createElementNS("http://www.w3.org/2000/svg", "g");
  
  // Set the transform attribute to the provided string value.
  g.setAttributeNS(null, "transform", transform);
  
  // consolidate the SVGTransformList containing all transformations
  // to a single SVGTransform of type SVG_TRANSFORM_MATRIX and get
  // its SVGMatrix. 
  var matrix = g.transform.baseVal.consolidate().matrix;
  
  // As per definition values e and f are the ones for the translation.
  return [matrix.e, matrix.f];
}

console.log(getTranslation("translate(20,30)"))  // simple case: should return [20,30]
console.log(getTranslation("rotate(45) skewX(20) translate(20,30) translate(-5,40)"))

Cela crée un élément factice g à des fins de calcul à l'aide de méthodes DOM standard et définit son attribut transform sur la chaîne contenant vos transformations. Il appelle ensuite .consolidate() de l'interface SVGTransformList pour consolider la liste éventuellement longue de transformation en un seul SVGTransform de type SVG_TRANSFORM_MATRIX qui contient la version condensée de toutes les transformations de sa propriété matrix. Cette SVGMatrix per définition contient les valeurs de la traduction dans ses propriétés e et f.

En utilisant cette fonction getTranslation() vous pouvez réécrire votre déclaration D3 v3

d3.transform(transformString).translate;

comme

getTranslation(transformString);

Addenda

Cette réponse ayant suscité un certain intérêt au fil du temps, j'ai décidé de mettre au point une méthode plus générale capable de renvoyer non seulement la traduction, mais également les valeurs de toutes les définitions de transformation d'une chaîne de transformation. L'approche de base est la même que celle décrite dans mon message original ci-dessus, plus les calculs sont pris à partir de transform/decompose.js . Cette fonction retournera un objet ayant des propriétés pour toutes les définitions de transformation, un peu comme l'ancien d3.transform() did.

function getTransformation(transform) {
  // Create a dummy g for calculation purposes only. This will never
  // be appended to the DOM and will be discarded once this function 
  // returns.
  var g = document.createElementNS("http://www.w3.org/2000/svg", "g");
  
  // Set the transform attribute to the provided string value.
  g.setAttributeNS(null, "transform", transform);
  
  // consolidate the SVGTransformList containing all transformations
  // to a single SVGTransform of type SVG_TRANSFORM_MATRIX and get
  // its SVGMatrix. 
  var matrix = g.transform.baseVal.consolidate().matrix;
  
  // Below calculations are taken and adapted from the private function
  // transform/decompose.js of D3's module d3-interpolate.
  var {a, b, c, d, e, f} = matrix;   // ES6, if this doesn't work, use below assignment
  // var a=matrix.a, b=matrix.b, c=matrix.c, d=matrix.d, e=matrix.e, f=matrix.f; // ES5
  var scaleX, scaleY, skewX;
  if (scaleX = Math.sqrt(a * a + b * b)) a /= scaleX, b /= scaleX;
  if (skewX = a * c + b * d) c -= a * skewX, d -= b * skewX;
  if (scaleY = Math.sqrt(c * c + d * d)) c /= scaleY, d /= scaleY, skewX /= scaleY;
  if (a * d < b * c) a = -a, b = -b, skewX = -skewX, scaleX = -scaleX;
  return {
    translateX: e,
    translateY: f,
    rotate: Math.atan2(b, a) * 180 / Math.PI,
    skewX: Math.atan(skewX) * 180 / Math.PI,
    scaleX: scaleX,
    scaleY: scaleY
  };
}

console.log(getTransformation("translate(20,30)"));  
console.log(getTransformation("rotate(45) skewX(20) translate(20,30) translate(-5,40)"));

36
altocumulus

Si vous extrayez d3 v4 à npm, vous pouvez importer le fichier src/transform/parse directement et appeler parseSvg:

// using es2015 modules syntax
import { parseSvg } from "d3-interpolate/src/transform/parse";

parseSvg("translate(20, 20)");
4
Xiaohan Zhang

Sur les éléments portant l'écouteur d3.js zoom - généralement l'élément <g> ajouté à l'élément svg - vous pouvez utiliser cet appel pour obtenir les attributs de transformation en dehors de la fonction de zoom:

var self = this;
var t = d3.zoomTransform(self.svg.node()); 

// t = {k: 1, x: 0, y: 0} or your current transformation values

Cela renvoie les mêmes valeurs que lorsque vous appelez d3.event.transform dans la fonction d'événement de zoom elle-même. 

Si vous appelez d3.event.transform en dehors de la fonction d’événement de zoom, vous obtiendrez une erreur: Uncaught TypeError: Cannot read property 'transform' of null

Je dois utiliser d3.zoomTransform pour autoriser le panoramique et le zoom à partir de boutons extérieurs au graphique.

2
NuclearPeon

Je suis un peu en retard à la fête, mais j'avais un code qui m'a été bénéfique, j'espère qu'il vous aidera aussi.

Le code ci-dessus de @altocumulus est assez complet et fonctionne à merveille. Cependant, cela ne répondait pas tout à fait à mes besoins puisque je faisais les calculs à la main et que je devais modifier certaines propriétés de transformation aussi facilement que possible. 

Ce n'est peut-être pas la solution pour tout le monde, mais c'était parfait pour moi.

function _getTokenizedTransformAttributeValue(transformStr) {
    var cleanedUpTransformAttrArr = transformStr.split(')', ).slice(0,-1);

    return cleanedUpTransformAttrArr.reduce(function(retObj, item) { 
        var transformPair = item.split('(');

        retObj[transformPair[0]] = transformPair[1].split(',');

        return retObj;
    }, {});
}

function _getStringFromTokenizedTransformAttributeObj(transformAttributeObj) {
    return _.keys(transformAttributeObj).reduce(function(finalStr, key) {
        // wrap the transformAttributeObj[key] in array brackets to ensure we have an array
        // join will flatten the array first and then do the join so [[x,y]].join(',') -> "x,y"
        return finalStr += key + "(" + [transformAttributeObj[key]].join(',') + ")"; 
     }, '');
}

La première grande chose à faire avec la première fonction est que je peux modifier manuellement une propriété spécifique (par exemple la rotation) sans avoir à vous soucier de son incidence sur la translation ou autre chose (lorsque vous faites pivoter un point), alors que lorsque je me base sur la construction -in ou même les méthodes d3.transform ils consolident toutes les propriétés en une seule valeur. 

Pourquoi est-ce cool?

Imaginez un peu de HTML 

<g class="tick-label tick-label--is-rotated" transform="translate(542.8228777985075,0) rotate(60, 50.324859619140625, 011.402383210764288)" style="visibility: inherit;"></g>


En utilisant d3.transfrom je reçois:

Sous forme d'objet

jr {rotate: 59.99999999999999, translate: [577.8600589984691, -37.88141544673796], scale: [1, 1], skew: 0, toString: function} 


Sous forme de chaîne

"translate(577.8600589984691,-37.88141544673796)rotate(59.99999999999999)skewX(0)scale(1,1)"


Ce qui est mathématiquement correct mais me rend difficile la suppression de l'angle de rotation et de la translation à introduire pour faire pivoter cet élément autour d'un point donné.


Utiliser ma fonction _getTokenizedTransformAttributeValue

Sous forme d'objet

{translate: ["542.8228777985075", "0"],  rotate: ["60", " 50.324859619140625", " 011.402383210764288"]}


Sous forme de chaîne en utilisant la fonction _getStringFromTokenizedTransformAttributeObj

"translate(542.8228777985075,0)rotate(60, 50.324859619140625, 011.402383210764288)"


Ce qui est parfait car maintenant, lorsque vous supprimez la rotation, votre élément peut revenir à son emplacement d'origine.

Certes, le code pourrait être plus propre et les noms de fonctions plus concis, mais je voulais vraiment le diffuser pour que les autres puissent en tirer parti.

0
Samuel Mburu

J'ai trouvé un moyen d'obtenir quelque chose de similaire en utilisant ceci:

d3.select(this).node().getBBox();

cela vous donnera accès à la position x/y et à la largeur/hauteur Vous pouvez voir un exemple ici: https://bl.ocks.org/mbostock/1160929

0
Gader

J'ai trouvé une solution un peu plus simple que celle.

selection.node().transform.baseVal[0].matrix

Dans cette matrice, vous avez les coordonnées e et f qui sont équivalentes à x, y. (e === x, f === y). Pas besoin d'implémenter votre propre fonction pour cela.

baseVal est une liste de transformations de l'élément. Vous ne pouvez pas utiliser cela pour l'objet sans transformation de previus! (la liste sera vide) Ou si vous avez fait beaucoup de transformations à l'objet, la dernière position sera sous le dernier élément de la liste baseVal.

0
Michał