Actualmente estoy trabajando en un gráfico d3 dirigido por fuerza utilizando la función de zoom . El usuario tiene la opción de crear nuevos nodos en el gráfico. Esto se utiliza a través de un evento de doble clic en el gráfico para obtener las coordenadas en las que se crea el nuevo nodo.
Es posible, y bastante común durante el trabajo diario del usuario, que haga doble clic en el gráfico, luego haga zoom a otra área y/o cambie la escala, y luego termine la creación del nodo (configuración de atributos). En este caso, se supone que el nodo se crea en el área visible del gráfico.
Para este escenario, escribí una función que toma el nodo a crear y el elemento svg y mutó las coordenadas del nodo para centrarlo en el área visible, si es necesario:
moveNodeIntoViewPort(node: NodeInterface, svgElement: any): NodeInterface { let svg = svgElement._groups[0][0]; let minX: number = parseInt(svg.attributes.viewBox.nodeValue.split(" ")[0]); let minY: number = parseInt(svg.attributes.viewBox.nodeValue.split(" ")[1]); let width: number = parseInt( svg.attributes.viewBox.nodeValue.split(" ")[1].split(" ")[0] ); let height: number = parseInt( svg.attributes.viewBox.nodeValue.split(" ")[1].split(" ")[1] ); let zoomX: number = parseInt(svg.__zoom.x) * -1; let zoomY: number = parseInt(svg.__zoom.y) * -1; let zoomK: number = parseFloat(svg.__zoom.k.toFixed(2)); node.x = node.x < minX + zoomX || node.x > minX + width + zoomX ? (minX + zoomX + width / 2) / zoomK : node.x; node.y = node.y < minY + zoomY || node.y > minY + height + zoomY ? (minY + zoomY + height / 2) / zoomK : node.y; return node; }
Aunque este código funciona según lo previsto, no estoy satisfecho con el código en sí y me pregunto si D3 ofrece una solución mejor lista para usar. No pude encontrar ninguna información en la documentación oficial de D3 sobre cómo:
Cualquier aporte es muy apreciado.
Después de algunas investigaciones (y con algo de ayuda), logramos obtener el mismo resultado con el siguiente código usando DOMMatrix:
moveNodeIntoViewPort(node: NodeInterface, svgElement: any): NodeInterface { let transformationMatrix = this.viewportRef._groups[0][0].getScreenCTM(); const nodePoint = new DOMPoint(node.x, node.y).matrixTransform( transformationMatrix ); let currentViewport = svgElement._groups[0][0].getBoundingClientRect(); let centerPoint = new DOMPoint( currentViewport.left + currentViewport.width / 2, currentViewport.top + currentViewport.height / 2 ).matrixTransform(transformationMatrix.inverse()); if ( nodePoint.x < currentViewport.left || nodePoint.x > currentViewport.right || nodePoint.y < currentViewport.top || nodePoint.y > currentViewport.bottom ) { node.x = centerPoint.x; node.y = centerPoint.y; } return node; }
viewportRef
es la selección d3 del elemento secundario <g class="viewport>
del elemento <svg>
.
Aunque no es mucho más corta que la función original, tal como es ahora, podemos evitar el uso de la manipulación de cadenas y podemos extraer parte de la función para usarla en otros procesos.