ICoded
ICoded

Reputation: 341

Links connected but not visible

The graph connects the nodes proper but the links are not visible. I added the .attr("stroke", "black") .attr("stroke-width", 2) directly in the update() function. Before I had it in CSS which also wasn´t working. The browser inspector is telling me that the x, y positons are 0 for my link group. Which irritates me, usually those positions are provided in the ticked() function, by source.x,y and target.x,y.

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>D3v4 enter().exit().remove()</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);
    }

    .node_group {
        fill: whitesmoke;
        stroke: white;
        stroke-width: 2px
    }

    .node_label {
        stroke: black;
        stroke-width: 0px;
        fill: black
    }

    .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": [{
                "source": 1,
                "target": 0,
            },
            {
                "source": 2,
                "target": 0,
            },
            {
                "source": 3,
                "target": 0,
            },
            {
                "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)
            .call(d3.zoom().on("zoom", function () {
                svg.attr("transform", d3.event.transform)
            }))
            .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 - nodes
            node_group = svg.selectAll(".node_group")
                .data(graph.nodes, d => d.id)

            //exit, remove
            node_group.exit().remove();

            //enter
            var enterN = node_group.enter()
                .append("g").attr("class", "node_group");

            //append
            enterN.append("circle")
                .attr("class", "node_circle")
                .attr("r", 20)
                .on("contextmenu", contextMenu)
               

            enterN.append("text")
                .attr("class", "node_label")
                .text(function (d) {
                    return d.name
                })

            //merge
            node_group = node_group.merge(enterN);

            node_group.call(d3.drag()
                    .on("start", dragStarted)
                    .on("drag", dragged)
                    .on("end", dragEnded)
                )

            //###LINKS

            //define group and join -links
            link_group = svg.selectAll(".link_group")
                .data(graph.links)

            //exit, remove
            link_group.exit().remove();

            //enter
            var enterL = link_group.enter()
                .append("g").attr("class", "link_group")

            //append
            enterL.append("line")
                .attr("class", "link_stroke")
                .attr("stroke", "black")
                .attr("sroke-width", 2)

            //merge
            link_group = link_group.merge(enterL);


            simulation
                .nodes(graph.nodes)
                .on("tick", ticked);

            simulation
                .force("link")
                .links(graph.links)

            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 removeNodeClicked() {
            removeNode(thisNode)
        }

        function addNode() {
            var newID = Math.floor(Math.random() * 100000)

            /*
            console.log("Before adding Node")
            console.log(graph.nodes)
            console.log("------------------")
            */

            graph.links.push({source: newID, target: thisNode.id})

            graph.nodes.push({ id: newID, name: newID })

            /*
            console.log("After adding Node")
            console.log(graph.nodes)
            console.log("------------------")
            */

            update()
        }

        function removeNode(thisNode) {
            var indexOfNode = graph.nodes.indexOf(thisNode)

            var indexOfLink = graph.links.findIndex(element => element.source.id === thisNode.id)

            /*
            console.log("Before removing Node")
            console.log(graph.nodes)
            console.log("------------------")
            */

            graph.links.splice(indexOfLink,1)

            graph.nodes.splice(indexOfNode, 1)

            /*
            console.log("After removing Node")
            console.log(graph.nodes)
            console.log("------------------")
            */

            update()
        }

        function ticked() {
            // update link positions
            link_group
                .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_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>

Upvotes: 1

Views: 61

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102194

For whatever reason you're appending the <line> inside <g> elements, and then in the ticked function you're setting the x1, y1, x2 and y2 properties to those groups, not to the lines.

That said, the minimal change for that code to work is, in the ticked function:

link_group.select("line")
    .attr("x1", etc...

However, I'd ditch the groups altogether and append just the lines.

Here is your code with that change only:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>D3v4 enter().exit().remove()</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);
    }

    .node_group {
        fill: whitesmoke;
        stroke: white;
        stroke-width: 2px
    }

    .node_label {
        stroke: black;
        stroke-width: 0px;
        fill: black
    }

    .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": [{
                "source": 1,
                "target": 0,
            },
            {
                "source": 2,
                "target": 0,
            },
            {
                "source": 3,
                "target": 0,
            },
            {
                "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)
            .call(d3.zoom().on("zoom", function () {
                svg.attr("transform", d3.event.transform)
            }))
            .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 - nodes
            node_group = svg.selectAll(".node_group")
                .data(graph.nodes, d => d.id)

            //exit, remove
            node_group.exit().remove();

            //enter
            var enterN = node_group.enter()
                .append("g").attr("class", "node_group");

            //append
            enterN.append("circle")
                .attr("class", "node_circle")
                .attr("r", 20)
                .on("contextmenu", contextMenu)
               

            enterN.append("text")
                .attr("class", "node_label")
                .text(function (d) {
                    return d.name
                })

            //merge
            node_group = node_group.merge(enterN);

            node_group.call(d3.drag()
                    .on("start", dragStarted)
                    .on("drag", dragged)
                    .on("end", dragEnded)
                )

            //###LINKS

            //define group and join -links
            link_group = svg.selectAll(".link_group")
                .data(graph.links)

            //exit, remove
            link_group.exit().remove();

            //enter
            var enterL = link_group.enter()
                .append("g").attr("class", "link_group")

            //append
            enterL.append("line")
                .attr("class", "link_stroke")
                .attr("stroke", "black")
                .attr("sroke-width", 2)

            //merge
            link_group = link_group.merge(enterL);


            simulation
                .nodes(graph.nodes)
                .on("tick", ticked);

            simulation
                .force("link")
                .links(graph.links)

            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 removeNodeClicked() {
            removeNode(thisNode)
        }

        function addNode() {
            var newID = Math.floor(Math.random() * 100000)

            /*
            console.log("Before adding Node")
            console.log(graph.nodes)
            console.log("------------------")
            */

            graph.links.push({source: newID, target: thisNode.id})

            graph.nodes.push({ id: newID, name: newID })

            /*
            console.log("After adding Node")
            console.log(graph.nodes)
            console.log("------------------")
            */

            update()
        }

        function removeNode(thisNode) {
            var indexOfNode = graph.nodes.indexOf(thisNode)

            var indexOfLink = graph.links.findIndex(element => element.source.id === thisNode.id)

            /*
            console.log("Before removing Node")
            console.log(graph.nodes)
            console.log("------------------")
            */

            graph.links.splice(indexOfLink,1)

            graph.nodes.splice(indexOfNode, 1)

            /*
            console.log("After removing Node")
            console.log(graph.nodes)
            console.log("------------------")
            */

            update()
        }

        function ticked() {
            // update link positions
            link_group.select("line")
                .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_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>

Upvotes: 2

Related Questions