d3用力添加和删除节点 [英] d3 adding and removing nodes with force

查看:167
本文介绍了d3用力添加和删除节点的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我根据我在书中看到的一些代码,整理了以下jfiddle: http: //jsfiddle.net/hiwilson1/o3gwejbx/2 。大致来说,我会跟踪发生的情况,但有一些方面我不追踪。

  svg.on(mousemove, function(){

var point = d3.mouse(this),
node = {x:point [0],y:point [1]};

svg.append(circle)
.data([node])
.attr(r,1e-6)
.transition()
.attr (r,4.5)
.transition()
.delay(1000)
.attr(r,1e-6)
.remove();
force.nodes()。push(node);
force.start();
});

在这里,我们构建新的数据点,并附加一个带有此数据点的属性x和y的圆。我转换节点半径,然后出去,然后删除()它。这里是我不遵循的位 - 在删除它之前,数据点被添加到force.nodes()数组,而不是圆本身,只是数据点。


  1. 为什么在将数据点推入force.nodes()数组之前删除圆。

  2. 为什么我们只将数据点推入force.nodes()数组,它不需要引用圆圈?

  3. 为什么我们每次移动鼠标时都会启动force()函数?我认为force()在后台跳过,并且不需要在追加每个节点后重新启动。

UPDATE :我认为我最终寻求清楚的是force()布局实际上是在底层做什么。



理论:你给力布局一个数组节点。对于每个数据元素,x和y被提供或任意分配。一旦力开始,不断地重新计算阵列,以根据所施加的附加力性质(例如重力和电荷)移动那些x和y分量。力布局与圆圈本身的可视化无关 - 您必须继续绘制/刷新它们的x和y位置,以反映强制操作的数组值的位置。



是否正确?

解决方案

我想这只是一个简单的方法,不是一个真正好的学习示例...
一方面,节点数据从不删除,其次,该方法有点命令,而不是真正的数据驱动。



此演示中的节点数是节点数组的长度属性



  var w = 900,h = 400,nodes = [],indx = 0,show = false,svg = d3.select(body)append(svg).attr(width,w).attr (height,h),force = d3.layout.force().nodes(nodes).size([w,h]).gravity(0).charge(1).friction(0.7),outputDiv = d3 .select(body)。insert(div,svg)。attr(id,output); $(#toggleShow)。click(function(e){d3.selectAll(。dead)。attr(opacity,(show =!show)?0.2:0)$(this).text show?do not:)+show dead nodes)}); $(#clear)。click(function(e){nodes.length = 0; d3.selectAll(circle)。remove();}); force.on(tick,function(e){outputDiv.text(alpha:\t+ d3.format(。3f)(force.alpha())+\tnodes:\t + force.nodes()。length)var circles = svg.selectAll(circle)。data(nodes,function(d){return d.id})// ENTER // direct // data is there,圆已经通过完成过渡删除//用死一个//替换以前的活节点//惯用//总是零大小circle.enter()。append(circle).attr(r,4.5).attr (class,dead).attr(opacity,show?0.2:0); // UPDATE + ENTER circle .attr(cx,function(d){return dx;}).attr cy,function(d){return dy;});}); (moosemove,onMove).on(touchmove,onMove).on(touchstart,onMove); function onMove(){d3.event.preventDefault(); d3.event.stopPropagation(); updateMethod.call(this)} function direct(){return function(){var pointM = d3.mouse(this),pointT = d3.touches(this),point = pointT.length? pointT [0]:pointM,node = {x:point [0],y:point [1],id:indx ++}; svg.append(circle).data([node]).attr(class,alive).attr(r,1e-6).transition().attr(r,4.5) .transition().delay(1000).attr(r,1e-6).remove(); force.nodes()。push(node);开始生效(); }} / * direct * / updateMethod = direct();  

  body,html {width:100%;高度:100%; } #vizcontainer {width:100%;高度:100%; } svg {outline:1px solid red; width:100%;高度:100%; } #output {pointer-events:none; display:inline-block; z-index:1; margin:10px; } button {display:inline-block; margin:10px; } .dead {fill:white;中风:黑色; stroke-width:1px; }  

 < button id =toggleShowname = >显示死节点< / button> < button id =clearname =clear> clear< / button>< script src =https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min。 js>< / script>< script src =https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js>< / script& code> 



即使节点已被转换移除,在节点数组中,因此,仍然在力计算中起作用。你可以看到,作为一种黑洞效应,随着(数据)节点的计数建立:由于不可见节点的增长簇,开始开始开发。



回答您的问题...


  1. 为什么不?它的工作方式...

  2. 正如你在更新中提到的,布局只提供对象的位置,没有对对象本身的引用。

  3. 如果您使用浏览器开发人员工具查看 force.nodes()返回的数组元素, code>,你会看到有很多的状态添加超过原来的 x y 成员,在 d3.force 对象中也有状态关闭,如距离,强度和收费。所有这些都必须在某处设置,并不奇怪,它在 force.start()中完成。所以这就是为什么你每次更改数据的结构时都必须调用 force.start()。这真的不难跟踪这个东西下来,如果你RTFC,这就是你怎么知道什么在下面。

在模式方面,这将更适用于d3 ...



 ;(function(){var w = 900,h = 400,nodes = [],touch,svg = d3.select(#vizcontainer)append(svg)。 attr(width,w).attr(height,h),force = d3.layout.force().size([w,h])。 ),outputDiv = d3.select(body)。insert(div,#vizcontainer)。attr(id,output)。attr(class,output),touchesDiv = d3 .select(body)。insert(div,#output)。attr(id,touches).style(margin-right,10px)。attr output); force.on(tick,function(e){outputDiv.text(alpha:\t+ d3.format(。3f)(force.alpha tnodes:\t+ force.nodes()。length)svg.selectAll(circle).attr(cx,function(d){return dx; }).attr(cy,function(d){return d.y;}); }); svg.on(mousemove,onMove); svg.on(touchmove,onTouch); svg.on(touchstart,onTouch); function onMove(){updateMethod.call(this)} function onTouch(){d3.event.preventDefault(); d3.event.stopPropagation(); updateMethod.call(this)} function idiomatic(){force.nodes(nodes); return function(){var pointM = d3.mouse(this),pointT = d3.touches(this),point = pointT.length? pointT [0]:pointM,node = {x:point [0],y:point [1]}; //touchesDiv.text(pointT.length?pointT:mouse); nodes.push(node); svg.selectAll(circle).data(nodes).enter()。append(circle).attr(r,1e-6).transition(in).attr(r,4.5 ).transition(out).delay(1000).attr(r,1e-6).remove().each(end.out,(function(n){return function(d,i) {//console.log(\"length:+ nodes.length +\tdeleting+ i)var i = nodes.indexOf(n); nodes.splice(i,1)}}开始生效(); }} / * idiomatic * / updateMethod = idiomatic(); })() 

 body,html {width:100% ;高度:100%; } #vizcontainer {width:100%;高度:100%; } svg {outline:1px solid red; width:100%;高度:100%; } .output {pointer-events:none; display:inline-block; z-index:1; margin:10px; }  

 < script src =https:// cdnjs .cloudflare.com / ajax / libs / d3 / 3.4.11 / d3.min.js>< / script> < div id =vizcontainer>< / div>  



这是常用的一般更新模式的子集。然而,在这种情况下,仅考虑进入选择,因为数据仅驱动该阶段。退出行为被预编程到转换中,因此这是一种特殊情况,并且数据清除需要由转换的时序驱动。使用 end 事件是一种方法。重要的是要注意,每个节点都有自己的单独转换,所以在这种情况下它很好地工作。



是的,你的理论是正确的。


I've put together the following jfiddle based on some code I've seen in a book - http://jsfiddle.net/hiwilson1/o3gwejbx/2. Broadly speaking I follow what's happening, but there's a few aspects I don't follow.

svg.on("mousemove", function () {

    var point = d3.mouse(this), 
        node = {x: point[0], y: point[1]}; 

    svg.append("circle")
        .data([node])
            .attr("r", 1e-6)
            .transition()
            .attr("r", 4.5)
            .transition()
            .delay(1000)
            .attr("r", 1e-6)
            .remove();
        force.nodes().push(node); 
        force.start(); 
});

Here we build our new data point and append a circle with attributes x and y of this data point. I transition the nodes radius in and then out and then remove() it. Here's the bit I don't follow - BEFORE removing it the data point is added to the force.nodes() array, not the circle itself, just the data point. I then start() the force.

  1. Why are we removing the circle BEFORE pushing the data point into the force.nodes() array.
  2. Why are we pushing just the data point into the force.nodes() array, does it not need a reference to the circle? Or is the circle somehow just the visual representation of the data point and manipulating the data point manipulates the circle?
  3. Why are we starting the force() each time we move the mouse? I thought the force() was ticking over in the background and didn't need to be restarted after appending every single node?

UPDATE: I think what I ultimately am looking for clarity on is what the force() layout is actually doing under the hood.

Theory: You give the force layout an array of nodes. For each data element, the x and y is either provided or arbitrarily assigned. Once the force is started, the array is constantly recalculated to move those x and y components according to the additional force properties applied, such as gravity and charge. The force layout has nothing to do with the visualisation of the circles themselves - you have to keep drawing them / refreshing their x and y locations to reflect the positions of the array values that the force is manipulating.

Is any of that correct?

解决方案

I guess it's just a compact way of doing it but, not a really good learning example... For one thing, the nodes data are never removed and secondly, the method is a bit imperative and not really data driven.

The nodes number in this demo is the length property of the nodes array

		var w = 900, h = 400, nodes = [],
						indx = 0, show = false,

		svg = d3.select("body").append("svg")
		.attr("width", w)
		.attr("height", h),

		force = d3.layout.force()
		.nodes(nodes)
		.size([w, h])
		.gravity(0)
		.charge(1)
		.friction(0.7),

		outputDiv = d3.select("body").insert("div", "svg").attr("id", "output");

		$("#toggleShow").click(function (e) {
			d3.selectAll(".dead").attr("opacity", (show = !show) ? 0.2 : 0)
			$(this).text((show ? "don't " : "") + "show dead nodes")
		});
		$("#clear").click(function (e) {
			nodes.length = 0;
			d3.selectAll("circle").remove();
		});


		force.on("tick", function (e) {
			outputDiv.text("alpha:\t" + d3.format(".3f")(force.alpha())
				+ "\tnodes:\t" + force.nodes().length)
			var circles = svg.selectAll("circle").data(nodes, function (d) { return d.id })

			//ENTER
			//  direct
			//    data is there but the circle has been deleted by completion of transition
			//    replace the previously live node with a dead one
			//  idiomatic
			//    always zero size
			circles.enter().append("circle")
				.attr("r", 4.5)
				.attr("class", "dead")
			.attr("opacity", show ? 0.2 : 0);
			//UPDATE+ENTER
			circles
				.attr("cx", function (d) { return d.x; })
				.attr("cy", function (d) { return d.y; });
		});

		svg.on("mousemove", onMove)
		.on("touchmove", onMove)
		.on("touchstart", onMove);

		function onMove() {
		  d3.event.preventDefault();
		  d3.event.stopPropagation();
		  updateMethod.call(this)
		}

		function direct() {
			return function () {
			  var pointM = d3.mouse(this), pointT = d3.touches(this),
						point = pointT.length ? pointT[0] : pointM,
						node = { x: point[0], y: point[1], id: indx++ };

				svg.append("circle")
							.data([node])
									.attr("class", "alive")
									.attr("r", 1e-6)
									.transition()
									.attr("r", 4.5)
									.transition()
									.delay(1000)
									.attr("r", 1e-6)
									.remove();
				force.nodes().push(node);
				force.start();
			}
		} /*direct*/


		updateMethod = direct();

	body, html {
			width:100%;
			height:100%;
	}
		#vizcontainer {
			width: 100%;
			height: 100%;
		}

	 svg {
			outline: 1px solid red;
			width: 100%;
			height: 100%;
		}

		#output {
			pointer-events: none;  
			display: inline-block;
			z-index: 1;
			margin: 10px;
		}

		button {
			display: inline-block;
			margin: 10px;
		}
		.dead {
			fill: white;
			stroke: black;
			stroke-width: 1px;
		}

<button id="toggleShow" name="">show dead nodes</button>
	<button id="clear" name="clear">clear</button>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Even though the nodes have been removed by the transitions, they are still there in the nodes array and therefore, still acting in the force calculation. You can see that as a sort of a black hole effect as the count of (data) nodes builds up: a sink starts to develop due to the growing clump of invisible nodes.

In answer to your questions...

  1. Why not? It works either way...
  2. As you mention in your update, the layout is only providing the positions of the objects and has no reference to the objects themselves. Yes, that's up to you to manage.
  3. If you use the browser developer tools to look at the elements of the array returned by force.nodes(), you will see that there is a lot of state added over and above the original x and y members, there is also state closured in the d3.force object such as distances, strengths and charges. All this has to be set up somewhere and not surprisingly, it's done in force.start(). So that's why you have to call force.start() every time you change the structure of the data. It's really not hard to track this stuff down if you RTFC, that's how you find out what's under the hood.

in terms of patterns, this would be more idiomatic for d3...

    ;(function() {
      var w = 900, h = 400, nodes = [], touch,

          svg = d3.select("#vizcontainer").append("svg")
          .attr("width", w)
          .attr("height", h),

          force = d3.layout.force()
          .size([w, h])
          .gravity(0)
          .charge(1)
          .friction(0.7),

          outputDiv = d3.select("body").insert("div", "#vizcontainer").attr("id", "output").attr("class", "output"),
          touchesDiv = d3.select("body").insert("div", "#output").attr("id", "touches")
          .style("margin-right", "10px").attr("class", "output");


      force.on("tick", function (e) {

        outputDiv.text("alpha:\t" + d3.format(".3f")(force.alpha())
          + "\tnodes:\t" + force.nodes().length)

        svg.selectAll("circle")
        .attr("cx", function (d) { return d.x; })
        .attr("cy", function (d) { return d.y; });
      });

      svg.on("mousemove", onMove);
      svg.on("touchmove", onTouch);
      svg.on("touchstart", onTouch);

      function onMove() {
        updateMethod.call(this)
      }
      function onTouch() {
        d3.event.preventDefault();
        d3.event.stopPropagation();
        updateMethod.call(this)
      }

      function idiomatic() {
        force.nodes(nodes);
        return function () {
          var pointM = d3.mouse(this), pointT = d3.touches(this),
              point = pointT.length ? pointT[0] : pointM,
          node = { x: point[0], y: point[1] };

          //touchesDiv.text(pointT.length ? pointT : "mouse");

          nodes.push(node);

          svg.selectAll("circle")
          .data(nodes)
          .enter().append("circle")
          .attr("r", 1e-6)
          .transition("in")
          .attr("r", 4.5)
          .transition("out")
          .delay(1000)
          .attr("r", 1e-6)
          .remove()
          .each("end.out", (function (n) {
            return function (d, i) {
              //console.log("length: " + nodes.length + "\tdeleting " + i)
              var i = nodes.indexOf(n);
              nodes.splice(i, 1)
            }
          })(node));

          force.start();
        }
      } /*idiomatic*/


      updateMethod = idiomatic();
    })()

  body, html {
      width:100%;
      height:100%;
  }
    #vizcontainer {
      width: 100%;
      height: 100%;
    }

   svg {
      outline: 1px solid red;
      width: 100%;
      height: 100%;
    }

    .output {
      pointer-events: none;  
      display: inline-block;
      z-index: 1;
      margin: 10px;
    }

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
  <div id="vizcontainer"></div>

This is a subset of the often mentioned general update pattern. In this case however, only the enter selection is considered, because the data is driving this phase only. Exit behaviour is pre-programmed into the transitions, so this is a special case and the data clean-up needs to be driven by the timing of the transitions. Using the end event is one way to do that. It's important to note that each node has its own individual transition, so that it works out nicely in this case.

And yes, your theory is correct.

这篇关于d3用力添加和删除节点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆