在强制布局中添加链接时,基于内容BBox创建节点并获取源节点的宽度

时间:2015-07-15 20:40:58

标签: d3.js font-awesome force-layout

我有一个d3力布局,其中一条线连接一个角色。我的代码目前看起来像这样......

$('.best_in_place').bind("ajax:success", function(){
    $(.'div').load(location.href+" .div>*","");
});

当我使用它时,我会看到以下内容...

Smile

你可以看到所有关系都在底部相遇,但是,我希望他们在左边见面。为此,我想我需要改为......

edges.selectAll("line")
          .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; });

<g class="edge"><line x1="183.7429436753079" y1="-3182.732396966405" x2="-224.94046319279028" y2="-2920.273406745797" style="stroke: rgb(255, 255, 255); stroke-width: 1px;"></line><text fill="white" x="-20.59875975874118" y="-3051.502901856101"></text></g>

<g class="node" transform="translate(4109.590685978889,2004.5469511133144)"><text fill="white"></text><text fill="white" y="15">Interior</text></g>

其中r是半径。如果没有保持所有节点宽度的数组,有没有办法获取源和目标的宽度?

1 个答案:

答案 0 :(得分:1)

您可以将BBox添加到节点数据上作为参考,同时将文本元素添加到文档中。如果首先添加节点,然后是链接,则可以通过在数据元素上添加节点元素的引用,将信息从前者传输到后者。您还可以在数据数组元素上添加可能需要的任何其他有用的定位状态。之后,您可以通过d.sourced.target在强制勾选回拨中访问您需要的任何内容。

使用字体真棒字体我注意到边界框至少需要一个动画周期才能演变成正确的形状所以我必须添加一个动画计时器,在初始渲染后运行几(10)次来更新边界框a几次并重新定位字形以使其正确居中。

<强> 修改
使边界框调整永久(不只是运行10次)来解决webkit中的错误,其中字形对齐在缩放事件中中断。但这会导致moz出现问题,因此需要找到另一种方法来修复webkit中的缩放错误。

  

<强> 注意
  从该数据元素引用数据绑定的svg元素,创建循环引用。因此需要特别注意打破参考链。在下面的示例中,在将所需状态复制到数据元素后,将删除BBox引用。

工作示例

&#13;
&#13;
//debug panel/////////////////////////////////////////////////////////////////////////////
d3.select("#update").on("click", (function() {
	var dataSet = false;
	return function() {
		refresh(dataSets[(dataSet = !dataSet, +dataSet)])
	}
})());
var alpha = d3.select("#alpha").text("waiting..."),
		cog = d3.select("#wrapAlpha").insert("i", "#fdg").classed("fa fa-cog fa-spin", true),
		fdgInst = d3.select("#fdg");
elapsedTime = ElapsedTime("#panel", {margin: 0, padding: 0})
	.message(function (id) {
		return 'fps : ' + d3.format(" >8.3f")(1/this.aveLap())
	});
elapsedTime.consoleOn = false;
//////////////////////////////////////////////////////////////////////////////////
var dataSets = [
	{
		"nodes": [
			{"name": "node1", "content": "the first Node"},
			{"name": "node2", "content": "node2"},
			{"name": "node3", "content":{"fa": "fa/*-spin*/", text: "\uf013"}},
			{"name": "node4", "content":{"fa": "fa/*-spin*/", text: "\uf1ce"}}
		],
		"edges": [
			{"source": 2, "target": 0},
			{"source": 2, "target": 1},
			{"source": 2, "target": 3}
		]
	},
	{
		"nodes": [
			{"name": "node1", "content": "node1"},
			{"name": "node2", "content":{"fa": "fa/*-spin*/", text: "\uf1ce"}},
			{"name": "node3", "content":{"fa": "fa/*-spin*/", text: "\uf013"}},
			{"name": "node4", "content": "4"},
			{"name": "node5", "content": "5"},
			{"name": "node6", "content": "6"}
		],
		"edges": [
			{"source": 2, "target": 0},
			{"source": 2, "target": 1},
			{"source": 2, "target": 3},
			{"source": 2, "target": 4},
			{"source": 2, "target": 5}
		]
	}
];
var refresh = (function(){
	var instID = Date.now(),
			height = 160,
			width = 500,
			force = d3.layout.force()
				.size([width, height])
				.charge(-1000)
				.linkDistance(50)
				.on("end", function(){cog.classed("fa-spin", false); elapsedTime.stop()})
				.on("start", function(){cog.classed("fa-spin", true); elapsedTime.start()});

	return function refresh(data) {
		force
			.nodes(data.nodes)
			.links(data.edges)
			.on("tick", (function(instID) {
				return function(e) {
					elapsedTime.mark();
					alpha.text(d3.format(" >8.4f")(e.alpha));
					fdgInst.text("fdg instance: " + instID);
					lines.attr("x1", function(d) {
						return d.source.x + d.source.cx + d.source.r;
					}).attr("y1", function(d) {
						return d.source.y + d.source.cy;
					}).attr("x2", function(d) {
						return d.target.x + d.target.cx;
					}).attr("y2", function(d) {
						return d.target.y + d.target.cy;
					});
					node.attr("transform", function(d) {
						return "translate(" + [d.x, d.y] + ")"
					});
				}
			})(instID))
			.start();

		var svg = d3.select("body").selectAll("svg").data([data]);

		svg.enter().append("svg")
			.attr({height: height, width: width});

		var lines = svg.selectAll(".links")
					.data(linksData),
				linesEnter = lines.enter()
					.insert("line", d3.select("#nodes") ? "#nodes" : null)
					.attr("class", "links")
					.attr({stroke: "steelblue", "stroke-width": 3});

		var nodes = svg.selectAll("#nodes").data(nodesData),
				nodesEnter = nodes.enter().append("g")
					.attr("id", "nodes"),
				node = nodes.selectAll(".node")
					.data(id),
				newNode = node.enter().append("g")
					.attr("class","node")
					.call(force.drag);
		newNode.append("text")
			.attr({class: "content", fill: "steelblue"})
		newNode.insert("circle", ".node .content");

		var glyphs = node.select("text")
					.each(function(d) {
						var node = d3.select(this);
						if(d.content.fa)
							node.style({'font-family': 'FontAwesome', 'font-size': '32px', 'dominant-baseline': 'central'})
								.classed(d.content.fa, true)
								.text(d.content.text);
						else
							node.text(d.content)
								.attr({"class": "content", style: null});
					})
					.call(getBB),
				backGround  = node.select("circle").each(function(d) {
				d3.select(this).attr(makeCircleBB(d))
			}).style({"fill": "red", opacity: 0.8});

		(function(id){
			//adjust the bounding box after the font loads
			var count = 0;
			d3.timer(function() {
				console.log(id);
				glyphs.call(getBB);
				backGround.each(function(d) {
					d3.select(this).attr(makeCircleBB(d))
				});
				return /*false || id != instID*/++count > 10;	//needs to keep running due to webkit zoom bug
			})
		})(instID);

		lines.exit().remove();
		node.exit().remove();

		function nodesData(d) {
			return [d.nodes];
		}

		function linksData(d) {
			return d.edges;
		}
	};
	function getBB(selection) {
		this.each(function(d) {
			d.bb = this.getBBox();
		})
	}

	function makeCircleBB(d, i, j) {
		var bb = d.bb;
		d.r = Math.max(bb.width, bb.height) / 2;
		delete d.bb; //plug potential memory leak!
		d.cy = bb.height / 2 + bb.y;
		d.cx = bb.width / 2;
		return {
			r: d.r, cx: d.cx, cy: d.cy, height: bb.height, width: bb.width
		}
	}
	function id(d) {
		return d;
	}

})();
refresh(dataSets[0]);
&#13;
svg {
      outline: 1px solid #282f51;
      pointer-events: all;
      overflow: visible;
    }

    g.outline {
      outline: 1px solid red;
    }

    #panel div {
      display: inline-block;
      margin: 0 .25em 3px 0;
    }

    #panel div div {
      white-space: pre;
      margin: 0 .25em 3px 0;
    }

    div#inputDiv {
      white-space: normal;
      display: inline-block;
    }

    .node {
      cursor: default;
    }

    .content {
      transform-origin: 50% 50%;
&#13;
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://rawgit.com/cool-Blue/d3-lib/master/elapsedTime/elapsed-time-1.0.js"></script>
<div id="panel">
  <div id="inputDiv">
    <input id="update" type="button" value="update">
  </div>
  <div id="wrapAlpha">alpha:<div id="alpha"></div></div>
  <div id="fdg"></div>
</div>
<div id="viz"></div>
&#13;
&#13;
&#13;