删除节点后未删除D3.js链接

时间:2020-09-17 12:08:05

标签: javascript svg d3.js

我试图通过直接修改treeData来删除d3树中的一个节点(遵循Mike Bostocks在此处建议的添加/删除节点:-https://github.com/d3/d3-hierarchy/issues/139)。

我能够删除该节点,但是以某种方式,链接不会消失。

jsFiddle:-http://jsfiddle.net/1sr5tbLx/1/

在Fiddle中重现问题的步骤:- 要删除一个节点(现在),您必须单击该节点,您将找到橙色的svgs->选择 右上方的“矩形” sv​​g。那应该删除该节点。

enter image description here

单击此橙色矩形后,我将调用方法-> removeSelectedNode()(具有选定的节点),它将找到父节点并删除子索引,然后重新计算根详细信息并调用更新再次树(不确定是否有更好的方法来进行此更新)。

enter image description here

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

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

function generateEmptyActionBox(condition) {
  return condition === 'False' ? [{
    "name": "newAction",
    "id": "newId",
    "type": "action",
    "value": "notSure",
    "condition": `${condition}`,
  }, {}] : [{}, {
    "name": "newAction",
    "id": "newId",
    "type": "action",
    "value": "notSure",
    "condition": `${condition}`,
  }];
}

var selectedNode;
var selectedLink;


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

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

var treeMap = d3.tree()
  .nodeSize([150, 180]);

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

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

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

  if (source.x < target.x && d.data.type) {
    // Child is to the right of the parent
    result += ' H' + (inflection.x - radius);
  } else if (d.data.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 //
  
 const zoomSvg = d3.select('.tree-diagram')
      .append('svg')
      .attr('width', width)
      .attr('height', height)
      .append('g');

const svg = zoomSvg.append('g')  
      .attr('transform', 'translate(' + 300 + ',' + 20 + ')');

const attachZoom = d3.select('svg');
    attachZoom.call(d3.zoom().on('zoom',() => {
        zoomSvg.attr('transform',d3.event.transform)
    }))


// 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 = d3.hierarchy(treeData[0], function(d) {
  return d.children;
});
root.x0 = 0;
root.y0 = 0;

update(root);

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

// END OF DRAW TREEE //

function update(source) {
  const treeData = treeMap(root);
  const treeRoot = d3.hierarchy(root);
//  d3.tree(treeRoot);
  // var treeData = treeMap(root);

  // Compute the new tree layout.
  var nodes = treeData.descendants(),
    links = treeData.descendants()
    .slice(1);

  console.log(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.data.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.data.type === 'decision') {
          return 'M 60 0 120 30 60 60 0 30 Z';
        } else if (d.data.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.data.name;
    });

  // UPDATE
  var nodeUpdate = nodeEnter.merge(node);

  // Transition to the proper position for the node
  nodeUpdate.transition()
    .duration(duration)
    .attr("transform", function(d) {
      return "translate(" + (d.x) + "," + (d.y) + ")";
    });


  nodeUpdate.select('path.myPaths')
    .attr("d", function(d) {
      if (d.data.type === 'decision') {
        return 'M 60 0 120 30 60 60 0 30 Z';
      } else if (d.data.type === 'action') {
        return 'M 0 0 120 0 120 60 0 60 Z';
      } else {
        return 'M -100 -10 -10 -10 -10 -10 -10 -10Z';
      }
    });


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

  // Update the links…
  var link = svg.selectAll(".link")
    .data(links, function(d) {
      return d.data.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 path

      var bbox = this.getBBox();
      var x;
      var y;

      if (d.parent.x < d.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');

  // Merge the new and the existing links before setting `d` and `text` on all of them
  link = linkEnter.merge(link);

  link.select('path')
    .attr("d", linkFunc);

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


  //LinkUpdate
  var linkUpdate = linkEnter.merge(link);

  // 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
function click(d) {

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

  selectedNode = d;
    
        if(!(d.data.children && d.data.children[0] && Object.keys(d.data.children[0]).length)){
     diamondImageFalse
      .attr('transform', 'translate(' + (d.x - 20) + ', ' + (d.y + 35) + ')')
      .classed('hide', false);

    rectangleShapeFalse.attr('transform', 'translate(' + (d.x - 20) + ', ' + d.y + ')').classed('hide', false);

    }

    if(!(d.data.children && d.data.children[1] && Object.keys(d.data.children[1]).length)){
    diamondImage
      .attr('transform', 'translate(' + (d.x + 110) + ', ' + (d.y + 35) + ')')
      .classed('hide', false);

    rectangleShape.attr('transform', 'translate(' + (d.x + 110) + ', ' + d.y + ')').classed('hide', false);

    }
}

// oN CALL

function addElement(d, truthy) {
  console.log(d);

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

// draw elements //

function drawDiamond(centroid) {
  // Start at the top
  console.log(centroid);
  console.log("rectH", rectH, rectW);
  // Start at the top
  var 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;
}

function drawRect(centroid) {
  // Start at the top left
  console.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() {
  // for now modifying it to remove thi node.
  // selected Node is saved on click of the decision/rect svg in the click function
     removeSelectedNode(selectedNode);
     /* addActionOrDecision(selectedNode,'action','True') */;
    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() {
    addActionOrDecision(selectedNode,'decision','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() {
     addActionOrDecision(selectedNode,'action','False');
    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() {
   addActionOrDecision(selectedNode,'decision','False');
   //  addElement(selectedNode, 'False');
    removeAllButtonElements();
  });

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


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


function addActionOrDecision(selectedNode,nodeType,conditionType){  

    const parentNodeId = selectedNode.parent.data.id;
    const selectedNodeId = selectedNode.data.id;
    console.log(parentNodeId,selectedNodeId);

// find the selected node from the actual treeData
    const foundRule = getNodeFromNodeId(treeData,selectedNodeId);
    const newRuleId = Math.random();

    const newNodeToAdd = {
      "condition": conditionType,
      "name": nodeType === 'decision'? 'New Decision' : 'New Action',
      "type": nodeType,
      "value": "",
      "id": newRuleId,
      "parent": parentNodeId,
      "children": [],
    };
    
    const clonedNewNode = {...newNodeToAdd};

    if(conditionType === 'False' && foundRule.children){
  //    foundRule.children[0] = newNodeToAdd;
      
      foundRule.children.splice(0,1,clonedNewNode);
      
     if(!(foundRule.children[1] && Object.keys(foundRule.children[1]))){
                foundRule.children[1] = {};
     }
            
      } else {
   //   foundRule.children[1] = newNodeToAdd;
   
         foundRule.children.splice(1,1,clonedNewNode);
   
      if(!(foundRule.children[0] && Object.keys(foundRule.children[0]))){
                founRule.children[0] = {};
     }
    }

    // find the node and add a child to it. 
        updateTree();
 
  }
  
 function updateTree(){
    root = d3.hierarchy(treeData[0], function(d) {
      return d.children;
    });
    root.x0 = height/2;
    root.y0 = 0;

    update(root);
    
    console.log(treeData);

  }
  
function getNodeFromNodeId(nodes, nodeId){
    for (const node of nodes) {
      const currNode = node;
      if (currNode) {
        if (currNode.id === nodeId) {
          return currNode;
        } else if (currNode.children) {
          const childResult = getNodeFromNodeId(currNode.children, nodeId);
          if (childResult) {
            return childResult;
          }
        }
      }
    }
    return null;
  }
  
function removeSelectedNode() {
   const parentNodeId = selectedNode.parent.data.id;
   const selectedNodeId = selectedNode.data.id;
   
   const foundParentNode = getNodeFromNodeId(treeData,parentNodeId);
   const foundNode = getNodeFromNodeId(treeData,selectedNodeId);

 const foundIndex = foundParentNode.children.findIndex(child => child.id === foundNode.id);
   //find the other node and just push that into the children array. 


 // create a new array and push the index not found into the children array.
  let i =0;
  let results = [];
  let len = foundParentNode.children.length;
   for(let i=0; i < len; i++){
     if(i !== foundIndex){
       results.push(foundParentNode.children[i]);
     }
   }

  foundParentNode.children = results;

    updateTree();

}

css:-

.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 {
  outline: 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;
}

1 个答案:

答案 0 :(得分:0)

显然,我必须使用:-

删除屏幕上未显示的链接。
link.exit().remove();

这是更新的jsfiddle:-http://jsfiddle.net/awymjbon/

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

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

function generateEmptyActionBox(condition) {
  return condition === 'False' ? [{
    "name": "newAction",
    "id": "newId",
    "type": "action",
    "value": "notSure",
    "condition": `${condition}`,
  }, {}] : [{}, {
    "name": "newAction",
    "id": "newId",
    "type": "action",
    "value": "notSure",
    "condition": `${condition}`,
  }];
}

var selectedNode;
var selectedLink;


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

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

var treeMap = d3.tree()
  .nodeSize([150, 180]);

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

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

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

  if (source.x < target.x && d.data.type) {
    // Child is to the right of the parent
    result += ' H' + (inflection.x - radius);
  } else if (d.data.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 //
  
 const zoomSvg = d3.select('.tree-diagram')
      .append('svg')
      .attr('width', width)
      .attr('height', height)
      .append('g');

const svg = zoomSvg.append('g')  
      .attr('transform', 'translate(' + 300 + ',' + 20 + ')');

const attachZoom = d3.select('svg');
    attachZoom.call(d3.zoom().on('zoom',() => {
        zoomSvg.attr('transform',d3.event.transform)
    }))


// 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 = d3.hierarchy(treeData[0], function(d) {
  return d.children;
});
root.x0 = 0;
root.y0 = 0;

update(root);

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

// END OF DRAW TREEE //

function update(source) {
  const treeData = treeMap(root);
  const treeRoot = d3.hierarchy(root);
//  d3.tree(treeRoot);
  // var treeData = treeMap(root);

  // Compute the new tree layout.
  var nodes = treeData.descendants(),
    links = treeData.descendants()
    .slice(1);

  console.log(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.data.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.data.type === 'decision') {
          return 'M 60 0 120 30 60 60 0 30 Z';
        } else if (d.data.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.data.name;
    });

  // UPDATE
  var nodeUpdate = nodeEnter.merge(node);

  // Transition to the proper position for the node
  nodeUpdate.transition()
    .duration(duration)
    .attr("transform", function(d) {
      return "translate(" + (d.x) + "," + (d.y) + ")";
    });


  nodeUpdate.select('path.myPaths')
    .attr("d", function(d) {
      if (d.data.type === 'decision') {
        return 'M 60 0 120 30 60 60 0 30 Z';
      } else if (d.data.type === 'action') {
        return 'M 0 0 120 0 120 60 0 60 Z';
      } else {
        return 'M -100 -10 -10 -10 -10 -10 -10 -10Z';
      }
    });


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

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


// remove links that are no longer on screen.
     link.exit().remove();

  // 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 path

      var bbox = this.getBBox();
      var x;
      var y;

      if (d.parent.x < d.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');

  // Merge the new and the existing links before setting `d` and `text` on all of them
  link = linkEnter.merge(link);

  link.select('path')
    .attr("d", linkFunc);

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


  //LinkUpdate
  var linkUpdate = linkEnter.merge(link);

  // 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
function click(d) {

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

  selectedNode = d;
    
        if(!(d.data.children && d.data.children[0] && Object.keys(d.data.children[0]).length)){
     diamondImageFalse
      .attr('transform', 'translate(' + (d.x - 20) + ', ' + (d.y + 35) + ')')
      .classed('hide', false);

    rectangleShapeFalse.attr('transform', 'translate(' + (d.x - 20) + ', ' + d.y + ')').classed('hide', false);

    }

    if(!(d.data.children && d.data.children[1] && Object.keys(d.data.children[1]).length)){
    diamondImage
      .attr('transform', 'translate(' + (d.x + 110) + ', ' + (d.y + 35) + ')')
      .classed('hide', false);

    rectangleShape.attr('transform', 'translate(' + (d.x + 110) + ', ' + d.y + ')').classed('hide', false);

    }
}

// oN CALL

function addElement(d, truthy) {
  console.log(d);

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

// draw elements //

function drawDiamond(centroid) {
  // Start at the top
  console.log(centroid);
  console.log("rectH", rectH, rectW);
  // Start at the top
  var 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;
}

function drawRect(centroid) {
  // Start at the top left
  console.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() {
  // for now modifying it to remove thi node.
  // selected Node is saved on click of the decision/rect svg in the click function
     removeSelectedNode(selectedNode);
     /* addActionOrDecision(selectedNode,'action','True') */;
    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() {
    addActionOrDecision(selectedNode,'decision','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() {
     addActionOrDecision(selectedNode,'action','False');
    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() {
   addActionOrDecision(selectedNode,'decision','False');
   //  addElement(selectedNode, 'False');
    removeAllButtonElements();
  });

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


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


function addActionOrDecision(selectedNode,nodeType,conditionType){  

    const parentNodeId = selectedNode.parent.data.id;
    const selectedNodeId = selectedNode.data.id;
    console.log(parentNodeId,selectedNodeId);

// find the selected node from the actual treeData
    const foundRule = getNodeFromNodeId(treeData,selectedNodeId);
    const newRuleId = Math.random();

    const newNodeToAdd = {
      "condition": conditionType,
      "name": nodeType === 'decision'? 'New Decision' : 'New Action',
      "type": nodeType,
      "value": "",
      "id": newRuleId,
      "parent": parentNodeId,
      "children": [],
    };
    
    const clonedNewNode = {...newNodeToAdd};

    if(conditionType === 'False' && foundRule.children){
  //    foundRule.children[0] = newNodeToAdd;
      
      foundRule.children.splice(0,1,clonedNewNode);
      
     if(!(foundRule.children[1] && Object.keys(foundRule.children[1]))){
                foundRule.children[1] = {};
     }
            
      } else {
   //   foundRule.children[1] = newNodeToAdd;
   
         foundRule.children.splice(1,1,clonedNewNode);
   
      if(!(foundRule.children[0] && Object.keys(foundRule.children[0]))){
                founRule.children[0] = {};
     }
    }

    // find the node and add a child to it. 
        updateTree();
 
  }
  
 function updateTree(){
    root = d3.hierarchy(treeData[0], function(d) {
      return d.children;
    });
    root.x0 = height/2;
    root.y0 = 0;

    update(root);
    
    console.log(treeData);

  }
  
function getNodeFromNodeId(nodes, nodeId){
    for (const node of nodes) {
      const currNode = node;
      if (currNode) {
        if (currNode.id === nodeId) {
          return currNode;
        } else if (currNode.children) {
          const childResult = getNodeFromNodeId(currNode.children, nodeId);
          if (childResult) {
            return childResult;
          }
        }
      }
    }
    return null;
  }
  
function removeSelectedNode() {
   const parentNodeId = selectedNode.parent.data.id;
   const selectedNodeId = selectedNode.data.id;
   
   const foundParentNode = getNodeFromNodeId(treeData,parentNodeId);
   const foundNode = getNodeFromNodeId(treeData,selectedNodeId);

 const foundIndex = foundParentNode.children.findIndex(child => child.id === foundNode.id);
   //find the other node and just push that into the children array. 


 // create a new array and push the index not found into the children array.
  let i =0;
  let results = [];
  let len = foundParentNode.children.length;
   for(let i=0; i < len; i++){
     if(i !== foundIndex){
       results.push(foundParentNode.children[i]);
     }
   }

  foundParentNode.children = results;

    updateTree();

}