【问题标题】:Dynamically Centering Filtered Node within SVG Element in D3在 D3 中的 SVG 元素中动态居中过滤节点
【发布时间】:2016-12-07 00:42:53
【问题描述】:

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

这是我帮助它居中的原因:

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

这就是图表的样子,上面的行打破了包含的脚本。

当我删除那条线时,它没有居中,几乎看起来像是过度渲染了图表。显然我需要删除上面不正确的代码行,但有人不知道为什么在这种情况下图表不能正确呈现吗?:

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

过滤节点的代码:

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

【问题讨论】:

  • 是的,所有节点都位于 g.nodes 中,然后每个节点都有 g.node 类。我确实具有 subGraph 功能,因此新的节点集位于容器节点内。我正在寻找与上图中的搜索输入匹配的任何节点居中。我也在上图中包含了 DOM 布局。这可能吗?感谢您的帮助
  • 当您的脚本抛出错误(如在第一个屏幕截图中)时,视觉输出是您期望的吗?你有其他代码调整 svgGroup 的变换吗?如果您注释掉导致空指针/引用的行,如果您在调试器中单步执行代码会发生什么?
  • 是的,脚本抛出错误的第一个屏幕截图是预期的输出。我只希望每个选定的节点居中。然后,我可以选择是否有多个匹配的搜索结果,用户可以单击以查看匹配结果。这段代码是一个大角度组件的一部分,但我已经添加了主要代码,包括调用其他缩放的区域。
  • 从我在调试器中可以看出,当用户输入搜索输入并再次调用 renderGraph 时,匹配的节点被推入“searchedNodes”数组,然后首先按最低 id 排序,然后第一个节点被选中并且应该在 svg 中居中。它似乎运行了一次。我似乎无法判断 scaleGraph 是否导致 svgGroup translate 属性调用不正确?我在原始代码中添加了一些额外的代码post.再次感谢
  • 我认为这个错误是一个红鲱鱼。您在出错之前立即从转换中删除“比例”。编辑答案如下。

标签: javascript jquery angularjs d3.js svg


【解决方案1】:

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

//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 + ")");

通过包含 "scale(" + 3 + ")" 来缩放整个图形 - 您不是在“放大”居中的位置,而是内容本身更大,因此 offsetX 和 offsetY 不是以正确的坐标为中心。

添加另一行后看起来更好的原因是您正在删除比例。

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

因此,在您抛出错误之前,我们将恢复到默认比例。

如果要缩放,则需要将 offsetX 和 offsetY 乘以要缩放的任何值。

如果您不想扩展,只需删除

"scale(" + 3 + ")"

【讨论】:

  • 我看到它破坏了我的图表,并且它不在 svg 元素中居中。我需要上面的整个图表与滑动到 SVG 元素中心的匹配节点保持在一起。
  • 我更新了我最初的帖子,以展示我的工作。谢谢
【解决方案2】:

我是这样解决的:

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

【讨论】:

    猜你喜欢
    • 2021-04-03
    • 2014-12-24
    • 2021-09-19
    • 1970-01-01
    • 2012-03-08
    • 2015-10-22
    • 1970-01-01
    • 2014-06-30
    • 1970-01-01
    相关资源
    最近更新 更多