jeudi 23 avril 2020

How to implement d3- tree in react?


I'm trying to solve a small bug in my programm. I converted the following d3 example to react D3 Tree v5. It's working pretty solid, but there's still one bug in my updating method which I am not able to solve. Everytime I click on a node, which contains children every node which also contains children starts to "move", like the following: Bug
My code looks like the following:

export default class Tree extends Component {
   constructor(props) {

    super(props)

    this.state = {

        root: null,
        informationNode: [1], 
        collapse: true,
        clickedNode: "click on node to see signals",



        } 


    // Set the dimensions and margins of the diagram
    this.margin = this.props.margin;
    this.width = this.props.width;
    this.height = this.props.height;
    this.duration = 2000;
    this.root = null;
    this.treemap = null;
    //this.update = null;
    this.i = 0;
    this.data = this.props.data;
    this.g = null
    this.treemap= tree().size([this.height, this.width])
    this.svg = null
    this.collapse = this.collapse.bind(this)
    this.update = this.update.bind(this)


    }



    componentDidMount() {
        this.svg = d3.select("#contentTree").append("svg") // append the svg object to content id
                    .attr("width", this.width - this.margin.left - this.margin.right) // moves the 'group' element to the top left margin
                    .attr("height", this.height - this.margin.top - this.margin.bottom)
                    .append("g") // appends a 'group' element to 'svg'
                    .attr("transform", "translate(" + this.margin.left + "," + this.margin.top + ")")

        // declares a tree layout and assigns the size
        this.treemap = d3.tree().size([this.height, this.width]);

        // Assigns parent, children, height, depth
        this.root = d3.hierarchy( this.data, function(d) { return d.children; });
        this.root.x0 = this.height / 2;
        this.root.y0 = 0;
        this.root.children.forEach(this.collapse)
        this.update(this.root);


    }


    // Collapse the node and all it's children
    collapse(d) {
        if(d.children) {
            d._children = d.children
            d._children.forEach(this.collapse)
            d.children = null
        }
        }



    update(source) {

            // Assigns the x and y position for the nodes
            var treeData = this.treemap(this.root);
            var i = 0 ;

            // 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});

            // ****************** Nodes section ***************************

            // Update the nodes...
            var node = this.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', this.handleClick);

            // Add Circle for the nodes
            nodeEnter.append('circle')
                .attr('class', 'node')
                .attr('r', 1e-6)
                .attr('node','pointer')
                .attr('fill','#fff')
                .attr('stroke','steelblue')
                .attr('strokeWidth','3px')

                .style("fill", function(d) {
                    return d._children ? "lightsteelblue" : "#fff";
                });

            // Add labels for the nodes
            nodeEnter.append('text')

                .attr("font", "12px sans-serif")

                .attr("dy", ".35em")
                .attr("x", function(d) {
                    return d.children || d._children ? -13 : 13;
                })
                .attr("text-anchor", function(d) {
                    return d.children || d._children ? "end" : "start";
                })
                .text(function(d) { return d.data.name; });

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

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

            // Update the node attributes and style
            nodeUpdate.select('circle.node')
                .attr('r', 10)
                .style("fill", function(d) {
                    return d._children ? "lightsteelblue" : "#fff";
                })
                .attr('cursor', 'pointer');


            // Remove any exiting nodes
            var nodeExit = node.exit().transition()
                .duration(this.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', 1e-6);

            // On exit reduce the opacity of text labels
            nodeExit.select('text')
                .style('fill-opacity', 1e-6);

            // ****************** links section ***************************

            // Update the links...
            var link = this.svg.selectAll('path.link')
                .data(links, function(d) { return d.id; });

            // Enter any new links at the parent's previous position.
            var linkEnter = link.enter().insert('path', "g")
                .attr("class", "link")
                .attr("fill", "none")
                .attr("stroke", "#ccc")
                .attr("stroke-width", "2px")

                .attr('d', function(d){
                    var o = {x: source.x0, y: source.y0}
                    return diagonal(o, o)});


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

            // Transition back to the parent element position
            linkUpdate.transition()
                .duration(this.duration)
                .attr('d', function(d){ return diagonal(d, d.parent) });

            // Remove any exiting links
            var linkExit = link.exit().transition()
                .duration(this.duration)
                .attr('d', function(d) {
                    var o = {x: source.x, y: source.y}
                    return diagonal(o, o)
                })
                .remove();

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

                // Creates a curved (diagonal) path from parent to the child nodes
                function diagonal(s, d) {

                    var path = `M ${s.y} ${s.x}
                            C ${(s.y + d.y) / 2} ${s.x},
                            ${(s.y + d.y) / 2} ${d.x},
                            ${d.y} ${d.x}`

                    return path
                }

                }


      // Toggle children on click.
        handleClick(d) {
            //console.log("d",d.data.children)
            if (d.data.children.length !== 0) { 
                if (d.children) {
                    d._children = d.children;
                    d.children = null;
                } else {
                    d.children = d._children;
                    d._children = null;
                }
                this.update(d);

                //pass
            }
        }     

render() {

return ( ...
            <div id="contentTree" > </div>
         ...   
);
  }
}

Any help would be really appreciated!




Aucun commentaire:

Enregistrer un commentaire