Placing the word "repeatedly" in a d3 js tree

I am currently working on building a tree structure and incorporating edge labels. The tree allows nodes to be dynamically added using a plus button that appears when you click on a parent node. One issue arises when adding a new child.

(apologies for the long code, I was unable to provide a shorter example - the code has been commented for easier analysis)

The Challenges

Upon inspecting the DOM, I noticed that the text element with the class text-link is being duplicated within the g element with the class edge-container. This duplication results in multiple instances of the same label being rendered. Here's an excerpt from the markup:

<g class="edge-container">
<path class="link" id="rightlink1-2" d="M0,230C90,230 90,76.66666666666667 180,76.66666666666667"></path>
<text class="text-link" x="135" y="230" text-anchor="start" style="font-size: 14px;">test-label</text>
</g>

...
</pre>

<p>It seems that each new node addition causes all previous labels to be attached to the newly created <code>edge-container.

How can this issue be rectified?

This means that every time a new node is added, all existing labels are also appended to the new edge-container.

What modifications should be implemented in the code so that only the corresponding text-link is added for each edge inside its respective parent g element?

Operation

Click on a node to reveal the add button, then click on the button to introduce a child node. (For better usability, view the full-screen version.)

Answer №1

Don't forget to remove the old text-link like you did for the left side, and then add this line:

 d3.selectAll(".right-tree-container .text-link").remove();

In your makeRightTree function as follows:

 $.fn.makeRightTree = function() {
  // ************** Generate the tree diagram  *****************
  var margin = {
      top: 20,
      right: 120,
      bottom: 20,
      left: 120
    },
    width = 1260 - margin.right - margin.left,
    height = 500 - margin.top - margin.bottom;

  var i = 0,
    duration = 750,
    root;

  var tree = d3.layout.tree()
    .size([height, width]);

  var diagonal = d3.svg.diagonal()
    .projection(function(d) {
      return [d.y, d.x];
    });



  var svg = d3.select("svg").append("g")
    .attr("class", "right-tree-container")
    .attr("transform", "translate(600,0)");
  svg.append("g").attr("id", "right-edges")


  root = treeData[0];
  oldrx = root.x0 = height / 2;
  oldry = root.y0 = 0;

  update(root);



  function update(source) {

    // Compute the new tree layout.
    var nodes = tree.nodes(root).reverse(),
      links = tree.links(nodes);

    // Normalize for fixed-depth.
    nodes.forEach(function(d) {
      d.y = d.depth * 180;
    });

    // Update the nodes…
    var node = svg.selectAll("g.node")
      .data(nodes, function(d) {
        return d.id || (d.id = ++i);
      });

    // Enter any new nodes at the parent's previous position.
    var nodeEnter = node.enter().append("g")
      .attr("class", function(d) {
        console.log(d);
        if (d.parent == "null") {

          if (d.children)

          {
            return "node rightparent collapsed"; //since it's root its parent is null
          } else {
            return "node rightparent";
          }

        } else {
          if (d.children) {
            return "node rightchild collapsed"; //all nodes with a parent will have this class
          } else {

            return "node rightchild"; //all nodes with a parent will have this class
          }
        }

      })
      .attr("transform", function(d) {
        return "translate(" + source.y0 + "," + source.x0 + ")";
      })
      .on("click", click);

    nodeEnter.append("rect")

      .attr("id", function(d) {
        return "rightnode" + d.id;
      })
      .attr("x", "-10")
      .attr("y", "-15")
      .attr("height", 30)
      .attr("width", 100)
      .attr("rx", 15)
      .attr("ry", 15)
      .style("fill", "#f1f1f1");

    nodeEnter.append("image")
      .attr("xlink:href", "img.png")
      .attr("x", "0")
      .attr("y", "-10")
      .attr("width", 16)
      .attr("height", 16);




    nodeEnter.append("text")
      .attr("x", function(d) {
        return d.children || d._children ? -13 : 13;
      })
      .attr("dy", ".35em")
      .attr("text-anchor", function(d) {
        return d.children || d._children ? "end" : "start";
      })
      .text(function(d) {
        return d.name;
      })
      .style("fill-opacity", 1e-6);




    var addRightChild = nodeEnter.append("g").attr("class", "addRightChild");
    addRightChild.append("rect")
      .attr("x", "90")
      .attr("y", "-10")
      .attr("height", 20)
      .attr("width", 20)
      .attr("rx", 10)
      .attr("ry", 10)
      .style("stroke", "#444")
      .style("stroke-width", "2")
      .style("fill", "#ccc");

    addRightChild.append("line")
      .attr("x1", 95)
      .attr("y1", 1)
      .attr("x2", 105)
      .attr("y2", 1)
      .attr("stroke", "#444")
      .style("stroke-width", "2");

    addRightChild.append("line")
      .attr("x1", 100)
      .attr("y1", -4)
      .attr("x2", 100)
      .attr("y2", 6)
      .attr("stroke", "#444")
      .style("stroke-width", "2");



    // adding the right chevron
    var rightChevron = nodeEnter.append("g").attr("class", "right-chevron");





    rightChevron.append("line")
      .attr("x1", 75)
      .attr("y1", -5)
      .attr("x2", 80)
      .attr("y2", 0)
      .attr("stroke", "#444")
      .style("stroke-width", "2");

    rightChevron.append("line")
      .attr("x1", 80)
      .attr("y1", 0)
      .attr("x2", 75)
      .attr("y2", 5)
      .attr("stroke", "#444")
      .style("stroke-width", "2");

    rightChevron.on("click", function(d) {


    });




    // Transition nodes to their new position.
    var nodeUpdate = node.transition()
      .duration(duration)
      .attr("transform", function(d) {
        if (d.parent == "null") {
          d.y = oldry;
          d.x = oldrx;


        }

        return "translate(" + d.y + "," + d.x + ")";
      });



    nodeUpdate.select("text")
      .style("fill-opacity", 1);

    // Transition exiting nodes to the parent's new position.
    var nodeExit = node.exit().transition()
      .duration(duration)
      .attr("transform", function(d) {
        return "translate(" + source.y + "," + source.x + ")";
      })
      .remove();

    nodeExit.select("text")
      .style("fill-opacity", 1e-6);

    var edge = d3.select("#right-edges")
      .append("g")
      .attr("class", "edge-container")
      .data(links);

 **d3.selectAll(".right-tree-container .text-link").remove();**
    var text = edge.selectAll("text.text-link")
      .data(links, function(d) {
        return d.target.id + d.source.id;
      });

    text.enter().insert("text", "edge-container")
      .attr("class", "text-link")

    .attr("x", function(d) {
        var x = (d.source.y + d.target.y) / 2
        return parseInt(x + 45);
      })
      .attr("y", function(d) {
        var y = (d.source.x + d.target.x) / 2
        return y;
      })
      .text("test-label")
      .attr("text-anchor", "start")
      .style("font-size", "14px");

    var link = edge.selectAll("path.link")
      .data(links, function(d) {
        return d.target.id;
      });





    // Enter any new links at the parent's previous position.
    link.enter().insert("path", "edge-container")
      .attr("class", "link")
      .attr("id", function(d) {
        return ("rightlink" + d.source.id + "-" + d.target.id)
      })
      .attr("d", function(d) {
        var o = {
          x: source.x0,
          y: source.y0
        };

        return diagonal({
          source: o,
          target: o
        });
      }).on("click", removelink)
      .on("mouseover", showRemoveButton)
      .on("mouseout", hideRemoveButton);

    function showRemoveButton() {
      console.log("hover");
    }

    function hideRemoveButton() {
      console.log("hover-out");
    }




    /***** end of relevant code *******/


    function removelink(d) {

      var confirmDelete = confirm("Are you sure you want to delete?");


      if (confirmDelete) {

        //this is the links target node which you want to remove
        var target = d.target;
        //make a new set of children
        var children = [];
        //iterate through the children 
        target.parent.children.forEach(function(child) {
          if (child.id != target.id) {
            //add to the child list if the target id is not the same 
            //so that the node target is removed.
            children.push(child);
          }
        });
        //set the target parent with a new set of children sans the one which is removed
        target.parent.children = children;
        //redraw the parent since one of its children is removed
        update(d.target.parent)
      }



    }

    var link = svg.selectAll("path.link")
      .data(links, function(d) {
        return d.target.id;
      });

    // Transition links to their new position.
    link.transition()
      .duration(duration)
      .attr("d", diagonal);

    // Transition exiting nodes to the parent's new position.
    link.exit().transition()
      .duration(duration)
      .attr("d", function(d) {
        var o = {
          x: source.x,
          y: source.y
        };
        return diagonal({
          source: o,
          target: o
        });
      })

      .remove();

    // Stash the old positions for transition.
    nodes.forEach(function(d) {


      d.x0 = d.x;
      d.y0 = d.y;
    });


    addRightChild.on("click", function(d) {



      event.stopPropagation();
      $("#child-info").show();
      $("#child-text").val("");

      $("#btn-add-child").off('click');
      $("#btn-add-child").click(function() {
        var childname = $("#child-text").val();


        if (typeof d._children === 'undefined' || d._children === null) {
          if (typeof d.children === 'undefined') {


            var newChild = [{
              "name": childname,
              "parent": "Son Of A",
            }];



            var newnodes = tree.nodes(newChild);
            d.children = newnodes[0];

            update(d);


          } else {
            var newChild = {
              "name": childname,
              "parent": "Son Of A",
            };

            d.children.push(newChild);

            update(d);
          }
        } else {
          var newChild = {
            "name": childname,
            "parent": "Son Of A",
          };


          d.children = d._children;
          d.children.push(newChild);

          update(d);


        }


        $("#child-info").hide();
      });
    });;

  }


  // Toggle children on click.
  function click(d) {

    if (d.children) {
      d._children = d.children;
      d.children = null;
    } else {
      d.children = d._children;
      d._children = null;
    }
    update(d);

    $(".addLeftChild, .addRightChild").hide();
    if (d.id === 1) {
      $(".rightparent").children(".addRightChild").show();
      $(this).children(".addRightChild").show();
    } else {
      $(this).children(".addRightChild").show();

    }
    d3.selectAll("rect").style("fill", "#f1f1f1"); //reset all node colors
    d3.selectAll("path").style("stroke", "#85e0e0"); //reset the color for all links
    while (d.parent) {

      d3.select("#leftnode1").style("fill", "#F7CA18");
      d3.selectAll("#rightnode" + d.id).style("fill", "#F7CA18"); //color the node
      if (d.parent != "null")
        d3.selectAll("#rightlink" + d.parent.id + "-" + d.id).style("stroke", "#F7CA18"); //color the path
      d = d.parent;
    }
  }
}

This is a working demo

Answer №2

Resolved the issue by incorporating the following code snippet :

// Update labels to new positions
var textLabels = svg.selectAll("text.text-link")
    .data(links, function(d) {
        return d.target.id;
    });

textLabels.transition()
    .duration(1000)
    .attr("d", diagonal);

textLabels.exit().transition()
    .duration(1000)
    .attr("d", function(d) {
        var origin = {
            x: source.x,
            y: source.y
        };
        return diagonal({
            source: origin,
            target: origin
        });
    })
    .remove();

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Missing dots on owl carousel

Currently, I am working on a project that involves using the owlcarousel library in javascript. Everything was running smoothly until I encountered an issue. Although I have set the dots to true in the code, they are not appearing on the carousel. Below i ...

The Markdown-to-jsx tool is having trouble processing the provided source code

Utilizing a Material UI blog post template found at https://github.com/mui-org/material-ui/tree/master/docs/src/pages/getting-started/templates/blog, I have created my React app using npx create-react-app. Upon console logging the source, it displays as a ...

What is the best way to organize and group messages in JavaScript in order to push them to a subarray?

Having trouble coming up with a name for this question, but here's the issue I'm facing: I currently have an array in PHP containing several entries. Array ( [0] => stdClass Object ( [sender_id] => 0 [me ...

What is the best way to ensure my php variable is easily accessed?

Recently, I've been working on implementing a timer and came across the idea in a post on Stack Overflow. <?php if(($_SERVER['REQUEST_METHOD'] === 'POST') && !empty($_POST['username'])) { //secondsDif ...

Tips on how to round a whole number to the closest hundred using Javascript

Suppose there is a numerical value given let x = 890 and the objective is to round it off to 900. If the number is 820, then it should be rounded to 800. We can safely assume that the input number (x) will always be an integer. While manually checking ...

Adjust the output number in a JavaScript BMI calculator to the nearest whole number when using the

Hey there, I'm currently working on a beginner project and could use some assistance. My project involves creating a basic BMI calculator using metric units, but I seem to be encountering issues with rounding numbers. Here is a snippet of my code: var ...

Enhancing Angular with Plotly: Implementing click events on bar chart legends

I'm currently working on implementing color pickers for my plotly-plot charts within an Angular template. I am looking to add a function that triggers when the chart legend is clicked. How can I achieve this and get a click event for the chart legends ...

Attempting to navigate a tutorial on discord.js, I encountered an issue labeled as "Unknown application" that disrupted my progress

I attempted a tutorial for discord.js, but encountered an error message saying "DiscordAPIError[10002]: Unknown Application." Even though I checked my clientID and applicationID and they seem to be correct! You can find the tutorial here. Here is the co ...

Transform the post data into a JSON string within the controller

Hello everyone, I have a sample table that I want to share: <table class="table table-bordered" width="100%" cellspacing="0" id="tableID"> <thead> <tr> <th>A</th> <th>B</th> <th>C< ...

use ajax to post saved data to a WebAPI in php

I have successfully implemented the code to save data in a custom table using Ajax. Now, I need to figure out how to send this data to an asp.Net API using js/jQuery. How can I achieve this? Below is my HTML form and JS code: <div id="inline1" class= ...

`Achieving object placement on top of another object in ThreeJS`

Being new to ThreeJS, I am seeking help with a basic question. I have loaded multiple GLTF files into my scene and I need to position one object on top of another (but the position should be adjustable later on) For instance: https://i.sstatic.net/oJq1BIi ...

The outcome of a MySQL query involving JSON_OBJECT() is a string value

I have crafted a query that extracts posts from a table and includes information about each post's author: SELECT post.id, post.text, post.datetime, JSON_OBJECT( 'username', user.username, 'firstName', user.firstName, 'last ...

What is the best way to retrieve the second element based on its position using a class name in Jquery?

DisablePaginationButton("first"); The statement above successfully disables the first element that is fetched. DisablePaginationButton("second"); ===> not functioning function DisablePaginationButton(position) { $(".pagination a:" + position).ad ...

Tips for leveraging async and await within actions on google and API integration

Currently, I am developing an Actions on Google project that utilizes an API. To handle the API calls, I am using request promise for implementation. Upon testing the API call, I observed that it takes approximately 0.5 seconds to retrieve the data. Theref ...

Tips for updating the color of table data when the value exceeds 0

I'm currently working on developing a transaction table that is intended to display debit values in red and credit values in green. However, I would like these colors to only be applied if the value in the table data is greater than 0 via JavaScript. ...

What is the best way to load a partial in Rails asynchronously with AJAX?

I am currently using the following code to load a partial when I reach the bottom of a div containing a table: $(document).ready(function () { $("#pastGigs").scroll(function () { if (isScrollBottom()) { $('#pastGig ...

Adjust the jQuery.ScrollTo plugin to smoothly scroll an element to the middle of the page both vertically and horizontally, or towards the center as much as possible

Visit this link for more information Have you noticed that when scrolling to an element positioned to the left of your current scroll position, only half of the element is visible? It would be ideal if the entire element could be visible and even centere ...

Progressing with JavaScript: Implementing Sound Controls and Dynamic Image Switching

I am looking to create a button that, when clicked, displays a small image and plays a sound. The button should change to a different image and stop the sound when clicked again. Essentially, it functions as a "start" button that loops the sound and change ...

Executing MySQL queries through JavaScript functions

I need to create a PHP function that I can use in my JavaScript code. The issue I'm facing is that the variables from the beginning of the file are not recognized in the JavaScript block. <!DOCTYPE html> <?php include 'pdo_connect.php&a ...

What is the best way to access event.target as an object in Angular 2?

Apologies for my limited English proficiency. . I am trying to write code that will call myFunction() when the user clicks anywhere except on an element with the class .do-not-click-here. Here is the code I have written: document.addEventListener(' ...