我正在向画布手动添加一些节点,然后添加一些链接。我期望该网络会立即出现,但不会出现。经过数小时的调查,我发现当刚添加节点时,添加链接会将节点的x
,y
属性选择为Nan
,并使节点消失。
因此,我添加了一个refresh
按钮,以便在节点沉降后可以稍后手动刷新网络。
是否有更好的方法来解决此问题?我有数百个节点是实时发送的,因此最初,我什么都没有,后来外部服务将调用addNodeCanvas
和addLinkCanvas
。
运行以下代码时,最初不会显示任何内容,几秒钟后按refresh
按钮将显示网络。
编辑
根据下面Shashank的回答,我摆脱了onload
功能。
请检查此jsfiddle,摆脱onload
;我在链接节点后立即调用refresh方法,但是除非节点安定下来,否则什么都不会显示。
请与此jsfiddle进行比较,我删除了所有链接,现在节点立即显示。
我发现,刚添加节点时,添加链接会将节点的x
,y
属性选择为Nan
,并使节点消失。
原始代码:
<!DOCTYPE html>
<html>
<body onload="connect1();">
<canvas width="300" height="100"></canvas>
<button id="ref" onclick="refresh()">refresh </button>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var canvas = document.querySelector("canvas"),
context = canvas.getContext("2d"),
width = canvas.width,
height = canvas.height;
var links = [],
nodes = [];
var graph = {
nodes,
links
},
wsConn;
refresh();
function connect1() {
addNodeCanvas("A");
addNodeCanvas("B");
addNodeCanvas("C");
addNodeCanvas("D");
addNodeCanvas("E");
addLinkCanvas("A", "B");
addLinkCanvas("A", "C");
addLinkCanvas("D", "C");
addLinkCanvas("E", "D");
addLinkCanvas("E", "B");
}
function addNodeCanvas(nodeName, g) {
var node = {
x: 100,
y: 100,
id: nodeName,
grp: g
};
var n = nodes.push(node);
//console.log(node);
refresh();
}
function addLinkCanvas(idSrc, idTarget) {
if (idSrc != idTarget) {
var s = {},
t = {};
nodes.forEach(function(curNode) {
if (typeof curNode.id != "undefined") {
if (curNode.id == idSrc) {
s = curNode;
}
if (curNode.id == idTarget) {
t = curNode;
}
}
});
//console.log( { s,t});
links.push({
source: s,
target: t
});
};
}
function refresh() {
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.id;
}))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
simulation
.nodes(nodes)
.on("tick", ticked)
.force("link")
.links(links);
d3.select(canvas)
.call(d3.drag()
.container(canvas)
.subject(dragsubject)
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
function ticked() {
var margin = 20;
nodes.forEach(function(d) {
d.x = Math.max(margin, Math.min(width - margin, d.x))
d.y = Math.max(margin, Math.min(height - margin, d.y))
});
function dblclick() {
nodes.forEach(function(d) {
d.fx = d.fy = null;
})
};
context.clearRect(0, 0, width, height);
context.beginPath();
links.forEach(drawLink);
context.strokeStyle = "#aaa";
context.stroke();
context.beginPath();
nodes.forEach(drawNode);
}
function dragsubject() {
return simulation.find(d3.event.x, d3.event.y);
}
var clickDate = new Date();
var difference_ms;
function dragstarted() {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d3.event.subject.fx = Math.max(10, Math.min(width - 10, d3.event.subject.x));
d3.event.subject.fy = Math.max(10, Math.min(height - 10, d3.event.subject.y));
}
function dragged() {
d3.event.subject.fx = Math.max(10, Math.min(width - 10, d3.event.x));
d3.event.subject.fy = Math.max(10, Math.min(height - 10, d3.event.y));
}
function dragended() {}
function drawLink(d) {
context.moveTo(d.source.x, d.source.y);
context.lineTo(d.target.x, d.target.y);
}
var nodeColors = d3.scaleOrdinal().range(d3.schemeCategory20),
labelColors = d3.scaleOrdinal().range(['red', 'orange', 'blue', 'green', 'purple']);
function drawNode(d) {
context.beginPath();
context.moveTo(d.x + 10, d.y);
context.arc(d.x, d.y, 10, 0, 2 * Math.PI);
context.strokeStyle = "#fff";
context.stroke();
context.fillStyle = nodeColors(d.grp);
context.closePath();
context.fill();
context.beginPath();
context.font = (d.labelSize ? d.labelSize : 10) + 'px Arial';
context.fillStyle = labelColors(d.grp);
context.fillText(d.id ? d.id : d.grp, d.x, d.y);
context.closePath();
}
}
</script>
</body>
</html>
答案 0 :(得分:1)
问题是,在调用nodes
方法时,links
和refresh()
尚未添加到各自的数组中。
以下是onload
函数何时被调用的说明(在MSDN web docs中提到):
在文档加载过程结束时触发load事件。至此,文档中的所有对象都在DOM中,并且所有图像,脚本,链接和子框架均已完成加载。
因此,在设置了所有变量之后,刷新被调用的时间(在<script>
标记中,connect1
尚未被调用,这意味着没有节点,也没有链接,因此也没有图但是,当您单击刷新按钮时,connect1
已经执行,并且节点,链接已添加,从而导致绘制图形。
在许多解决方案中,这是一种方法:(在refresh
函数中调用connect1
)
<!DOCTYPE html>
<html>
<body onload="connect1();">
<canvas width="300" height="100"></canvas>
<!--button id="ref" onclick="refresh()">refresh </button-->
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var canvas = document.querySelector("canvas"),
context = canvas.getContext("2d"),
width = canvas.width,
height = canvas.height;
var links =[] , nodes = [] ;
var graph={nodes,links}, wsConn;
function connect1(){
addNodeCanvas("A");
addNodeCanvas("B");
addNodeCanvas("C");
addNodeCanvas("D");
addNodeCanvas("E");
addLinkCanvas("A","B");
addLinkCanvas("A","C");
addLinkCanvas("D","C");
addLinkCanvas("E","D");
addLinkCanvas("E","B");
refresh();
}
function addNodeCanvas(nodeName,g) {
var node = {
x: 100,
y: 100,
id: nodeName,
grp:g
};
var n = nodes.push(node);
}
function addLinkCanvas(idSrc, idTarget) {
if (idSrc != idTarget) {
var s = {},
t = {};
nodes.forEach(function(curNode) {
if (typeof curNode.id != "undefined") {
if (curNode.id == idSrc) {
s = curNode;
}
if (curNode.id == idTarget) {
t = curNode;
}
}
});
links.push({
source: s,
target: t
});
};
}
function refresh() {
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.id;
}))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
simulation
.nodes(nodes)
.on("tick", ticked)
.force("link")
.links(links);
d3.select(canvas)
.call(d3.drag()
.container(canvas)
.subject(dragsubject)
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
function ticked() {
var margin = 20;
nodes.forEach(function(d) {
d.x = Math.max(margin, Math.min(width - margin, d.x))
d.y = Math.max(margin, Math.min(height - margin, d.y))
});
function dblclick() {
nodes.forEach(function(d) {
d.fx = d.fy = null;
})
};
context.clearRect(0, 0, width, height);
context.beginPath();
links.forEach(drawLink);
context.strokeStyle = "#aaa";
context.stroke();
context.beginPath();
nodes.forEach(drawNode);
}
function dragsubject() {
return simulation.find(d3.event.x, d3.event.y);
}
var clickDate = new Date();
var difference_ms;
function dragstarted() {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d3.event.subject.fx = Math.max(10, Math.min(width - 10, d3.event.subject.x));
d3.event.subject.fy = Math.max(10, Math.min(height - 10, d3.event.subject.y));
}
function dragged() {
d3.event.subject.fx = Math.max(10, Math.min(width - 10, d3.event.x));
d3.event.subject.fy = Math.max(10, Math.min(height - 10, d3.event.y));
}
function dragended() {
}
function drawLink(d) {
context.moveTo(d.source.x, d.source.y);
context.lineTo(d.target.x, d.target.y);
}
var nodeColors = d3.scaleOrdinal().range(d3.schemeCategory20),
labelColors = d3.scaleOrdinal().range(['red', 'orange', 'blue', 'green', 'purple']);
function drawNode(d) {
context.beginPath();
context.moveTo(d.x + 10, d.y);
context.arc(d.x, d.y, 10, 0, 2 * Math.PI);
context.strokeStyle = "#fff";
context.stroke();
context.fillStyle = nodeColors(d.grp);
context.closePath();
context.fill();
context.beginPath();
context.font = (d.labelSize ? d.labelSize : 10) + 'px Arial';
context.fillStyle = labelColors(d.grp);
context.fillText(d.id ? d.id : d.grp, d.x, d.y);
context.closePath();
}
}
</script>
</body>
</html>
编辑:添加每个节点/链接后调用刷新:
此bl.ocks snippet有助于了解添加动态力布局所必须执行的操作,该布局清楚地表明设置仅需执行一次,但是仿真的节点/链接可以执行任意数量的操作次。另外,我正在使用.restart()
:
重新启动模拟的内部计时器并返回模拟。结合simulation.alphaTarget或simulation.alpha,此方法可用于在交互过程中(例如,拖动节点时)“重新加热”仿真,或在使用simulation.stop暂时将其暂停后恢复仿真。
这是文档链接:d3 simulation restart
我引用的示例是一个SVG,但其逻辑与画布几乎相同。
<!DOCTYPE html>
<html>
<body >
<canvas width="900" height="600"></canvas>
<button id="ref" onclick="refresh()">refresh </button>
<script src="https://d3js.org/d3.v4.js"></script>
<script>
var canvas = document.querySelector("canvas"),
context = canvas.getContext("2d"),
width = canvas.width,
height = canvas.height;
var links =[] , nodes = [] ;
var graph={nodes,links}, wsConn;
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.id;
}))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
refresh();
connect1();
function connect1(){
addNodeCanvas("A");
addNodeCanvas("B");
addNodeCanvas("C");
addNodeCanvas("D");
addNodeCanvas("E");
addLinkCanvas("A", "B");
addLinkCanvas("A", "C");
addLinkCanvas("D", "C");
addLinkCanvas("E", "D");
addLinkCanvas("E", "B");
refresh();
}
function addNodeCanvas(nodeName,g) {
var node = {
x: 100,
y: 100,
id: nodeName,
grp:g
};
var n = nodes.push(node);
//console.log(node);
refresh();
}
function addLinkCanvas(idSrc, idTarget) {
if (idSrc != idTarget) {
var s = {},
t = {};
nodes.forEach(function(curNode) {
if (typeof curNode.id != "undefined") {
if (curNode.id == idSrc) {
s = curNode;
}
if (curNode.id == idTarget) {
t = curNode;
}
}
});
//console.log( { s,t});
links.push({
source: s,
target: t
});
};
refresh();
}
function refresh() {
simulation
.nodes(nodes)
.on("tick", ticked);
simulation
.force("link")
.links(links);
simulation.alpha(1).restart();
d3.select(canvas)
.call(d3.drag()
.container(canvas)
.subject(dragsubject)
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
function ticked() {
var margin = 20;
nodes.forEach(function(d) {
d.x = Math.max(margin, Math.min(width - margin, d.x))
d.y = Math.max(margin, Math.min(height - margin, d.y))
});
function dblclick() {
nodes.forEach(function(d) {
d.fx = d.fy = null;
})
};
context.clearRect(0, 0, width, height);
context.beginPath();
links.forEach(drawLink);
context.strokeStyle = "#aaa";
context.stroke();
context.beginPath();
nodes.forEach(drawNode);
}
function dragsubject() {
return simulation.find(d3.event.x, d3.event.y);
}
var clickDate = new Date();
var difference_ms;
function dragstarted() {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d3.event.subject.fx = Math.max(10, Math.min(width - 10, d3.event.subject.x));
d3.event.subject.fy = Math.max(10, Math.min(height - 10, d3.event.subject.y));
}
function dragged() {
d3.event.subject.fx = Math.max(10, Math.min(width - 10, d3.event.x));
d3.event.subject.fy = Math.max(10, Math.min(height - 10, d3.event.y));
}
function dragended() {
if (!d3.event.active) simulation.alphaTarget(0);
// Time between 2 ends of drag:
difference_ms = (new Date()).getTime() - clickDate.getTime();
clickDate = new Date();
// if the time between these 2 ends of drag is short enough, then
// it's considered a double click:
if (difference_ms < 200) {
// And we can release the node:
simulation.alphaTarget(0.3).restart()
d3.event.subject.fx = null;
d3.event.subject.fy = null;
}
}
function drawLink(d) {
context.moveTo(d.source.x, d.source.y);
context.lineTo(d.target.x, d.target.y);
}
var nodeColors = d3.scaleOrdinal().range(d3.schemeCategory20),
labelColors = d3.scaleOrdinal().range(['red', 'orange', 'blue', 'green', 'purple']);
function drawNode(d) {
context.beginPath();
context.moveTo(d.x + 10, d.y);
context.arc(d.x, d.y, 10, 0, 2 * Math.PI);
context.strokeStyle = "#fff";
context.stroke();
context.fillStyle = nodeColors(d.grp);
context.closePath();
context.fill();
context.beginPath();
context.font = (d.labelSize ? d.labelSize : 10) + 'px Arial';
context.fillStyle = labelColors(d.grp);
context.fillText(d.id ? d.id : d.grp, d.x, d.y);
context.closePath();
}
}
</script>
</body>
</html>
这是jsfiddle。希望这会有所帮助。