javascript - 在力导向图上拖动闪烁
问题描述
我的 D3v4 图有问题,每次我拖动一个节点时,似乎 DOM 都想更快地重绘这个节点。我正在初始化 svg 区域和模拟。此外,我将大部分 enter().exit().remove() 逻辑放在自己的函数中,以避免冗余。
我感谢任何评论和提示。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Playground D3v4</title>
<!-- favcon -->
<link rel="icon" href="https://networkrepository.com/favicon.png">
<!-- call external d3.js framework -->
<script src="https://d3js.org/d3.v4.js"></script>
<!-- import multiselection framework -->
<script src="https://d3js.org/d3-selection-multi.v1.js"></script>
<!-- import "font awesome" stylesheet https://fontawesome.com/ -->
<script src="https://kit.fontawesome.com/39094309d6.js" crossorigin="anonymous"></script>
</head>
<style>
body {
overflow: hidden;
margin: 0px;
}
.canvas {
background-color: rgb(220, 220, 220);
}
.link {
stroke: rgb(0, 0, 0);
stroke-width: 1px;
}
circle {
fill: whitesmoke
}
.node {
stroke: white;
stroke-width: 2px
}
.tooltip {
font-family: "Open Sans", sans-serif;
position: absolute;
text-align: left;
background: rgb(245, 245, 245);
border: 2px;
border-radius: 6px;
border-color: rgb(255, 255, 255);
border-style: solid;
pointer-events: none;
line-height: 150%;
padding: 8px 10px;
}
#context-menu {
font-family: "Open Sans", sans-serif;
position: fixed;
z-index: 10000;
width: 190px;
background: whitesmoke;
border: 2px;
border-radius: 6px;
border-color: white;
border-style: solid;
transform: scale(0);
transform-origin: top left;
}
#context-menu.active {
transform: scale(1);
transition: transform 200ms ease-in-out;
}
#context-menu .item {
padding: 8px 10px;
font-size: 15px;
color: black;
}
#context-menu .item i {
display: inline-block;
margin-right: 5px;
}
#context-menu hr {
margin: 5px 0px;
border-color: whitesmoke;
}
#context-menu .item:hover {
background: lightblue;
}
</style>
<body>
<!-- right click context menu -->
<div id="context-menu">
<div id="addObject" class="item">
<i class="fa fa-plus-circle"></i> Add Node
</div>
<div id="removeObject" class="item">
<i class="fa fa-minus-circle"></i> Remove Node
</div>
</div>
<svg id="svg"> </svg>
<!-- call script where the main application is written -->
<script>
var graph = {
"nodes": [{
"id": 0,
"name": "Company",
},
{
"id": 1,
"name": "1",
},
{
"id": 2,
"name": "2",
},
{
"id": 3,
"name": "3",
},
{
"id": 4,
"name": "4",
}
],
"links": [{
"id": 0,
"source": 1,
"target": 0,
},
{
"id": 1,
"source": 2,
"target": 0,
},
{
"id": 2,
"source": 3,
"target": 0,
},
{
"id": 3,
"source": 4,
"target": 0,
},
]
}
// declare initial variables
var svg = d3.select("svg")
width = window.innerWidth
height = window.innerHeight
thisNode = null;
// define cavnas area to draw everything
svg = d3.select("svg")
.attr("class", "canvas")
.attr("width", width)
.attr("height", height)
.append("g")
// iniital force simulation
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(100))
.force("charge", d3.forceManyBody().strength(-80))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("attraceForce", d3.forceManyBody().strength(70));
var node_group = null
var link_group = null
update()
/*
console.log("Initial Nodes")
console.log(graph.nodes)
console.log("------------------")
*/
function update() {
//define group and join
node_group = svg.selectAll(".node_group")
.data(graph.nodes, d => d.id)
//exit, remove
node_group.exit().remove();
//enter
var enter = node_group.enter()
.append("g").attr("class", "node_group");
//append - as many items as you need
enter.append("circle")
.attr("class", "node_circle")
.attr("r", 20)
.on("contextmenu", contextMenu)
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
enter.append("text")
.attr("class", "node_label")
.text(function (d) {
return d.name
})
//merge
node_group = node_group.merge(enter);
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.alphaTarget(0.3).restart()
}
function contextMenu(d) {
thisNode = d
event.preventDefault()
var contextMenu = document.getElementById("context-menu")
contextMenu.style.top = event.clientY + "px"
contextMenu.style.left = event.clientX + "px"
contextMenu.classList.add("active")
window.addEventListener("click", function () {
contextMenu.classList.remove("active")
})
document.getElementById("addObject").addEventListener("click", addNode)
document.getElementById("removeObject").addEventListener("click", removeNodeClicked)
}
function addNodeClicked() {
addNode(thisNode)
}
function removeNodeClicked() {
removeNode(thisNode)
}
function addNode() {
var newID = Math.floor(Math.random() * 100000)
/*
console.log("Before adding Node")
console.log(graph.nodes)
console.log("------------------")
*/
graph.nodes.push({ id: newID, name: "Software_" + newID })
/*
console.log("After adding Node")
console.log(graph.nodes)
console.log("------------------")
*/
update()
}
function removeNode(thisNode) {
var indexOfNode = graph.nodes.indexOf(thisNode)
/*
console.log("Before removing Node")
console.log(graph.nodes)
console.log("------------------")
*/
graph.nodes.splice(indexOfNode, 1)
/*
console.log("After removing Node")
console.log(graph.nodes)
console.log("------------------")
*/
update()
}
function ticked() {
// update link positions
// update node positions
node_group
.attr("transform", function (d) { return "translate(" + d.x + ", " + d.y + ")"; });
}
function dragStarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragEnded(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = undefined;
d.fy = undefined;
}
</script>
</body>
</html>
解决方案
在ticked
函数中,您正在翻译组,而不是圆圈。因此,您应该调用d3.drag
相同的组:
node_group.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
这是您进行更改的代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Playground D3v4</title>
<!-- favcon -->
<link rel="icon" href="https://networkrepository.com/favicon.png">
<!-- call external d3.js framework -->
<script src="https://d3js.org/d3.v4.js"></script>
<!-- import multiselection framework -->
<script src="https://d3js.org/d3-selection-multi.v1.js"></script>
<!-- import "font awesome" stylesheet https://fontawesome.com/ -->
<script src="https://kit.fontawesome.com/39094309d6.js" crossorigin="anonymous"></script>
</head>
<style>
body {
overflow: hidden;
margin: 0px;
}
.canvas {
background-color: rgb(220, 220, 220);
}
.link {
stroke: rgb(0, 0, 0);
stroke-width: 1px;
}
circle {
fill: whitesmoke
}
.node {
stroke: white;
stroke-width: 2px
}
.tooltip {
font-family: "Open Sans", sans-serif;
position: absolute;
text-align: left;
background: rgb(245, 245, 245);
border: 2px;
border-radius: 6px;
border-color: rgb(255, 255, 255);
border-style: solid;
pointer-events: none;
line-height: 150%;
padding: 8px 10px;
}
#context-menu {
font-family: "Open Sans", sans-serif;
position: fixed;
z-index: 10000;
width: 190px;
background: whitesmoke;
border: 2px;
border-radius: 6px;
border-color: white;
border-style: solid;
transform: scale(0);
transform-origin: top left;
}
#context-menu.active {
transform: scale(1);
transition: transform 200ms ease-in-out;
}
#context-menu .item {
padding: 8px 10px;
font-size: 15px;
color: black;
}
#context-menu .item i {
display: inline-block;
margin-right: 5px;
}
#context-menu hr {
margin: 5px 0px;
border-color: whitesmoke;
}
#context-menu .item:hover {
background: lightblue;
}
</style>
<body>
<!-- right click context menu -->
<div id="context-menu">
<div id="addObject" class="item">
<i class="fa fa-plus-circle"></i> Add Node
</div>
<div id="removeObject" class="item">
<i class="fa fa-minus-circle"></i> Remove Node
</div>
</div>
<svg id="svg"> </svg>
<!-- call script where the main application is written -->
<script>
var graph = {
"nodes": [{
"id": 0,
"name": "Company",
},
{
"id": 1,
"name": "1",
},
{
"id": 2,
"name": "2",
},
{
"id": 3,
"name": "3",
},
{
"id": 4,
"name": "4",
}
],
"links": [{
"id": 0,
"source": 1,
"target": 0,
},
{
"id": 1,
"source": 2,
"target": 0,
},
{
"id": 2,
"source": 3,
"target": 0,
},
{
"id": 3,
"source": 4,
"target": 0,
},
]
}
// declare initial variables
var svg = d3.select("svg")
width = window.innerWidth
height = window.innerHeight
thisNode = null;
// define cavnas area to draw everything
svg = d3.select("svg")
.attr("class", "canvas")
.attr("width", width)
.attr("height", height)
.append("g")
// iniital force simulation
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.id;
}).distance(100))
.force("charge", d3.forceManyBody().strength(-80))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("attraceForce", d3.forceManyBody().strength(70));
var node_group = null
var link_group = null
update()
/*
console.log("Initial Nodes")
console.log(graph.nodes)
console.log("------------------")
*/
function update() {
//define group and join
node_group = svg.selectAll(".node_group")
.data(graph.nodes, d => d.id)
//exit, remove
node_group.exit().remove();
//enter
var enter = node_group.enter()
.append("g").attr("class", "node_group");
//append - as many items as you need
enter.append("circle")
.attr("class", "node_circle")
.attr("r", 20)
.on("contextmenu", contextMenu)
enter.append("text")
.attr("class", "node_label")
.text(function(d) {
return d.name
})
//merge
node_group = node_group.merge(enter);
node_group.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.alphaTarget(0.3).restart()
}
function contextMenu(d) {
thisNode = d
event.preventDefault()
var contextMenu = document.getElementById("context-menu")
contextMenu.style.top = event.clientY + "px"
contextMenu.style.left = event.clientX + "px"
contextMenu.classList.add("active")
window.addEventListener("click", function() {
contextMenu.classList.remove("active")
})
document.getElementById("addObject").addEventListener("click", addNode)
document.getElementById("removeObject").addEventListener("click", removeNodeClicked)
}
function addNodeClicked() {
addNode(thisNode)
}
function removeNodeClicked() {
removeNode(thisNode)
}
function addNode() {
var newID = Math.floor(Math.random() * 100000)
/*
console.log("Before adding Node")
console.log(graph.nodes)
console.log("------------------")
*/
graph.nodes.push({
id: newID,
name: "Software_" + newID
})
/*
console.log("After adding Node")
console.log(graph.nodes)
console.log("------------------")
*/
update()
}
function removeNode(thisNode) {
var indexOfNode = graph.nodes.indexOf(thisNode)
/*
console.log("Before removing Node")
console.log(graph.nodes)
console.log("------------------")
*/
graph.nodes.splice(indexOfNode, 1)
/*
console.log("After removing Node")
console.log(graph.nodes)
console.log("------------------")
*/
update()
}
function ticked() {
// update link positions
// update node positions
node_group
.attr("transform", function(d) {
return "translate(" + d.x + ", " + d.y + ")";
});
}
function dragStarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragEnded(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = undefined;
d.fy = undefined;
}
</script>
</body>
</html>
推荐阅读
- javascript - 选择形式反应
- python - Adaptive modelling using GEKKO sysid
- r - 在 R 中查找掷骰的条件概率
- raku - 是否可以解析一个字符串,就好像它在 Raku 中被 << 和 >> 包围?
- machine-learning - 如何为机器人代码构建状态/动作的 Q 表?
- android - 如何在 Android Studio 上构建一个 apk
- neural-network - 使用 Gekko 的大脑模块,我如何确定使用多少层和什么类型的层来解决深度学习问题?
- google-drive-api - Google Oauth 客户端对已验证的应用程序是否有用户限制?
- python - 如何在 Python 上使用 FinQuant 访问 Quandl 网络数据?
- c# - 反射在评估堆栈上发出推送和附加参数