Add Node to D3 Tree v4

前端 未结 2 1885
生来不讨喜
生来不讨喜 2020-12-30 12:20

I am using D3 v4 to build a tree.

Fiddle: https://jsfiddle.net/a6pLqpxw/

I am now trying to add support for dynamically adding (and removing) children from a

相关标签:
2条回答
  • 2020-12-30 12:31

    I came up with this solution for Adding new Node dynamically to D3 Tree v4. .

    D3 v4 tree requires Nodes.

    Create Nodes from your tree data (json) using d3.hierarchy(..) and pushed it into it's parent.children array and update the tree.

    Code Snippet

    //Adding a new node (as a child) to selected Node (code snippet)
    var newNode = {
        type: 'node-type',
        name: new Date().getTime(),
        children: []
      };
      //Creates a Node from newNode object using d3.hierarchy(.)
      var newNode = d3.hierarchy(newNode);
    
      //later added some properties to Node like child,parent,depth
      newNode.depth = selected.depth + 1; 
      newNode.height = selected.height - 1;
      newNode.parent = selected; 
      newNode.id = Date.now();
    
      //Selected is a node, to which we are adding the new node as a child
      //If no child array, create an empty array
      if(!selected.children){
        selected.children = [];
        selected.data.children = [];
      }
    
      //Push it to parent.children array  
      selected.children.push(newNode);
      selected.data.children.push(newNode.data);
    
      //Update tree
      update(selected);
    


    Fiddle
    // ### DATA MODEL START
    
    var data = {
    	type: 'action',
      name: '1',
      attributes: [],
      children: [{
      	type: 'children',
      	name: '2',
        attributes: [{
        	'source-type-property-value': 'streetlight'
        }],
        children: [{
        	type: 'parents',
      		name: '3',
          attributes: [{
          	'source-type-property-value': 'cable'
          }],
          children: [{
          	type: 'resource-delete',
      			name: '4',
            attributes: [],
            children: []
          }]
        }, {
        	type: 'children',
      		name: '5',
          attributes: [{
          	'source-type-property-value': 'lantern'
          }],
          children: []
        }]
      }]
    };
    
    // ### DATA MODEL END
    
    // Set the dimensions and margins of the diagram
    var margin = {top: 20, right: 90, bottom: 30, left: 90},
    	width = 960 - margin.left - margin.right,
      height = 500 - margin.top - margin.bottom;
    
    // append the svg object to the body of the page
    // appends a 'group' element to 'svg'
    // moves the 'group' element to the top left margin
    var svg = d3.select("body").
    	append("svg").
      attr("width", width + margin.right + margin.left).
      attr("height", height + margin.top + margin.bottom).
      append("g").
      attr("transform", "translate(" + margin.left + "," + margin.top + ")");
    
    var i = 0, duration = 750, root;
    
    // declares a tree layout and assigns the size
    var treemap = d3.tree().size([height, width]);
    
    // Assigns parent, children, height, depth
    root = d3.hierarchy(data, function(d) {
    	return d.children;
    });
    root.x0 = height / 2;
    root.y0 = 0;
    
    update(root);
    
    var selected = null;
    
    function update(source) {
    
      // Assigns the x and y position for the nodes
      var treeData = treemap(root);
    
      // Compute the new tree layout.
      var nodes = treeData.descendants(),
      	links = treeData.descendants().slice(1);
    
      // Normalize for fixed-depth.
      nodes.forEach(function(d){
      	d.y = d.depth * 180
      });
    
      // ### LINKS
    
      // Update the links...
      var link = svg.selectAll('line.link').
      	data(links, function(d) {
        	return d.id;
        });
    
      // Enter any new links at the parent's previous position.
      var linkEnter = link.enter().
      	append('line').
        attr("class", "link").
        attr("stroke-width", 2).
        attr("stroke", 'black').
        attr('x1', function(d) {
        	return source.y0;
        }).
        attr('y1', function(d) {
        	return source.x0;
        }).
        attr('x2', function(d) {
        	return source.y0;
        }).
        attr('y2', function(d) {
        	return source.x0;
        });
        
      var linkUpdate = linkEnter.merge(link);
      
      linkUpdate.transition().
      	duration(duration).
        attr('x1', function(d) {
        	return d.parent.y;
        }).
        attr('y1', function(d) {
        	return d.parent.x;
        }).
        attr('x2', function(d) {
        	return d.y;
        }).
        attr('y2', function(d) {
        	return d.x;
        });
    
      // Transition back to the parent element position
      linkUpdate.transition().
      	duration(duration).
        attr('x1', function(d) {
        	return d.parent.y;
        }).
        attr('y1', function(d) {
        	return d.parent.x;
        }).
        attr('x2', function(d) {
        	return d.y;
        }).
        attr('y2', function(d) {
        	return d.x;
        });
    
      // Remove any exiting links
      var linkExit = link.exit().
      	transition().
        duration(duration).
        attr('x1', function(d) {
        	return source.x;
        }).
        attr('y1', function(d) {
        	return source.y;
        }).
        attr('x2', function(d) {
        	return source.x;
        }).
        attr('y2', function(d) {
        	return source.y;
        }).
        remove();
    
    	// ### CIRCLES
    
      // Update the nodes...
      var node = svg.selectAll('g.node')
      	.data(nodes, function(d) {
        	return d.id || (d.id = ++i);
        });
    
      // Enter any new modes at the parent's previous position.
      var nodeEnter = node.enter().
        append('g').
        attr('class', 'node').
        attr("transform", function(d) {
          return "translate(" + source.y0 + "," + source.x0 + ")";
        }).
        on('click', click);
    
      // Add Circle for the nodes
      nodeEnter.append('circle').
      	attr('class', 'node').
        attr('r', 25).
        style("fill", function(d) {
        	return "#0e4677";
        });
    
      // 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.y + "," + d.x + ")";
      	});
    
      // Update the node attributes and style
      nodeUpdate.select('circle.node').
      	attr('r', 25).
        style("fill", function(d) {
        	return "#0e4677";
      	}).
        attr('cursor', 'pointer');
    
      // Remove any exiting nodes
      var nodeExit = node.exit().
      	transition().
        duration(duration).
        attr("transform", function(d) {
        	return "translate(" + source.y + "," + source.x + ")";
        }).
        remove();
    
      // On exit reduce the node circles size to 0
      nodeExit.select('circle').attr('r', 0);
      
      // Store the old positions for transition.
      nodes.forEach(function(d){
        d.x0 = d.x;
        d.y0 = d.y;
      });
    
      // Toggle children on click.
      function click(d) {
        selected = d;
        document.getElementById('add-child').disabled = false;
        document.getElementById('remove').disabled = false;
        update(d);
      }
    }
    
    document.getElementById('add-child').onclick = function() {
      
      //creates New OBJECT
      var newNodeObj = {
      	type: 'resource-delete',
        name: new Date().getTime(),
        attributes: [],
        children: []
      };
      //Creates new Node 
      var newNode = d3.hierarchy(newNodeObj);
      newNode.depth = selected.depth + 1; 
      newNode.height = selected.height - 1;
      newNode.parent = selected; 
      newNode.id = Date.now();
      
      if(!selected.children){
        selected.children = [];
      	    selected.data.children = [];
      }
      selected.children.push(newNode);
      selected.data.children.push(newNode.data);
      
      update(selected);
    };
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <button id="add-child" disabled="disabled">Add Child</button>

    0 讨论(0)
  • 2020-12-30 12:40

    The height calculation in the accepted answer fails to update the ancestors of the created node. This means, for example, that the height of the root will never increase, even as many children are added.

    The following code fixes these problems:

    function insert(par, data) {
       let newNode = d3.hierarchy(data);
       newNode.depth = par.depth + 1;
       newNode.parent = par;
       // Walk up the tree, updating the heights of ancestors as needed.
       for(let height = 1, anc = par; anc != null; height++, anc=anc.parent) {
         anc.height = Math.max(anc.height, height);
       }
       if (!par.data.children) {
          par.children = [];
          par.data.children = [];
       }
       par.children.push(newNode);
       par.data.children.push(newNode.data);
    }
    

    It should be noted that the d3.tree layout algorithm doesn't actually use the height parameter, which is probably why it wasn't noted before.

    If we take this route of "minimum code that makes the code work", we can also get rid of the par.data update and just use:

    function insert(par, data) {
       let newNode = d3.hierarchy(data);
       newNode.depth = par.depth + 1;
       newNode.parent = par;
       if (!par.children)
          par.children = [];
       par.children.push(newNode);
    }
    

    To be functionally equivalent to the previous answer, we would write:

    insert(selected, {
       type: 'node-type',
       name: new Date().getTime()
    });
    update(selected);
    
    0 讨论(0)
提交回复
热议问题