Skip to content Skip to sidebar Skip to footer

D3.js - Old Links Not Getting Deleted After Adding A New Node And Trasitioning

I am working on getting a treeview working with some conditional logics. I currently have the following working fiddle: https://jsfiddle.net/zgv9ajn4/4/ From the jsfiddle you can s

Solution 1:

Note that your nodes are moving fine, just the links are not. That is because you find them using path.link, but the real structure is g.link path, since you first add a g element with class link, and then a path. You could fix that by changing path.link to g.link, but then you need to select the path and text separately.

I fixed this by calling .attr('d', linkFunc) on all links, not just the new ones, and by calling .text() and .attr('transform' on all texts.

Finally, I changed the text transform to use the x of the target, since a target determines where the line goes vertical, not the source.

var margin = {
    top: 20,
    right: 120,
    bottom: 20,
    left: 120
  },
  width = 960 - margin.right - margin.left,
  height = 800 - margin.top - margin.bottom;

functiongenerateEmptyDecisionBox(condition) {
  return condition === 'False' ? [{
    "name": "newDecision",
    "id": "newId",
    "type": "decision",
    "value": "notSure",
    "condition": `${condition}`,
  }, {}] : [{}, {
    "name": "newDecision",
    "id": "newId",
    "type": "decision",
    "value": "notSure",
    "condition": `${condition}`,
  }];
}

var selectedNode;
var selectedLink;


var root = {
  "name": "Root",
  "type": "decision",
  "children": [{
      "name": "analytics",
      "condition": "False",
      "type": "decision",
      "value": "a+b",
      "children": [{
        "name": "distinction",
        "type": "action",
        "condition": "True",
        "value": "5",
      }, {
        "name": "nonDistinction",
        "type": "action",
        "condition": "false",
        "value": "4",
        "children": [],
      }],
    },
    {
      "condition": "True",
      "name": "division",
      "type": "decision",
      "value": "a-b",
      "children": {
        "name": "analytics",
        "condition": "False",
        "type": "decision",
        "value": "a+b",
        "children": [{
          "name": "distinction",
          "type": "action",
          "condition": "True",
          "value": "5",
        }, {
          "name": "nonDistinction",
          "type": "decision",
          "condition": "false",
          "value": "4",
          "children": {
            "name": "analytics",
            "condition": "False",
            "type": "decision",
            "value": "a+b",
            "children": [{
              "name": "distinction",
              "type": "action",
              "condition": "True",
              "value": "5",
            }, {
              "name": "nonDistinction",
              "type": "action",
              "condition": "false",
              "value": "4"
            }],
          },
        }],
      },
    }
  ]
};

var i = 0,
  duration = 500,
  rectW = 120,
  rectH = 60;

var tree = d3.layout.tree().nodeSize([150, 90]);



//LINK FUNCTION TO DRAW LINKS var linkFunc = function(d) {
  console.log("linkFunc", d);
  var source = {
    x: d.source.x + rectW / 2,
    y: d.source.y + (rectH / 2)
  };
  var target = {
    x: d.target.x + (rectW / 2),
    y: d.target.y + 3,
  };

  // This is where the line bendsvar inflection = {
    x: target.x,
    y: source.y
  };
  var radius = 5;

  var result = "M" + source.x + ',' + source.y;

  if (source.x < target.x && d.target.type) {
    // Child is to the right of the parent
    result += ' H' + (inflection.x - radius);
  } elseif (d.target.type) {
    result += ' H' + (inflection.x + radius);
  } else {
    return;
  }

  // Curve the line at the bend slightly
  result += ' Q' + inflection.x + ',' + inflection.y + ' ' + inflection.x + ',' + (inflection.y + radius);

  result += 'V' + target.y;
  return result;
}

// END OF LINK FUNC //var diagonal = d3.svg.diagonal()
  .projection(function(d) {
    return [d.x + rectW / 2, d.y + rectH / 2];
  });

// DRAW TREE //var svg = d3.select(".tree-diagram").append("svg").attr("width", 1000).attr("height", 1000)
  .call(zm = d3.behavior.zoom().scaleExtent([1, 3]).on("zoom", redraw)).append("g")
  .attr("transform", "translate(" + 350 + "," + 20 + ")");

// ADD ARROW TO THE BOTTOM POINTING TO THE NEXT DECISION.

svg.append("svg:defs").selectAll("marker")
  .data(["end"]) // Different link/path types can be defined here
  .enter().append("svg:marker") // This section adds in the arrows
  .attr("id", String)
  .attr("viewBox", "0 -5 10 10")
  .attr("refX", 15)
  .attr("refY", 0.5)
  .attr("markerWidth", 4)
  .attr("markerHeight", 4)
  .attr("orient", "auto")
  .append("svg:path")
  .attr("d", "M0,-5L10,0L0,5");

//necessary so that zoom knows where to zoom and unzoom from
zm.translate([350, 20]);

root.x0 = 0;
root.y0 = 0;

update(root);

d3.select(".tree-diagram").style("height", "1000px");

// END OF DRAW TREEE //functionupdate(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 * 90;
  });

  // 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('transform', 'translate(' + source.x0 + ', ' + source.y0 + ')')
    .attr("class", "node")
    .on("click", click)
  //  .on("blur", onNodeBlur);

  nodeEnter.append('path')
    .attr("d", function(d) {
      if (d.type === 'decision') {
        return'M 60 0 120 30 60 60 0 30 Z';
      } elseif (d.type === 'action') {
        return'M 0 0 120 0 120 60 0 60 Z';
      } else {
        return'M -100 -10 -10 -10 -10 -10 -10 -10Z'
      }
    }).attr("stroke-width", 1)
    .attr('class', 'myPaths')
    .style("fill", function(d) {
      return"lightsteelblue";
    });

  nodeEnter.append("text")
    .attr("x", rectW / 2)
    .attr("y", rectH / 2)
    .attr("dy", ".35em")
    .attr("text-anchor", "middle")
    .text(function(d) {
      return d.name;
    });

  var nodeUpdate = node.transition()
    .duration(duration)
    .attr("transform", function(d) {
      return"translate(" + (d.x) + "," + (d.y) + ")";
    });


  var nodeExit = node.exit().transition()
    .duration(duration)
    .attr("transform", function(d) {
      return"translate(" + source.x + "," + source.y + ")";
    })
    .remove();

  nodeExit.select("path")
    .attr("d", function(d) {
      if (d.type === 'decision') {
        return'M 60 0 120 30 60 60 0 30 Z';
      } elseif (d.type === 'action') {
        return'M 0 0 120 0 120 60 0 60 Z';
      } else {
        return'M -100 -10 -10 -10 -10 -10 -10 -10Z'
      }
    }).attr("stroke-width", 1)
    .attr('class', 'myPaths')
    .style("fill", function(d) {
      return"lightsteelblue";
    });



  nodeExit.select("text");

  // Update the links…var link = svg.selectAll(".link")
    .data(links, function(d) {
      return d.target.id;
    }).classed('link1', true);


  // Enter any new links at the parent's previous position.var linkEnter = link.enter()
    .insert("g", "g")
    .attr("class", "link");

  linkEnter.append('path')
    .on('click', function(d, i) {
      selectedLink = d;
      // Use the native SVG interface to get the bounding box to// calculate the center of the pathvar bbox = this.getBBox();
      var x;
      var y;

      if (d.source.x < d.target.x) {
        // Child is to the right of the parent
        x = bbox.x + bbox.width;
        y = bbox.y;
        plusButton
          .attr('transform', 'translate(' + x + ', ' + y + ')')
          .classed('hide', false);

      } else {
        x = bbox.x;
        y = bbox.y;
        plusButton
          .attr('transform', 'translate(' + x + ', ' + y + ')')
          .classed('hide', false);
      }
    })
    .on('blur', function(d, i) {
      plusButton
        .classed('hide', true);
    }).attr("marker-end", "url(#end)");


  // Add Link Texts.
  linkEnter.append('text');

  link.select('path')
    .attr("d", linkFunc);
  
  link.select('text')
    .text(function(d, i) {
      if (d.source.x < d.target.x) {
        return'True';
      } else {
        return'False';
      }
    })
    .attr('transform', function(d) {
      console.log(d);
      if (d.source.x < d.target.x && d.target.type) {
        console.log("comes in here for source < target");
        return'translate(' + (d.target.x + rectW / 2) + ',' + (d.source.y + rectH) + ')';
      } elseif (d.target.type) {
        return'translate(' + (d.target.x + rectW / 2) + ',' + (d.source.y + rectH) + ')';
      } else {
        return;
      }
    });

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

  // Transition links to their new position.// Transition exiting nodes to the parent's new position.
  link.exit().transition()
    .duration(duration)
    .attr("d", linkFunc)
    .remove();

  // Stash the old positions for transition.
  nodes.forEach(function(d) {
    d.x0 = d.x;
    d.y0 = d.y;
  });
}


// ON CLICK OF NODES functionclick(d) {

  if (d.type === 'action') {
    return;
  }

  selectedNode = d;
  var m = d.x;
  var h = d.y;

  var m = d.x + 110;
  var h = d.y + 35;

  diamondImage
    .attr('transform', 'translate(' + m + ', ' + h + ')')
    .classed('hide', false);

  var m = d.x + 110;
  var h = d.y;

  rectangleShape
    .attr('transform', 'translate(' + m + ', ' + h + ')')
    .classed('hide', false);

  var m = d.x - 20;
  var h = d.y + 35;

  diamondImageFalse
    .attr('transform', 'translate(' + m + ', ' + h + ')')
    .classed('hide', false);

  var m = d.x - 20;
  var h = d.y;

  rectangleShapeFalse
    .attr('transform', 'translate(' + m + ', ' + h + ')')
    .classed('hide', false);
}

/* function onNodeBlur(){
  diamondImage
  .classed('hide', true);
  rectangleShape
    .classed('hide', true);
   diamondImageFalse
     .classed('hide',true);
   rectangleShapeFalse
    .classed('hide',true);  
}
 *///Redraw for zoomfunctionredraw() {
  //console.log("here", d3.event.translate, d3.event.scale);
  svg.attr("transform",
    "translate(" + d3.event.translate + ")" +
    " scale(" + d3.event.scale + ")");
}



// oN CALLfunctionaddElement(d, truthy) {
  console.log(d);

  d.children = null;
  d.children = generateEmptyDecisionBox(truthy);
  update(root);
}

// draw elements //functiondrawDiamond(centroid) {
  // Start at the topconsole.log(centroid);
  console.log("rectH", rectH, rectW)
  // Start at the topvar result = 'M' + centroid.x + ',' + (centroid.y - rectH / 2);

  // Go right
  result += 'L' + (centroid.x + rectW / 2) + ',' + centroid.y;

  // Bottom
  result += 'L' + centroid.x + ',' + (centroid.y + rectH / 2);

  // Left
  result += 'L' + (centroid.x - rectW / 2) + ',' + centroid.y;

  // Close the shape
  result += 'Z';

  return result;
}

functiondrawRect(centroid) {
  // Start at the top leftconsole.log(centroid);
  var result = 'M' + (centroid.x - rectW / 2) + ',' + (centroid.y - rectH / 2);

  // Go right
  result += 'h' + rectW;

  // Go down
  result += 'v' + rectH;

  // Left
  result += 'h-' + rectW;

  // Close the shape
  result += 'Z';

  console.log(result);
  return result;
}

var plusButton = svg
  .append('g')
  .classed('button', true)
  .classed('hide', true)
  .on('click', function() {
    console.log("hello");
    /*        addElement(selectedLink.source); */console.log("Clicked on Diamond");
    console.log("set hide to true");
    removeAllButtonElements();
  });

plusButton
  .append('rect')
  .attr('transform', 'translate(-8, -8)') // center the button inside the `g`
  .attr('width', 16)
  .attr('height', 16)
  .attr('rx', 2);

plusButton
  .append('path')
  .attr('d', 'M-6 0 H6 M0 -6 V6');

var rectangleShape = svg.append('g')
  .classed('conditionImage', true)
  .classed('hide', true)
  .on('click', function() {
    removeAllButtonElements();
  })

rectangleShape
  .append('rect')
  .attr('width', 30)
  .attr('height', 20)
  .style('fill', 'orange');


var diamondImage = svg.append('g')
  .classed('conditionSvg', true)
  .classed('hide', true)
  .classed('scale', true)
  .on('click', function() {
    console.log("Clicked on Diamond");
    console.log("set hide to true");
    addElement(selectedNode, 'True');
    removeAllButtonElements();
  });

diamondImage
  .append('path')
  .attr('d', 'M 15 0 30 15 15 30 0 15 Z')
  .style("fill", 'orange');


var rectangleShapeFalse = svg.append('g')
  .classed('conditionImage', true)
  .classed('hide', true)
  .on('click', function() {
    console.log("rectangle clicked");
    removeAllButtonElements();
  })

rectangleShapeFalse
  .append('rect')
  .attr('width', 30)
  .attr('height', 20)
  .style('fill', 'orange');

var diamondImageFalse = svg.append('g')
  .classed('conditionImage', true)
  .classed('hide', true)
  .classed('scale', true)
  .on('click', function() {
    console.log("Clicked on Diamond");
    console.log("set hide to true");
    addElement(selectedNode, 'False');
    removeAllButtonElements();
  });

diamondImageFalse
  .append('path')
  .attr('d', 'M 15 0 30 15 15 30 0 15 Z')
  .style("fill", 'orange');


functionremoveAllButtonElements() {
  plusButton.classed('hide', true);
  diamondImage.classed('hide', true);
  rectangleShape.classed('hide', true);
  diamondImageFalse.classed('hide', true);
  rectangleShapeFalse.classed('hide', true);
}


// draw elements end ..
.node {
  cursor: pointer;
  outline: none !important;
}

.node text {
  font: 10px sans-serif;
}

.button>path {
  stroke: blue;
  stroke-width: 1.5;
  /*   outline: none;  */
}

.button>rect {
  fill: #ddd;
  stroke: grey;
  stroke-width: 1px;
}

.conditionalSvg {
  outline: none;
  display: none;
}

.hide {
  display: none;
  opacity: 0!important;
  pointer-events: none;
}

.link:hover {
  ouline: none !important;
  cursor: pointer;
  stroke-width: 3px;
}

.link path {
  outline: none !important;
  fill: none;
  stroke: darkgray;
  stroke-width: 2px;
}

.link path:hover {
  cursor: pointer;
  stroke-width: 4px;
}

.link text {
  font: 10px sans-serif;
}

.colorBlue {
  background-color: blue;
}
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script><divclass="tree-diagram"></div>

Post a Comment for "D3.js - Old Links Not Getting Deleted After Adding A New Node And Trasitioning"