冷静一个力布局的初始滴答

时间:2012-11-19 22:00:47

标签: d3.js force-layout

我刚刚开始涉足d3,发现学习曲线非常陡峭。这个过程与我习惯的过程完全不同,数学大多数都超出了我的想象。

无论如何,我的项目包含一个力布局,代表系统之间的集成地图。这部分工作得非常好,但我确实有一个主要问题,这也在Michael Bostocks网站上的强制定向布局演示中有所体现: 启动节点时,它们似乎在画布上呈现。在此之后,一些严肃的物理数学正在接管,模拟一个引力,它在一个相当混乱的路径上来回发送节点,直到它们冷静下来并在一些随机坐标上稳定下来。虽然这些动作在第一次运行演示时会逐渐消失,但是当您尝试从管理员的角度查看公司的网络接口状态时,服务器不会保持静止,一段时间后会变得很烦人。

我确信我为这个项目设置了正确的布局,因为我确实希望服务器自动布局,我确实希望可视化它们之间的链接。然而,我对万有引力效应感到矛盾。

我想知道;是否可以手动设置每个节点的初始位置,以便我可以将它们放在更接近重力中心并稍微缩短“反弹时间”?

6 个答案:

答案 0 :(得分:34)

上述所有答案都误解了ØysteinAmundsen的问题。

在它启动时稳定力的唯一方法是将node.x和node.y设置为适当的值。 请注意,节点是d3.js的数据,而不是表示的DOM类型。

例如,如果你加载

nodes = [{"id": a}, {"id": b}, {"id": c}]

进入

d3.layout.force().nodes(nodes)

您必须设置节点数组中所有元素的所有.x和.y 它会是这样的(在coffeescript中)

nodes = [{"id": a}, {"id": b}, {"id": c}]
for i in [0..2]
  nodes[i].x = 500 #the initial x position of node
  nodes[i].y = 300 #the initial y position of node
d3.layout.force().nodes(nodes).links(links)

然后节点将从force.start()开始。 这样可以避免混乱。

答案 1 :(得分:30)

在内部,在“正常”使用情况下,强制布局会反复调用自己的tick()方法(通过setIntervalrequestAnimationFrame),直到布局解决方案为止。此时,其alpha()值等于或接近0.

因此,如果您希望通过此解决方案流程“快进”,则可以反复同步调用tick()方法,直到布局的alpha达到一个值,根据您的特定要求,该值构成“关闭”足够的解决方案。像这样:

var force = d3.layout.force(),
    safety = 0;
while(force.alpha() > 0.05) { // You'll want to try out different, "small" values for this
    force.tick();
    if(safety++ > 500) {
      break;// Avoids infinite looping in case this solution was a bad idea
    }
}

if(safety < 500) {
  console.log('success??');
}

运行此代码后,您可以根据节点的状态绘制布局。或者,如果您通过绑定到tick事件(即force.on('tick', drawMyLayout))来绘制布局,那么您需要在此代码运行之后进行绑定,否则您将不必要地在while循环期间同步渲染布局数百次。

JohnS将这种方法归结为一个简洁的功能。在本页的某处查看他的答案。

答案 2 :(得分:11)

我刚才处理过类似的事情。有几点需要考虑。

1)迭代刻度模拟了一个达到平衡的系统。因此,在系统稳定并且您进行自动布局之前,无法避免在需要时多次调用tick。也就是说,您无需为每个刻度更新可视化模拟工作!事实上,如果你不这样做,迭代会更快。我的代码的相关部分是:

var iters = 600; // You can get decent results from 300 if you are pressed for time
var thresh = 0.001;
if(!hasCachedLayout || optionsChanged || minorOptionsChanged) {
    force.start(); // Defaults to alpha = 0.1
    if(hasCachedLayout) {
        force.alpha(optionsChanged ? 0.1 : 0.01);
    }
    for (var i = iters; i > 0; --i) {
        force.tick();
        if(force.alpha() < thresh) {
            //console.log("Reached " + force.alpha() + " for " + data.nodes.length + " node chart after " + (iters - i) + " ticks.");
            break;
        }
    }
    force.stop();
}

它同步运行,运行后我为所有节点和链接创建dom元素。对于小图表,这种运行速度非常快,但您会发现较大的图形(100多个节点)存在延迟 - 它们的计算成本更高。

2)您可以缓存/种子布局。如果没有设置位置,则力布局将在初始化时均匀分布节点!因此,如果确保节点已设置x和y属性,则将使用这些属性。特别是当我更新现有图形时,我将重新使用先前布局中的x和y位置。

您会发现初始布局良好,您需要批次更少的迭代才能达到稳定的配置。 (这是上面代码中hasCachedLayout跟踪的内容)。注意:如果你从相同的布局重新使用相同的节点,那么你还必须确保将px和py设置为NaN,否则你会得到奇怪的结果。

答案 3 :(得分:8)

基于其他答案我已经采用了这种方法:

function forwardAlpha(layout, alpha, max) {
  alpha = alpha || 0;
  max = max || 1000;
  var i = 0;
  while(layout.alpha() > alpha && i++ < max) layout.tick();
}

答案 4 :(得分:4)

也许force.friction(0.5)或其他低于默认值0.9的数字会有帮助吗?至少它会在页面加载时给人一种不那么混乱的印象。

答案 5 :(得分:0)

	var width = 960,
	  height = 500;

	var fill = d3.scale.category20();

	var force = d3.layout.force()
	  .size([width, height])
	  .nodes([{}]) // initialize with a single node
	  .linkDistance(30)
	  .charge(-60)
	  .on("tick", tick);

	var svg = d3.select("body").append("svg")
	  .attr("width", width)
	  .attr("height", height)
	  .on("mousedown", mousedown);

	svg.append("rect")
	  .attr("width", width)
	  .attr("height", height);

	var nodes = force.nodes(),
	  links = force.links(),
	  node = svg.selectAll(".node"),
	  link = svg.selectAll(".link");

	 // var cursor = svg.append("circle")
	 //     .attr("r", 30)
	 //     .attr("transform", "translate(-100,-100)")
	 //     .attr("class", "cursor");

	restart();

	function mousedown() {
	  var point = d3.mouse(this),
	    node = {
	      x: width / 2,
	      y: height / 2,
	      "number": Math.floor(Math.random() * 100)
	    },
	    n = nodes.push(node);

	  // add links to any nearby nodes
	  /*  nodes.forEach(function(target) {
		    var x = target.x - node.x,
		        y = target.y - node.y;
		    if (Math.sqrt(x * x + y * y) < 30) {
		      links.push({source: node, target: target});
		    }
		  });
		*/
	  restart();
	}

	function tick() {
	  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;
	    });

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

	function restart() {
	  link = link.data(links);

	  link.enter().insert("line", ".node")
	    .attr("class", "link");

	  node = node.data(nodes);

	  // node.enter().insert("circle", ".cursor")
	  //     .attr("class", "node")
	  //     .attr("r", 5)
	  //     .call(force.drag);

	  var nodeEnter = node.enter().insert("svg:g", ".cursor")
	    .attr("class", "node")
	    .call(force.drag);

	  nodeEnter.append("svg:circle")
	    .attr("r", 5)

	  nodeEnter.append("svg:text")
	    .attr("class", "textClass")
	    .attr("x", 14)
	    .attr("y", ".31em")
	    .text(function(d) {
	      return d.number;
	    });

	  force.start();
	}
	rect {
	  fill: none;
	  pointer-events: all;
	}
	.node {
	  fill: #000;
	}
	.cursor {
	  fill: none;
	  stroke: brown;
	  pointer-events: none;
	}
	.link {
	  stroke: #999;
	}
	.textClass {
	  stroke: #323232;
	  font-family: "Lucida Grande", "Droid Sans", Arial, Helvetica, sans-serif;
	  font-weight: normal;
	  stroke-width: .5;
	  font-size: 14px;
	}
	
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

您可能正在寻找的一个例子。它设定了x&amp; y在将新节点插入布局之前的新属性的属性。所需的位置是svg元素的中心。