我有一个用力网络绘制的简单网络,我希望用户在绘制后单独拖动节点。目前,这仅在用户拖动每个节点后才起作用。第一次拖动时,拖动一个节点将导致其他节点移动。我希望当拖动另一个节点时所有节点都保持在原位。我尝试在
simulation.stop()
之后添加拖动功能,但它不起作用。
下面的例子:
const networkData = {
"nodes": [{
"id": 1,
"name": "A",
isSource: true,
"sourceX": 100,
"sourceY": 100
},
{
"id": 2,
"name": "B",
isSource: false
},
{
"id": 3,
"name": "C",
isSource: true,
"sourceX": 150,
"sourceY": 150
},
{
"id": 4,
"name": "D",
isSource: false
},
{
"id": 5,
"name": "E",
isSource: true,
"sourceX": 200,
"sourceY": 200
},
{
"id": 6,
"name": "F",
isSource: false
}
],
"links": [{
"source": 1,
"target": 2
},
{
"source": 3,
"target": 4
},
{
"source": 5,
"target": 6
}
]
}
const svg = d3.select("#my_dataviz");
const sourceNodes = networkData.nodes.filter(node => {
return node.isSource === true;
});
const targetNodes = networkData.nodes.filter(node => {
return !node.isSource;
});
const link = svg
.selectAll(".link")
.data(networkData.links)
.enter()
.append("line")
.attr("class", "link")
.attr("stroke-width", 2)
.attr("stroke", 'grey');
const targetNodeSelection = svg
.selectAll(".node")
.data(targetNodes)
.enter()
.append("circle")
.attr("class", "node")
.attr("r", 10)
.attr("fill", 'blue');
// Separate selections for source nodes
const sourceNodeSelection = svg
.selectAll(".source-node")
.data(sourceNodes)
.enter()
.append("circle")
.attr("class", "source-node")
.attr("r", 1)
.attr("fill", 'white')
.attr("cx", d => d.sourceX) // Set fixed X position for source nodes
.attr("cy", d => d.sourceY); // Set fixed Y position for source nodes
const maxIterations = 10;
let iterationCount = 0; // Initialize the iteration counter
// Create a simulation for the target nodes only
const simulation = d3.forceSimulation(networkData.nodes)
.force("link", d3.forceLink(networkData.links)
.id(function(d) {
return d.id;
})
.links(networkData.links)
)
.force("center", d3.forceCenter(400 / 2, 400 / 2))
.force('collision', d3.forceCollide().radius(20))
.on("tick", function() {
tick();
iterationCount++;
if (iterationCount >= maxIterations) {
simulation.stop();
}
});
function tick() {
link
.attr("x1", function(d) {
return d.source.sourceX;
})
.attr("y1", function(d) {
return d.source.sourceY;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
targetNodeSelection
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
sourceNodeSelection
.attr("cx", function(d) {
return d.sourceX;
})
.attr("cy", function(d) {
return d.sourceY;
});
}
function clamp(x, lo, hi) {
return x < lo ? lo : x > hi ? hi : x;
}
function dragged(event, d) {
d.fx = clamp(event.x, 0, 400);
d.fy = clamp(event.y, 0, 400);
simulation.alpha(0).restart();
}
const drag = d3
.drag()
.on("drag", dragged);
targetNodeSelection.call(drag);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js" integrity="sha512-cd6CHE+XWDQ33ElJqsi0MdNte3S+bQY819f7p3NUHgwQQLXSKjE4cPZTeGNI+vaxZynk1wVU3hoHmow3m089wA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<svg id="my_dataviz" height='400' width='400'></svg>
拖动时,您的模拟仍然遵循您的
center
和 collision
力。您可以在初始模拟完成后简单地将它们归零。
const networkData = {
"nodes": [{
"id": 1,
"name": "A",
isSource: true,
"sourceX": 100,
"sourceY": 100
},
{
"id": 2,
"name": "B",
isSource: false
},
{
"id": 3,
"name": "C",
isSource: true,
"sourceX": 150,
"sourceY": 150
},
{
"id": 4,
"name": "D",
isSource: false
},
{
"id": 5,
"name": "E",
isSource: true,
"sourceX": 200,
"sourceY": 200
},
{
"id": 6,
"name": "F",
isSource: false
}
],
"links": [{
"source": 1,
"target": 2
},
{
"source": 3,
"target": 4
},
{
"source": 5,
"target": 6
}
]
}
const svg = d3.select("#my_dataviz");
const sourceNodes = networkData.nodes.filter(node => {
return node.isSource === true;
});
const targetNodes = networkData.nodes.filter(node => {
return !node.isSource;
});
const link = svg
.selectAll(".link")
.data(networkData.links)
.enter()
.append("line")
.attr("class", "link")
.attr("stroke-width", 2)
.attr("stroke", 'grey');
const targetNodeSelection = svg
.selectAll(".node")
.data(targetNodes)
.enter()
.append("circle")
.attr("class", "node")
.attr("r", 10)
.attr("fill", 'blue');
// Separate selections for source nodes
const sourceNodeSelection = svg
.selectAll(".source-node")
.data(sourceNodes)
.enter()
.append("circle")
.attr("class", "source-node")
.attr("r", 1)
.attr("fill", 'white')
.attr("cx", d => d.sourceX) // Set fixed X position for source nodes
.attr("cy", d => d.sourceY); // Set fixed Y position for source nodes
const maxIterations = 10;
let iterationCount = 0; // Initialize the iteration counter
// Create a simulation for the target nodes only
const simulation = d3.forceSimulation(networkData.nodes)
.force("link", d3.forceLink(networkData.links)
.id(function(d) {
return d.id;
})
.links(networkData.links)
)
.force("center", d3.forceCenter(400 / 2, 400 / 2))
.force('collision', d3.forceCollide().radius(20))
.on("tick", function() {
tick();
iterationCount++;
if (iterationCount >= maxIterations) {
simulation.stop();
simulation
.force("center", null)
.force('collision', null);
}
});
function tick() {
link
.attr("x1", function(d) {
return d.source.sourceX;
})
.attr("y1", function(d) {
return d.source.sourceY;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
targetNodeSelection
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
sourceNodeSelection
.attr("cx", function(d) {
return d.sourceX;
})
.attr("cy", function(d) {
return d.sourceY;
});
}
function clamp(x, lo, hi) {
return x < lo ? lo : x > hi ? hi : x;
}
function dragged(event, d) {
d.fx = clamp(event.x, 0, 400);
d.fy = clamp(event.y, 0, 400);
simulation.alpha(0).restart();
}
const drag = d3
.drag()
.on("drag", dragged);
targetNodeSelection.call(drag);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js" integrity="sha512-cd6CHE+XWDQ33ElJqsi0MdNte3S+bQY819f7p3NUHgwQQLXSKjE4cPZTeGNI+vaxZynk1wVU3hoHmow3m089wA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<svg id="my_dataviz" height='400' width='400'></svg>