我创建了一个带有工具提示悬停功能的小型 D3v6 强制图形。一切都按预期工作,但工具提示的位置是根据鼠标位置设置的。是否可以简单地使用当前节点位置作为参考?
到目前为止,我的想法是,获取当前的 d(节点)位置。
目标是规范化工具提示位置,以获得更清晰的外观。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Playground D3v6</title>
<!-- favcon -->
<link rel="icon" href="https://networkrepository.com/favicon.png">
<!-- call external d3.js framework -->
<script src="https://d3js.org/d3.v6.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;
r: 30px;
}
.node {
stroke: white;
stroke-width: 2px
}
#tooltip {
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:bottom left;
}
#tooltip.active {
transform:scale(1);
}
#tooltip .item {
padding:8px 10px;
font-size:15px;
color:black;
}
#tooltip .item i {
display: inline-block;
margin-right: 5px;
}
#tooltip hr {
margin: 5px 0px;
border-color: whitesmoke;
}
#tooltip .item::after {
content: " ";
position: absolute;
top: 100%;
left: 50%;
margin-left: -10px;
border-width: 10px;
border-style: solid;
border-color: whitesmoke transparent transparent transparent;
}
</style>
<body>
<div id="tooltip">
<div class="item">
<i class="fas fa-address-card"></i>Objekt ID: <label id="tooltip_id" class="item"></label>
</div>
<hr>
<table>
<tr>
<td class="item">Name</td>
<td id="tooltip_name" class="item">Test</td>
</tr>
</table>
</div>
<svg id="svg"> </svg>
<!-- call script where the main application is written -->
<script>
var graph = {
"nodes": [{
"id": 0,
"name": "Company",
},
{
"id": 1,
"name": "Software_1",
},
{
"id": 2,
"name": "Software_2",
},
{
"id": 3,
"name": "Software_3",
},
{
"id": 4,
"name": "Software_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(-400))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("attraceForce", d3.forceManyBody().strength(70));
//create links
var link = svg.selectAll(".link")
.data(graph.links, function (d) { return d.id })
.enter()
.append("line")
.attr("class", "link")
.style("pointer-events", "none")
var node = svg.selectAll(".node")
.data(graph.nodes, d => d.id)
.enter()
.append("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
node.append("circle")
.on("mouseenter", mouseEnter)
.on("mouseleave", mouseLeave)
node.append("text")
.style("class", "icon")
.attr("font-family", "FontAwesome")
.attr("dominant-baseline", "central")
.attr("text-anchor", "middle")
.attr("font-size", 30)
.attr("fill", "black")
.attr("stroke-width", "0px")
.attr("pointer-events", "none")
.text(function (d) {
return d.id
})
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation
.force("link")
.links(graph.links)
function mouseEnter(event, d) {
d3.select(this).style("fill", "lightblue")
var tooltip = document.getElementById("tooltip")
tooltip.style.top = event.clientY - 150 + "px"
tooltip.style.left = event.clientX - 95 + "px"
tooltip.classList.add("active")
document.getElementById("tooltip_id").innerHTML = d.id
document.getElementById("tooltip_name").innerHTML = d.name
}
function mouseLeave(d) {
d3.select(this).style("fill", "whitesmoke")
document.getElementById("tooltip").classList.remove("active")
}
function ticked() {
// update link positions
link
.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; });
// update node positions
node
.attr("transform", function (d) { return "translate(" + d.x + ", " + d.y + ")"; });
}
function dragStarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragEnded(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = undefined;
d.fy = undefined;
}
</script>
</body>
</html>
答案 0 :(得分:2)
你可以调整这两行:
tooltip.style.top = event.clientY - 150 + "px"
tooltip.style.left = event.clientX - 95 + "px"
致:
tooltip.style.top = `${(d.y - (tooltip.clientHeight / 2))}px`;
tooltip.style.left = `${(d.x + 35)}px`;
d
是触发事件的节点,节点中心具有 x
和 y
属性。
要将工具提示直接放置在节点的右侧:
d.x
添加 35,因为您将 circle
样式与 r
(adius) 设置为 30 像素,而我又添加了 5 像素用于填充clientHeight
中减去 d.y
的一半,使工具提示相对于节点位置垂直居中。您可以使用这些来获得所需的结果。您的代码调整如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Playground D3v6</title>
<!-- favcon -->
<link rel="icon" href="https://networkrepository.com/favicon.png">
<!-- call external d3.js framework -->
<script src="https://d3js.org/d3.v6.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;
r: 30px;
}
.node {
stroke: white;
stroke-width: 2px
}
#tooltip {
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:bottom left;
}
#tooltip.active {
transform:scale(1);
}
#tooltip .item {
padding:8px 10px;
font-size:15px;
color:black;
}
#tooltip .item i {
display: inline-block;
margin-right: 5px;
}
#tooltip hr {
margin: 5px 0px;
border-color: whitesmoke;
}
#tooltip .item::after {
content: " ";
position: absolute;
top: 100%;
left: 50%;
margin-left: -10px;
border-width: 10px;
border-style: solid;
border-color: whitesmoke transparent transparent transparent;
}
</style>
<body>
<div id="tooltip">
<div class="item">
<i class="fas fa-address-card"></i>Objekt ID: <label id="tooltip_id" class="item"></label>
</div>
<hr>
<table>
<tr>
<td class="item">Name</td>
<td id="tooltip_name" class="item">Test</td>
</tr>
</table>
</div>
<svg id="svg"> </svg>
<!-- call script where the main application is written -->
<script>
var graph = {
"nodes": [{
"id": 0,
"name": "Company",
},
{
"id": 1,
"name": "Software_1",
},
{
"id": 2,
"name": "Software_2",
},
{
"id": 3,
"name": "Software_3",
},
{
"id": 4,
"name": "Software_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(-400))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("attraceForce", d3.forceManyBody().strength(70));
//create links
var link = svg.selectAll(".link")
.data(graph.links, function (d) { return d.id })
.enter()
.append("line")
.attr("class", "link")
.style("pointer-events", "none")
var node = svg.selectAll(".node")
.data(graph.nodes, d => d.id)
.enter()
.append("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
node.append("circle")
.on("mouseenter", mouseEnter)
.on("mouseleave", mouseLeave)
node.append("text")
.style("class", "icon")
.attr("font-family", "FontAwesome")
.attr("dominant-baseline", "central")
.attr("text-anchor", "middle")
.attr("font-size", 30)
.attr("fill", "black")
.attr("stroke-width", "0px")
.attr("pointer-events", "none")
.text(function (d) {
return d.id
})
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation
.force("link")
.links(graph.links)
function mouseEnter(event, d) {
d3.select(this).style("fill", "lightblue")
var tooltip = document.getElementById("tooltip")
tooltip.style.top = `${(d.y - (tooltip.clientHeight / 2))}px`; //event.clientY - 150 + "px"
tooltip.style.left = `${(d.x + 35)}px`; //event.clientX - 95 + "px"
tooltip.classList.add("active")
document.getElementById("tooltip_id").innerHTML = d.id
document.getElementById("tooltip_name").innerHTML = d.name
}
function mouseLeave(d) {
d3.select(this).style("fill", "whitesmoke")
document.getElementById("tooltip").classList.remove("active")
}
function ticked() {
// update link positions
link
.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; });
// update node positions
node
.attr("transform", function (d) { return "translate(" + d.x + ", " + d.y + ")"; });
}
function dragStarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragEnded(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = undefined;
d.fy = undefined;
}
</script>
</body>
</html>