【问题标题】:D3 force directed layout with bounding box带边界框的 D3 强制定向布局
【发布时间】:2012-03-23 07:26:56
【问题描述】:

我是 D3 新手,无法为我的强制导向布局设置边界。我已经设法(从示例中)拼凑出我想要的东西,但我需要包含图表。在刻度函数中,转换/翻译将正确显示我的图形,但是当我将 cx 和 cy 与 Math.max/min 一起使用时(参见注释代码),节点被固定到 左上角,而线条被正确包含。

这是我下面的内容......我做错了什么?

var w=960, h=500, r=8,  z = d3.scale.category20();

var color = d3.scale.category20();

var force = d3.layout.force()
       .linkDistance( function(d) { return (d.value*180) } )
       .linkStrength( function(d) { return (1/(1+d.value)) } )
       .charge(-1000)
       //.gravity(.08)
       .size([w, h]);

var vis = d3.select("#chart").append("svg:svg")
       .attr("width", w)
       .attr("height", h)
       .append("svg:g")
       .attr("transform", "translate(" + w / 4 + "," + h / 3 + ")");

vis.append("svg:rect")
   .attr("width", w)
   .attr("height", h)
   .style("stroke", "#000");


d3.json("miserables.json", function(json) {

       var link = vis.selectAll("line.link")
               .data(json.links);

       link.enter().append("svg:line")
               .attr("class", "link")
               .attr("x1", function(d) { return d.source.x; })
               .attr("y1", function(d) { return d.source.y; })
               .attr("x2", function(d) { return d.source.x; })
               .attr("y2", function(d) { return d.source.y; })
               .style("stroke-width", function(d) { return (1/(1+d.value))*5 });

       var node = vis.selectAll("g.node")
               .data(json.nodes);

       var nodeEnter = node.enter().append("svg:g")
               .attr("class", "node")
               .on("mouseover", fade(.1))
               .on("mouseout", fade(1))
               .call(force.drag);

       nodeEnter.append("svg:circle")
               .attr("r", r)
               .style("fill", function(d) { return z(d.group); })
               .style("stroke", function(d) { return
d3.rgb(z(d.group)).darker(); });

       nodeEnter.append("svg:text")
               .attr("text-anchor", "middle")
               .attr("dy", ".35em")
               .text(function(d) { return d.name; });

       force
       .nodes(json.nodes)
       .links(json.links)
       .on("tick", tick)
       .start();

       function tick() {

       // This works
               node.attr("transform", function(d) { return "translate(" + d.x + ","
+ d.y + ")"; });

       // This contains the lines within the boundary, but the nodes are
stuck in the top left corner
               //node.attr("cx", function(d) { return d.x = Math.max(r, Math.min(w
- r, d.x)); })
               //      .attr("cy", function(d) { return d.y = Math.max(r, Math.min(h -
r, d.y)); });

       link.attr("x1", function(d) { return d.source.x; })
               .attr("y1", function(d) { return d.source.y; })
               .attr("x2", function(d) { return d.target.x; })
               .attr("y2", function(d) { return d.target.y; });
       }

       var linkedByIndex = {};

   json.links.forEach(function(d) {
       linkedByIndex[d.source.index + "," + d.target.index] = 1;
   });

       function isConnected(a, b) {
       return linkedByIndex[a.index + "," + b.index] ||
linkedByIndex[b.index + "," + a.index] || a.index == b.index;
   }

       function fade(opacity) {
       return function(d) {
           node.style("stroke-opacity", function(o) {
                       thisOpacity = isConnected(d, o) ? 1 : opacity;
                       this.setAttribute('fill-opacity', thisOpacity);
               return thisOpacity;
                       });

                       link.style("stroke-opacity", opacity).style("stroke-opacity",
function(o) {
               return o.source === d || o.target === d ? 1 : opacity;
               });
       };
       }

});

【问题讨论】:

  • 我玩过一些参数,并决定除非有人有解决方案,否则我将使用大量的 .gravity()。如果图非常大,它仍然会造成问题,但否则应该包含节点就好了。

标签: d3.js force-layout


【解决方案1】:

自定义部队也是一种可能的解决方案。我更喜欢这种方法,因为不仅显示的节点被重新定位,而且整个模拟都与边界力一起工作。

let simulation = d3.forceSimulation(nodes)
    ...
    .force("bounds", boxingForce);

// Custom force to put all nodes in a box
function boxingForce() {
    const radius = 500;

    for (let node of nodes) {
        // Of the positions exceed the box, set them to the boundary position.
        // You may want to include your nodes width to not overlap with the box.
        node.x = Math.max(-radius, Math.min(radius, node.x));
        node.y = Math.max(-radius, Math.min(radius, node.y));
    }
}

【讨论】:

    【解决方案2】:

    注释代码适用于节点,根据您的定义,节点是一个 svg g(rouping) 元素,并且不操作 cx/cy 属性。选择节点内的圆形元素,使这些属性生效:

    node.select("circle") // select the circle element in that node
        .attr("cx", function(d) { return d.x = Math.max(r, Math.min(w - r, d.x)); })
        .attr("cy", function(d) { return d.y = Math.max(r, Math.min(h - r, d.y)); });
    

    【讨论】:

      【解决方案3】:

      我的talk on force layouts 中有一个bounding box example。位置 Verlet 集成允许您在“tick”事件侦听器内部定义几何约束(例如边界框和collision detection);只需移动节点以符合约束条件,模拟就会相应地进行调整。

      也就是说,重力绝对是一种更灵活的方式来处理这个问题,因为它允许用户将图形暂时拖到边界框之外,然后图形就会恢复。根据图表的大小和显示区域的大小,您应该尝试不同的相对强度的重力和电荷(排斥)以使您的图表适合。

      【讨论】:

      • 关键是添加node.attr("cx", function(d) { return d.x = Math.max(r, Math.min(width - r, d.x)); }) .attr("cy", function(d) { return d.y = Math.max(r, Math.min(height - r, d.y)); });如果你使用path而不是line,你需要为`attr('d', function () {...}添加边界检查);``` 也是。
      • @mbostock,有没有办法提供边界坐标(仅在定义的 4 个角内移动)
      猜你喜欢
      • 2014-07-09
      • 1970-01-01
      • 1970-01-01
      • 2013-12-02
      • 1970-01-01
      • 2021-11-26
      • 2013-06-25
      • 2016-11-10
      • 2013-02-12
      相关资源
      最近更新 更多