D3 工具提示修复定位

时间:2021-03-02 07:43:31

标签: javascript d3.js

我创建了一个带有工具提示悬停功能的小型 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>

1 个答案:

答案 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 是触发事件的节点,节点中心具有 xy 属性。

要将工具提示直接放置在节点的右侧:

  • 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>

相关问题