d3 强制添加和删除节点

2024-04-21

我根据我在书中看到的一些代码整理了以下 jfiddle -http://jsfiddle.net/hiwilson1/o3gwejbx/2 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()在后台滴答作响,并且在附加每个节点后不需要重新启动?

更新:我认为我最终想要弄清楚的是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 ? "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>

即使节点已被转换删除,它们仍然存在于nodes数组,因此仍在力计算中起作用。随着(数据)节点数量的增加,您可以看到这是一种黑洞效应:由于不可见节点的不断增长,汇开始发展。

回答你的问题...

  1. 为什么不?无论哪种方式都有效...
  2. 正如您在更新中提到的,布局仅提供对象的位置,而不引用对象本身。是的,这取决于你来管理。
  3. 如果使用浏览器开发者工具查看返回的数组元素force.nodes(),你会看到在原始状态之上添加了很多状态x and 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])
          .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>

这是经常提到的一般更新模式的子集。然而,在这种情况下,仅考虑输入选择,因为数据仅驱动该阶段。退出行为被预先编程到转换中,因此这是一种特殊情况,数据清理需要由转换的时序驱动。使用end事件是做到这一点的一种方式。值得注意的是,每个节点都有自己单独的转换,因此在这种情况下效果很好。

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

d3 强制添加和删除节点 的相关文章

随机推荐