score:2

Accepted answer

You're right, when you change the nodes to be drawn statically, their positions are fine indeed, and the text can be easily changed too. One difference, I'd draw the nodes starting at the center, not at the top left, because that's prettier with transformations. I also changed the orange shape position a little bit to center the blue node that was clicked.

One problem with adding nodes: you add the same object every time. See this toturial for an explanation. A workaround is to copy the object or create it new every time.

You also deleted all children of the node you clicked on, with d.children = [];. Changing that to only execute if d has no children fixed that.

Finally, I added a case to the linkFunc just draw a vertical line if one node is straight below the other.

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

function emptyDecisionBox() {
  return {
    "name": "newDecision",
    "id": "newId",
    "value": "notSure",
    "condition": "true",
  };
}

var selectedNode;

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

var i = 0,
  duration = 750,
  rectW = 80,
  rectH = 40;

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

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

  // 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 the source and target are on the same position, just draw a straight vertical line
  if (source.x !== target.x) {
    if (source.x < target.x) {
      // Child is to the right of the parent
      result += ' H' + (inflection.x - radius);
    } else {
      result += ' H' + (inflection.x + radius);
    }

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

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

// 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 + ")");

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

root.x0 = 0;
root.y0 = height / 2;

update(root);

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

// UPDATE TREE
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 * 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("class", "node")
    .attr("transform", function(d) {
      return "translate(" + source.x0 + "," + source.y0 + ")";
    })
    .on("click", click);

  nodeEnter.append('path')
    .attr("d", function(d) {
      if (d.type === 'decision') {
        return drawDiamond();
      } else {
        return drawRect();
      }
    }).attr("stroke-width", 1)
    .attr('class', 'myPaths')
    .style("fill", "lightsteelblue");

  nodeEnter.append("text")
    .attr("dy", ".35em")
    .attr("text-anchor", "middle")
    .text(function(d) {
      return d.name;
    });

  // Transition nodes to their new position.
  var nodeUpdate = node.transition()
    .duration(duration)
    .attr("transform", function(d) {
      return "translate(" + d.x + ', ' + d.y + ")";
    });

  // Transition exiting nodes to the parent's new position.
  var nodeExit = node.exit().transition()
    .duration(duration)
    .remove();

  nodeExit.select("text");

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

  // Enter any new links at the parent's previous position.
  link.enter().insert("path", "g")
    .attr("class", "link")
    .attr("d", linkFunc)
    .on('click', function(d, i) {
      // 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.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);
    });

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

  // 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) {
  console.log(d);
  selectedNode = d;

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

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

  var m = d.x + 40;
  var h = d.y - 30;

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

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

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

  var m = d.x - 70;
  var h = d.y - 30;

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

//Redraw for zoom
function redraw() {
  //console.log("here", d3.event.translate, d3.event.scale);
  svg.attr("transform",
    "translate(" + d3.event.translate + ")" +
    " scale(" + d3.event.scale + ")");
}

// oN CALL

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

  if(d.children === undefined) { d.children = []; }
  d.children.push(emptyDecisionBox());
  update(root);
}


// draw elements //

function drawDiamond() {
  // Start at the top
  var result = 'M0,' + (-rectH / 2);

  // Go right
  result += 'L' + (rectW / 2) + ',0';

  // Bottom
  result += 'L0,' + (rectH / 2);

  // Left
  result += 'L' + (-rectW / 2) + ',0';

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

  return result;
}

function drawRect() {
  // Start at the top left
  var result = 'M' + (-rectW / 2) + ',' + (-rectH / 2);

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

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

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

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

  return result;
}

var plusButton = svg
  .append('g')
  .classed('button', true)
  .classed('hide', true)
  .on('click', function() {
    console.log("CLICKED");
  });

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('button', true)
  .classed('hide', true)
  .on('click', function() {
    removeAllButtonElements();
  })

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


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

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


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

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

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

diamondImageFalse
  .append('path')
  .attr('d', 'M 20 0 40 20 20 40 0 20 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);
}


// draw elements end ..
.node {
  cursor: pointer;
}

.node circle {
  fill: #fff;
  stroke: steelblue;
  stroke-width: 1.5px;
}

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

.link {
  fill: none;
  stroke: #ccc;
  stroke-width: 1.5px;
}


/* body {
  overflow: hidden;
} */

.button>path {
  stroke: blue;
  stroke-width: 1.5;
}

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

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

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

.scale {
  /* transform: scale(0.4); */
}

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


Related Query