D3.js - create new node in visible area when zoomed

139 views Asked by At

I am currently working on a force-directed d3 graph utilizing the zoom function. The user has the option to create new nodes on the graph. This is utilized via a double click event on the graph to get the coordinates at which the new node is created.

It is possible, and during the user's daily work quite common, that the double clicks on the graph, then zooms to another area and/or changes the scaling, and then finished the creation of the node (setting attributes). In this case, the node is suppose to be created in the visible area of the graph.

For this scenario, I wrote a function that takes the to be created node and the svg element and mutated the node's coordinates to center it in the visible area, if necessary:

  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;
  }

Although this code works as intended, I am not satisfied with the code itself and am wondering, if D3 offers any better solution out of the box. I could not find any information in the official D3 documentation about how to:

  • check, if a node is visible in the zoomed area
  • how to get the current center coordinates of a zoomed graph

Any input is greatly appreciated.

1

There are 1 answers

0
0kMike On

After some research (and with some help), we managed to realize the same result with following code using a 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 is the d3 selection of the <g class="viewport> child of the <svg> element.

Although not quite shorter than the original function, as it is now, we can prevent using string manipulation and may extract part of the function to be used in other processes.