49

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

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

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

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

4

6 回答 6

40

以上所有答案都误解了Øystein Amundsen的问题。

稳定启动力的唯一方法是设置 node.x 和 node.ya 适当的值。请注意,节点是 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() 时的位置开始。这样可以避免混乱。

于 2013-10-04T08:14:29.967 回答
30

tick()在内部,在“正常”使用情况下,强制布局反复异步调用自己的方法(通过 asetIntervalrequestAnimationFrame),直到布局确定解决方案。此时,它的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 将这种方法归结为一个简洁的函数。在此页面的某处查看他的答案。

于 2012-11-20T00:29:04.037 回答
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,否则你会得到奇怪的结果。

于 2012-11-21T14:40:11.070 回答
8

根据其他答案,我制作了这种方法:

function forwardAlpha(layout, alpha, max) {
  alpha = alpha || 0;
  max = max || 1000;
  var i = 0;
  while(layout.alpha() > alpha && i++ < max) layout.tick();
}
于 2013-05-03T02:29:48.247 回答
4

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

于 2014-03-18T09:22:00.907 回答
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 和 y 属性。所需位置是 svg 元素的中心。

于 2015-06-29T01:22:09.947 回答