D3 Canvas 强制布局 - 脱节组排斥另一组
D3 Canvas force layout - disjointed group replelling the other group
我有一组节点(A、B、C、D、E、F、G、H、I、J、K)但在 2 个不相交的组(A、B、C)中相互连接和 (D,E,F,G,H,I,J,K):
<!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", "1");
addNodeCanvas("B", "6");
addNodeCanvas("C", "4");
addNodeCanvas("D", "2");
addNodeCanvas("E", "3");
addNodeCanvas("F", "1");
addNodeCanvas("G", "1");
addNodeCanvas("H", "1");
addNodeCanvas("I", "1");
addNodeCanvas("J", "1");
addNodeCanvas("K", "1");
addLinkCanvas("A","B");
addLinkCanvas("A","C");
addLinkCanvas("E","D");
addLinkCanvas("F","D");
addLinkCanvas("G","D");
addLinkCanvas("H","D");
addLinkCanvas("I","D");
addLinkCanvas("J","D");
addLinkCanvas("K","D");
refresh();
}
var nodeColors = d3.scaleOrdinal().range(d3.schemeCategory20);
function addNodeCanvas(nodeName,g) {
var node = {
x: 400,
y: 400,
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() {
}
function dragged() {
}
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>
由于这 2 组在加载页面后脱节,它们开始相互排斥并继续推动直到到达 canvas 的末尾。
如何避免这件事,使 2 组彼此远离但仍保持在 canvas 的中心附近。
Please try it running a few times, and you will see nodes A,B & C getting pushed to the end and remain there in a straight line.
问题在于,一旦施加了初始排斥力,就没有其他力起作用来抵消它。所以运动一直持续到 canvas 容器的边界,节点在那里撞到墙上(相当于 normal force 并带来平衡)。
要使无限排斥运动停止,您需要一个viscosity force,它会逐渐减慢节点组的速度,直到它们达到零速度。
d3 的强制布局为此提供了一个参数:velocityDecay:
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))
.velocityDecay(0.8); // the velocity decay setting
您可以使用 velocityDecay
的值,它可以介于 0 和 1 之间,其中 0 相当于不应用设置(无粘度),1 相当于具有法向力且没有移动开始。
例如粘度为0.8:
<!DOCTYPE html>
<html>
<body onload="connect1();">
<canvas width="500" height="350"></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", "1");
addNodeCanvas("B", "6");
addNodeCanvas("C", "4");
addNodeCanvas("D", "2");
addNodeCanvas("E", "3");
addNodeCanvas("F", "1");
addNodeCanvas("G", "1");
addNodeCanvas("H", "1");
addNodeCanvas("I", "1");
addNodeCanvas("J", "1");
addNodeCanvas("K", "1");
addLinkCanvas("A","B");
addLinkCanvas("A","C");
addLinkCanvas("E","D");
addLinkCanvas("F","D");
addLinkCanvas("G","D");
addLinkCanvas("H","D");
addLinkCanvas("I","D");
addLinkCanvas("J","D");
addLinkCanvas("K","D");
refresh();
}
var nodeColors = d3.scaleOrdinal().range(d3.schemeCategory20);
function addNodeCanvas(nodeName,g) {
var node = {
x: width / 2,
y: height / 2,
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))
.velocityDecay(0.8);
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() {
}
function dragged() {
}
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>
forceCenter
看起来不够强,所以我用一组forceX()
和forceY()
力量代替了它。
通过为多体设置最大距离,推力受到限制,通过增加力量,它仍然在近距离排斥。
您指定了 link id
函数,但您设置的 source
和 target
不等于 id
。
<!DOCTYPE html>
<html>
<body onload="connect1();">
<canvas width="400" height="400"></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 connect2(){
addNodeCanvas("A", "1");
addNodeCanvas("B", "6");
addNodeCanvas("C", "4");
addNodeCanvas("D", "2");
addNodeCanvas("E", "3");
addNodeCanvas("F", "1");
addLinkCanvas("A","B");
addLinkCanvas("A","C");
addLinkCanvas("E","D");
addLinkCanvas("F","D");
refresh();
}
function connect1(){
addNodeCanvas("A", "1");
addNodeCanvas("B", "6");
addNodeCanvas("C", "4");
addNodeCanvas("D", "2");
addNodeCanvas("E", "3");
addNodeCanvas("F", "1");
addNodeCanvas("G", "1");
addNodeCanvas("H", "1");
addNodeCanvas("I", "1");
addNodeCanvas("J", "1");
addNodeCanvas("K", "1");
addLinkCanvas("A","B");
addLinkCanvas("A","C");
addLinkCanvas("E","D");
addLinkCanvas("F","D");
addLinkCanvas("G","D");
addLinkCanvas("H","D");
addLinkCanvas("I","D");
addLinkCanvas("J","D");
addLinkCanvas("K","D");
refresh();
}
var nodeColors = d3.scaleOrdinal().range(d3.schemeCategory20);
function addNodeCanvas(nodeName,g) {
var node = {
x: 400,
y: 400,
id: nodeName,
grp:g
};
var n = nodes.push(node);
}
function addLinkCanvas_1(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 addLinkCanvas(idSrc, idTarget) {
if (idSrc != idTarget) {
links.push({
source: idSrc,
target: idTarget
});
};
}
function refresh() {
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody().distanceMax(70).strength(d=>-50))
// .force("center", d3.forceCenter(width*0.5, height*0.5))
.force("centerX", d3.forceX(width*0.5).strength(0.02))
.force("centerY", d3.forceY(height*0.5).strength(0.02));
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() {
}
function dragged() {
}
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>
我有一组节点(A、B、C、D、E、F、G、H、I、J、K)但在 2 个不相交的组(A、B、C)中相互连接和 (D,E,F,G,H,I,J,K):
<!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", "1");
addNodeCanvas("B", "6");
addNodeCanvas("C", "4");
addNodeCanvas("D", "2");
addNodeCanvas("E", "3");
addNodeCanvas("F", "1");
addNodeCanvas("G", "1");
addNodeCanvas("H", "1");
addNodeCanvas("I", "1");
addNodeCanvas("J", "1");
addNodeCanvas("K", "1");
addLinkCanvas("A","B");
addLinkCanvas("A","C");
addLinkCanvas("E","D");
addLinkCanvas("F","D");
addLinkCanvas("G","D");
addLinkCanvas("H","D");
addLinkCanvas("I","D");
addLinkCanvas("J","D");
addLinkCanvas("K","D");
refresh();
}
var nodeColors = d3.scaleOrdinal().range(d3.schemeCategory20);
function addNodeCanvas(nodeName,g) {
var node = {
x: 400,
y: 400,
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() {
}
function dragged() {
}
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>
由于这 2 组在加载页面后脱节,它们开始相互排斥并继续推动直到到达 canvas 的末尾。
如何避免这件事,使 2 组彼此远离但仍保持在 canvas 的中心附近。
Please try it running a few times, and you will see nodes A,B & C getting pushed to the end and remain there in a straight line.
问题在于,一旦施加了初始排斥力,就没有其他力起作用来抵消它。所以运动一直持续到 canvas 容器的边界,节点在那里撞到墙上(相当于 normal force 并带来平衡)。
要使无限排斥运动停止,您需要一个viscosity force,它会逐渐减慢节点组的速度,直到它们达到零速度。
d3 的强制布局为此提供了一个参数:velocityDecay:
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))
.velocityDecay(0.8); // the velocity decay setting
您可以使用 velocityDecay
的值,它可以介于 0 和 1 之间,其中 0 相当于不应用设置(无粘度),1 相当于具有法向力且没有移动开始。
例如粘度为0.8:
<!DOCTYPE html>
<html>
<body onload="connect1();">
<canvas width="500" height="350"></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", "1");
addNodeCanvas("B", "6");
addNodeCanvas("C", "4");
addNodeCanvas("D", "2");
addNodeCanvas("E", "3");
addNodeCanvas("F", "1");
addNodeCanvas("G", "1");
addNodeCanvas("H", "1");
addNodeCanvas("I", "1");
addNodeCanvas("J", "1");
addNodeCanvas("K", "1");
addLinkCanvas("A","B");
addLinkCanvas("A","C");
addLinkCanvas("E","D");
addLinkCanvas("F","D");
addLinkCanvas("G","D");
addLinkCanvas("H","D");
addLinkCanvas("I","D");
addLinkCanvas("J","D");
addLinkCanvas("K","D");
refresh();
}
var nodeColors = d3.scaleOrdinal().range(d3.schemeCategory20);
function addNodeCanvas(nodeName,g) {
var node = {
x: width / 2,
y: height / 2,
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))
.velocityDecay(0.8);
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() {
}
function dragged() {
}
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>
forceCenter
看起来不够强,所以我用一组forceX()
和forceY()
力量代替了它。
通过为多体设置最大距离,推力受到限制,通过增加力量,它仍然在近距离排斥。
您指定了 link id
函数,但您设置的 source
和 target
不等于 id
。
<!DOCTYPE html>
<html>
<body onload="connect1();">
<canvas width="400" height="400"></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 connect2(){
addNodeCanvas("A", "1");
addNodeCanvas("B", "6");
addNodeCanvas("C", "4");
addNodeCanvas("D", "2");
addNodeCanvas("E", "3");
addNodeCanvas("F", "1");
addLinkCanvas("A","B");
addLinkCanvas("A","C");
addLinkCanvas("E","D");
addLinkCanvas("F","D");
refresh();
}
function connect1(){
addNodeCanvas("A", "1");
addNodeCanvas("B", "6");
addNodeCanvas("C", "4");
addNodeCanvas("D", "2");
addNodeCanvas("E", "3");
addNodeCanvas("F", "1");
addNodeCanvas("G", "1");
addNodeCanvas("H", "1");
addNodeCanvas("I", "1");
addNodeCanvas("J", "1");
addNodeCanvas("K", "1");
addLinkCanvas("A","B");
addLinkCanvas("A","C");
addLinkCanvas("E","D");
addLinkCanvas("F","D");
addLinkCanvas("G","D");
addLinkCanvas("H","D");
addLinkCanvas("I","D");
addLinkCanvas("J","D");
addLinkCanvas("K","D");
refresh();
}
var nodeColors = d3.scaleOrdinal().range(d3.schemeCategory20);
function addNodeCanvas(nodeName,g) {
var node = {
x: 400,
y: 400,
id: nodeName,
grp:g
};
var n = nodes.push(node);
}
function addLinkCanvas_1(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 addLinkCanvas(idSrc, idTarget) {
if (idSrc != idTarget) {
links.push({
source: idSrc,
target: idTarget
});
};
}
function refresh() {
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody().distanceMax(70).strength(d=>-50))
// .force("center", d3.forceCenter(width*0.5, height*0.5))
.force("centerX", d3.forceX(width*0.5).strength(0.02))
.force("centerY", d3.forceY(height*0.5).strength(0.02));
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() {
}
function dragged() {
}
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>