我试图在D3中获得一个力布局图(如http://bl.ocks.org/mbostock/1748247),以便与反应性数据源(meteor.js)配合使用。我对这两个世界都比较陌生,所以我遇到了这个问题。我已经使force布局正常工作,并且将数据源设置为流星集合效果很好,但是当我从控制台更新或添加到数据库时,所有节点都像刚刚生成的一样四处飞行。就好像所有的数据都被视为新的,而不仅仅是附加新数据,或者转换当前节点以匹配更新。
我已经浏览了所有我能讨论的D3和meteor,但我对概念的把握还不够强,无法进一步深入。感谢任何帮助或指出正确方向。
我的d3区域模板是用#常量包装的,像这样:
<template name="ideaspace">
{{#constant}}
<svg>
</svg>
{{/constant}}
</template>
在客户端,我得到了这个。(请原谅这些可怕的代码。
Template.ideaspace.rendered = function () {
var self = this;
self.node = self.find("svg");
if(! self.handle) {
self.handle = Deps.autorun(function () {
//d3 code
var nodes = DataPoints.find().fetch();
var force = d3.layout.force()
.nodes(nodes)
.size([width, height])
.gravity(.02)
.charge(0)
.on("tick", tick)
;
var svg = d3.select("svg")
.attr("width", width)
.attr("height", '100%')
.attr("id", 'container')
.style("top", '30px')
.style("position", 'fixed')
.attr("pointer-events", "all")
svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.attr("fill", "whitesmoke")
.call(d3.behavior.zoom()
.on("zoom", function() {
scale = d3.event.scale;
if (scale<=1){
width = $(window).width()/d3.event.scale;
foci = [{x: '0%', y: 150}, {x: width*1/4, y: 1/2*height}, {x: width*2/4, y: 1/2*height}, {x: width*3/4, y: 1/2*height}];
}
svg.attr("transform", "translate(" + d3.event.translate +
")scale(" + d3.event.scale + ")");
}));
var x =
d3.scale.linear()
.domain([0, width])
.range([0, width]);
var y =
d3.scale.linear()
.domain([0, height])
.range([0, height]);
var bubble = svg.selectAll(".bubble");
bubble = bubble.data(nodes,function (party) { return party._id; });
width = $(window).width();
bubble.enter().append("svg:circle")
.style("fill", function(d) { return color(+d.emperical); })
.style("stroke", "grey")
.attr("id", function(d) { return d.objectId+"_c"; })
.attr("r", function(d) { return d.radius; })
.call(force.drag);
bubble.transition().duration(5000);
force.start();
function tick(e) {
bubble
.each(collide(.5))
.attr("transform", function(d) {return "translate(" + d.x + "," + d.y + ")";
})
}
function cluster(alpha) {
var max = {};
// Find the largest node for each cluster.
nodes.forEach(function(d) {
if (!(d.emperical in max) || (d.radius > max[d.emperical].radius)) {
max[d.emperical] = d;
}
});
return function(d) {
var node = max[d.emperical],
l,
r,
x,
y,
i = -1;
if (node == d) return;
x = d.x - node.x;
y = d.y - node.y;
l = Math.sqrt(x * x + y * y);
r = d.radius + node.radius;
if (l != r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
node.x += x;
node.y += y;
}
};
}
})
};
};
UPDATE:这使添加新数据工作。现在我想我需要建立一些东西来检查是否有任何其他字段已被更改。
self.handle = Deps.autorun(function () {
var topicPoints = TopicPoints.find().fetch();
var found = true;
topicPoints.forEach(function (d) {
if(nodes.length>0){
for(var i = 0; i < nodes.length; i++) {
if (nodes[i]._id == d._id) {
console.log("Found "+ d._id)
found = true;
break;
} else {
found = false
console.log("Not Found "+ d._id)
}
}
if(!found){
nodes.push({
_id:d._id,
title: d.title,
radius: d.radius
});
}
} else {
nodes = topicPoints;
}
});
render(nodes);
})
问题是您正在用Datapoints.find().fetch()
构造nodes
数组。这个数组(可能)缺少以下属性:
- index -节点数组中节点的从零开始的索引。
- x -当前节点位置的x坐标。
- y -当前节点位置的y坐标。
- px -前一个节点位置的x坐标。
- py -前一个节点位置的y坐标。
- fixed -一个布尔值,指示节点位置是否被锁定。
- weight—节点权重;关联链接数
这些属性对于force.nodes()
的调用并不是严格需要的,但是如果这些属性不存在于数组中,那么它们将在第一次调用时由force.start()
随机初始化。这就是你正在目睹的。
nodes
数组,该数组在rendered
调用中保留。当您必须更新数据时,请自己将数组与新数据合并(可能使用一组party._id
或聪明的DB查询而不是.find().fetch()
)。这将保留现在设置了上述属性的旧对象,并插入没有这些属性的新元素。然后,在调用force.start()
之后,新的传入对象将被随机放置并同化。
这也允许您在如何插入更新方面有更大的灵活性。例如,如果您希望新对象总是从左上角进入,在合并期间,您可以将新对象的x
和y
设置为0
。