动态居中过滤节点在D3

问题描述:

我正在努力过滤功能添加到我的D3图。当用户根据标签或ID搜索特定节点时,我想重新渲染图形并再次显示整个图形,但我希望过滤的节点位于svg元素的中心。动态居中过滤节点在D3

enter image description here

这里是我的帮助它为中心:

// I get the width and height of the SVG element: 
    var svgWidth = parseInt(svg.style("width").replace(/px/, ""), 10); 
    var svgHeight = parseInt(svg.style("height").replace(/px/, ""), 10); 

// I get the center of the svg: 
    var centerX = svgWidth/2; 
    var centerY = svgHeight/2; 

    _.forEach(nodes, function(e) { 
      // get the full node (with x and y coordinates) based on the id 
      var nodeObject = g.node(nodeId); 

      // I look for matches between the nodeId or label and search word 
      if (searchInput) { 
       if (nodeObject.id === parseInt(searchInput, 10) || nodeObject.label.toUpperCase().indexOf(searchInput.toUpperCase()) > -1) { 
            searchedNodes.push(nodeObject); 
            console.log(searchedNodes); 
          } 
        } 
       } 

       // after looping through all the nodes rendered 
       if (searchedNodes.length > 0) { 
        //var width = searchedNodes[0].elem.getBBox().width; 
        //var height = searchedNodes[0].elem.getBBox().height; 
        ctrl.selectedNode = searchedNodes[0]; 
        var offsetX = centerX - searchedNodes[0].x; 
        var offsetY = centerY - searchedNodes[0].y; 
        svgGroup.attr("transform", "translate(" + offsetX + "," + offsetY + ")" + "scale(" + 3 + ")"); 

        // this line here is incorrect syntax and breaks the build, essentially stopping the script from running 
        // the graph renders correctly when this line is here 
        svgGroup.attr("transform", "translate(" + offsetX + "," + offsetY + ")").scale(2).event; 
       } 

这是图的样子与上面说的换行符包括脚本。

enter image description here

当我删除线,它没有中心,几乎看起来像过度渲染图。很显然,我将需要删除的上述代码行是不正确,但没有任何人没有为什么图表不正确地在这种情况下?:

enter image description here

  // get the user input and re-render the graph 
      elem.find(".search").bind("keyup", function (e:any) { 
       var searchInput; 
       if (e["keyCode"] === 13) { 
        searchedNodes = []; 
        searchInput = scope["searchInput"]; 
        currentFilteredNode = null; 
        enterKeyPressed = true; 
        renderGraph(searchInput); 
        } 

       if (e["keyCode"] === 8) { 
        searchedNodes = []; 
        searchInput = scope["searchInput"]; 
        currentFilteredNode = null; 
        renderGraph(searchInput); 
       } 
      }); 

// if there is searchInput and at least one matching node sort the nodes 
// by id and then select and center the first matching one 
if (searchInput && searchedNodes.length > 0) { 
     searchedNodes.sort(function (node1:any, node2:any) { 
          return node1.id - node2.id; 
        }); 

        // make sure the noResultsMessage does not get shown on the screen if there are matching results 
        scope.$apply(function() { 
         scope["noResultsMessage"] = false; 
        }); 

        ctrl.selectedNode = searchedNodes[0]; 
        offsetX = centerX - searchedNodes[0].x; 
        offsetY = centerY - searchedNodes[0].y; 
        svgGroup.attr("transform", "translate(" + offsetX + "," + offsetY + ")" + "scale(" + 3 + ")"); 
} 

       // the only other zoom and this runs just on page load 
       zoom = d3.behavior.zoom(); 

       zoom.on("zoom", function() { 
        svgGroup.attr("transform", "translate(" + (<any>d3.event).translate + ")" + "scale(" + (<any>d3.event).scale + ")"); 

      // this scales the graph - it runs on page load and whenever the user enters a search input, which re-renders the whole graph 
      var scaleGraph = function(useAnimation:any) { 
       var graphWidth = g.graph().width + 4; 
       var graphHeight = g.graph().height + 4; 
       var width = parseInt(svg.style("width").replace(/px/, ""), 10); 
       var height = parseInt(svg.style("height").replace(/px/, ""), 10); 
       var zoomScale = originalZoomScale; 
       // Zoom and scale to fit   
       if (ctrl.autoResizeGraph === "disabled") { 
        zoomScale = 1; 
       } else { 
        // always scale to canvas if set to fill or if auto (when larger than canvas) 
        if (ctrl.autoResizeGraph === "fill" || (graphWidth > width || graphHeight > height)) { 
         zoomScale = Math.min(width/graphWidth, height/graphHeight); 
        } 
       } 

       var translate; 

       if (direction.toUpperCase() === "TB") { 
        // Center horizontal + align top (offset 1px) 
        translate = [(width/2) - ((graphWidth * zoomScale)/2) + 2, 1]; 
       } else if (direction.toUpperCase() === "BT") { 
        // Center horizontal + align top (offset 1px) 
        translate = [(width/2) - ((graphWidth * zoomScale)/4) + 2, 1]; 
       } else if (direction.toUpperCase() === "LR") { 
        // Center vertical (offset 1px) 
        translate = [1, (height/2) - ((graphHeight * zoomScale)/2)]; 
       } else if (direction.toUpperCase() === "RL") { 
        // Center vertical (offset 1px) 
        translate = [1, (height/2) - ((graphHeight * zoomScale)/4)]; 
       } else { 
        // Center horizontal and vertical 
        translate = [(width/2) - ((graphWidth * zoomScale)/2), (height/2) - ((graphHeight * zoomScale)/2)]; 
       } 

       zoom.center([width/2, height/2]); 
       zoom.size([width, height]); 

       zoom.translate(translate); 
       zoom.scale(zoomScale); 

       // If rendering the first time, then don't use animation 
       zoom.event(useAnimation ? svg.transition().duration(500) : svg); 
      }; 

CODE渲染用于过滤节点:

// move to the left of the searchedNodes array when the left arrow is clicked 
      scope["filterNodesLeft"] = function() { 
       filterNodesIndex--; 
       if (filterNodesIndex < 0) { 
        filterNodesIndex = searchedNodes.length - 1; 
       } 
       currentFilteredNode = searchedNodes[filterNodesIndex]; 
       runScaleGraph = true; 
       number = 1; 
       renderGraph(); 
      }; 

      // move to the right of the searchNodes array when the right arrow is clicked 
      scope["filterNodesRight"] = function() { 
       filterNodesIndex++; 
       if (filterNodesIndex > searchedNodes.length - 1) { 
        filterNodesIndex = 0; 
       } 
       currentFilteredNode = searchedNodes[filterNodesIndex]; 
       runScaleGraph = true; 
       number = 1; 
       renderGraph(); 
      }; 

    // get the current filteredNode in the searchNodes array and center it 
    // when the graph is re-rendered 
    if (currentFilteredNode) { 
        ctrl.selectedNode = currentFilteredNode; 
        offsetX = centerX - currentFilteredNode.x; 
        offsetY = centerY - currentFilteredNode.y; 
        svgGroup.attr("transform", "translate(" + offsetX + "," + offsetY + ")"); 
        runScaleGraph = false; 
       } 
+0

是,所有节点坐g.nodes内,然后每个节点都具有克节点类。我确实具有subGraph功能,因此新的一组节点位于容器节点内。我正在寻找以上图中搜索输入匹配的节点为中心。我也在上图中包含了DOM布局。这可能吗?感谢您的帮助 – bschmitty

+0

当您的脚本引发错误(如在第一个屏幕截图中)时,您所期望的视觉输出是什么?你有其他代码调整svgGroup的转换吗?如果在注释掉导致空指针/引用的行时,如果在调试器中单步执行代码会发生什么情况? –

+0

是的脚本抛出错误的第一个屏幕截图是预期的输出。我只是希望每个选定的节点都居中。然后,如果有多个匹配的搜索结果,用户可以单击以查看匹配结果,则可以选择该选项。此代码是大角度组件的一部分,但我添加了主代码,包括调用其他缩放区的区域。 – bschmitty

这里是我如何解决它:

   // zoom in on the searched or filtered node 
       function zoomOnNode (node:any) { 

       // get the width and height of the svg 
       var svgWidth = parseInt(svg.style("width").replace(/px/, ""), 10); 
       var svgHeight = parseInt(svg.style("height").replace(/px/, ""), 10); 

       // loop through all the rendered nodes (these nodes have x and y coordinates) 
       for (var i = 0; i < renderedNodes.length; i++) { 

        // if the first matching node passed into the function 
        // and the renderedNode's id match get the 
        // x and y coordinates from that rendered node and use it to calculate the svg transition 
        if (node.id === renderedNodes[i].id) { 
          var translate = [svgWidth/2 - renderedNodes[i].x, svgHeight/2 - renderedNodes[i].y]; 
          var scale = 1; 
          svg.transition().duration(750).call(zoom.translate(translate).scale(scale).event); 
        } 
       } 
      } 

    // listen for the enter key press, get all matching nodes and pass in the first matching node in the array to the zoomOnNode function 
    elem.find(".search").bind("keyup", function (e:any) { 
       var searchInput; 
       if (e["keyCode"] === 13) { 
        searchedNodes = []; 
        searchInput = scope["searchInput"]; 
        enterKeyPressed = true; 
        if (searchInput) { 
         // recursively get all matching nodes based on search input 
         getMatchingNodes(ctrl.nodes, searchInput); 

         scope.$apply(function() { 
          // show the toggle icons if searchedNodes.length is greater then 1 
          scope["matchingNodes"] = searchedNodes.length; 
          scope["noResultsMessage"] = false; 

          if (searchedNodes.length > 0) { 
           var firstNode = searchedNodes[0]; 
           ctrl.selectedNode = firstNode; 
           zoomOnNode(firstNode); 
          } else if (searchedNodes.length === 0) { 
            ctrl.selectedNode = null; 
            // add the noResultsMessage to the screen 
            scope["noResultsMessage"] = true; 
           } 
          }); 
         } 
       } 
      } 

您将希望找到目标节点的x和y坐标,并相应地使用类'output'设置组的transform属性。您还需要知道“输出”的宽度和高度,以便定位它以使您的目标节点位于中心。

//when diagram is initially displayed 
var output = d3.select('.output'); 
var bbox = output.getBBox(); 
var centerX = bbox.width * .5; 
var centerY = bbox.height * .5; 


//in your block where you find a node matches the filter 
    if (node.label.toUpperCase().indexOf(searchString.toUpperCase()) > -1) { 
     var offsetX = centerX - node.x; 
     var offsetY = centerY - node.y; 
     output.attr('transform', 'translate(' + offsetX + ',' + offsetY + ')');  
    } 

根据节点的注册点,你可能还需要采取在考虑到节点的宽度和高度,以确保我们直接为中心的节点上。例如,如果注册点位于节点的左上角,则您希望将节点宽度的一半和节点高度的一半添加到偏移量。

- 编辑 -

在下面一行:

svgGroup.attr("transform", "translate(" + offsetX + "," + offsetY + ")" + "scale(" + 3 + ")"); 

通过包括 “规模(” + 3 + “),” 所以你缩放整个图形 - 你是不是“放大在'你居中的地方,而内容本身更大,所以偏移量X和偏移量Y不是正确的坐标中心。

之所以事情变得更好看,当你添加其他行,是要删除的规模。

svgGroup.attr("transform", "translate(" + offsetX + "," + offsetY + ")"); 

所以,我们又回到了默认的比例尺,就在错误被引发之前。

如果你想扩展,就需要通过任何你想扩展的乘OFFSETX和OFFSETY。

如果您不想向规模化,只是删除

"scale(" + 3 + ")" 
+0

我看到,打破了我的图形,它不在svg元素中心。我需要将上面的整个图形放在一起,并将匹配节点滑入SVG元素的中心。 – bschmitty

+0

我更新了我的初始帖子,以显示我在某种程度上的工作。谢谢 – bschmitty