D3.js forcer la mise en page de zoom / échelle automatique après le chargement

J'utilise cette jolie disposition de force de Flowingdata.com pour créer un diagramme de réseau.

Entrez la description de l'image ici

Entrez la description de l'image ici

Entrez la description de l'image ici

Mon diagramme montre actuellement entre 5 et 750 nœuds avec leurs relations. Cela fonctionne très bien avec des modifications personnalisées adaptées à mes besoins. Cependant, je ne peux pas travailler. J'ai une viewBox avec preserveAspectRatio pour monter automatiquement le conteneur dans viewBox il se trouve. Mais en fonction de la quantité de noeuds, il y a toujours des noeuds autour des bords (principalement top et buttom) qui se coupent. Et s'il y a très peu de noeuds, il les montre au milieu avec un énorme espace vide autour de lui (c'est un gros conteneur dans lequel il se trouve).

Existe-t-il un moyen de zoom automatique ou d'échelle de la mise en page pour s'adapter automatiquement? Afin qu'une grande mise en page soit quelque peu agrandie et qu'une petite mise en page soit agrandie. J'ai une configuration d'événement de zoom, de sorte que le défilement et le panoramique fonctionnent comme un charme. Mais peut-il faire automatiquement cela pour s'adapter au contenu?

Le code de démarrage d3.js:

  vis = d3.select(selection) .append("svg") .attr("viewBox", "0 0 " + width + " " + height ) .attr("preserveAspectRatio", "xMidYMid meet") .attr("pointer-events", "all") .call(d3.behavior.zoom().scaleExtent([.1, 3]) .on("zoom", redraw)).append('g'); 

Toutes les autres réponses à ce jour nécessitent l'accès aux données, et les itère à travers elle afin que la complexité soit au moins O(nodes) . J'ai continué à chercher et à trouver une manière qui est uniquement basée sur la taille visuelle déjà rendue, getBBox() qui espérons que O(1) . Peu importe ce qu'il y a ou comment il est exposé, juste sa taille et la taille du conteneur parent. J'ai réussi à le faire en fonction de http://bl.ocks.org/mbostock/9656675 :

 var root = // any svg.select(...) that has a single node like a container group by #id function lapsedZoomFit(ticks, transitionDuration) { for (var i = ticks || 100; i > 0; --i) force.tick(); force.stop(); zoomFit(transitionDuration); } function zoomFit(transitionDuration) { var bounds = root.node().getBBox(); var parent = root.node().parentElement; var fullWidth = parent.clientWidth || parent.parentNode.clientWidth, fullHeight = parent.clientHeight || parent.parentNode.clientHeight; var width = bounds.width, height = bounds.height; var midX = bounds.x + width / 2, midY = bounds.y + height / 2; if (width == 0 || height == 0) return; // nothing to fit var scale = 0.85 / Math.max(width / fullWidth, height / fullHeight); var translate = [fullWidth / 2 - scale * midX, fullHeight / 2 - scale * midY]; console.trace("zoomFit", translate, scale); root .transition() .duration(transitionDuration || 0) // milliseconds .call(zoom.translate(translate).scale(scale).event); } 

Vous pouvez itérer sur les nœuds; Obtenez les valeurs max et min x et y pour tous les noeuds et calculez le zoom et la traduction nécessaires pour couvrir tous vos voisins dans la taille SVG.

J'ai un code qui concentre le graphe sur un nœud donné; Cela pourrait vous aider à faire une idée.

 zs = zoom.scale() zt = zoom.translate(); dx = (w/2.0/zs) - dx; dy = (h/2.0/zs) - dy; zoom.translate([dx, dy]); zoom.scale(zs); 

Où w, h sont la largeur et la hauteur de ma toile SVG, et d le noeud auquel je veux me concentrer.

Au lieu de centrer à dx, dy, vous devez calculer la moyenne de x et y. Et calculez votre échelle de zoom qui fera en sorte que la largeur (et la hauteur) de votre graphique corresponde à la largeur (et à la taille) de votre SVG Choisissez le plus grand zoom des deux.

Votre code devrait être similaire à celui-ci

  vis = d3.select(selection) .append("svg") .attr("viewBox", "0 0 " + width + " " + height ) .attr("preserveAspectRatio", "xMidYMid meet") .attr("pointer-events", "all") .call(zoomListener) .on("zoom", redraw)); var mainGroup = vis.append('g'); function zoom() { mainGroup.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); } var zoomListener = d3.behavior.zoom().on("zoom", zoom); var xArray = YOUR_NODES.map(function(d) { //YOUR_NODES is something like json.nodes after forse.end() return dx }); var minX = d3.min(xArray); var maxX = d3.max(xArray); var scaleMin = Math.abs(width / (maxX - minX)); var startX = (minX) / scaleMin; var startY = 50 / scaleMin; // Same as in the zoom function mainGroup.attr("transform", "translate(" + [startX, startY] + ")scale(" + scaleMin + ")"); // Initialization start param of zoomListener zoomListener.translate([startX, startY]); zoomListener.scale(scaleMin); zoomListener.scaleExtent([scaleMin, 1]) vis.call(zoomListener); 

Ce code fonctionne uniquement pour xAxis. Parce que "global circle" svg RX === RY. Si ce n'était pas pour vous, vous pouvez ajouter la même logique pour yAxis var startY . Aussi, vous devez ajuster les coordonnées initiales en considérant les noeuds cr of circle.

Voici deux exemples:

Disposition de la force avec zoom automatique (toile):

https://vida.io/documents/pT3RDxrjKHLQ8CQxi

Disposition de force avec zoom automatique (SVG)

https://vida.io/documents/9q6B62xQgnZcYYen2

La fonction qui effectue l'échelle et dessine pour SVG:

 function scaleAndDraw() { var xExtent = d3.extent(d3.values(nodes), function(n) { return nx; }), yExtent = d3.extent(d3.values(nodes), function(n) { return ny; }); if ((xExtent[1] - xExtent[0]) > config.width) { scaleX = (xExtent[1] - xExtent[0]) / config.width; scaleY = (yExtent[1] - yExtent[0]) / config.height; scale = 1 / Math.max(scaleX, scaleY); translateX = Math.abs(xExtent[0]) * scale; translateY = Math.abs(yExtent[0]) * scale, svg.attr("transform", "translate(" + translateX + "," + translateY + ")" + " scale(" + scale + ")"); } draw(); }