当流星数据库更新时,在D3中重新绘制预测布局图



我试图在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()之后,新的传入对象将被随机放置并同化。

这也允许您在如何插入更新方面有更大的灵活性。例如,如果您希望新对象总是从左上角进入,在合并期间,您可以将新对象的xy设置为0

最新更新