且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

D3力定向布局与边界框

更新时间:2023-08-30 14:39:04

有一个边框框示例,位于我的强制布局 。位置Verlet集成允许您定义几何约束(例如边界框和碰撞检测) tick事件侦听器;简单地移动节点以符合约束,并且模拟将相应地适应。

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

I am new to D3 and having trouble setting the bounds for my force directed layout. I have managed to piece together (from examples) what I would like, but I need the graph to be contained. In the tick function, a transform/translate will display my graph correctly, but when i use cx and cy with Math.max/min (See commented code), the nodes are pinned to the top left corner while the lines are contained properly.

Here is what I have below... what am I doing wrong??

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

});

There's a bounding box example in my talk on force layouts. The position Verlet integration allows you to define geometric constraints (such as bounding boxes and collision detection) inside the "tick" event listener; simply move the nodes to comply with the constraint and the simulation will adapt accordingly.

That said, gravity is definitely a more flexible way to deal with this problem, since it allows users to drag the graph outside the bounding box temporarily and then the graph will recover. Depend on the size of the graph and the size of the displayed area, you should experiment with different relative strengths of gravity and charge (repulsion) to get your graph to fit.