Skip to content
Snippets Groups Projects
Select Git revision
  • main
  • hugland-main-patch-95531
  • hugland-main-patch-75685
3 results

backup.js

Blame
  • user avatar
    lzkd authored
    670516f1
    History
    Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    backup.js 22.94 KiB
    import * as d3 from 'd3'
    import '../css/radialTree.css'
    
    let DURATION = 500
    let ID = 0;
    export default function MyRadialTree(element, data, options){
    
        this.graphId = ID++
        this.showLeafNames = false
    
        this.width;
        this.height; //c'est un carré
        this.margin = 0; //en vrai ça va se faire del fast non ?
    
        //dimensions
        this.rootSize = 100;//size of the middle circle
        this.leafLineSize = 10;//size of the lines of the last nodes
        this.leafOffset = 2;
        this.leafNameSize = 0;//size of the outer ring, between the last node circles and the svg borders
        
        this.leafRadius //experimental
        this.leafLineRadius // radius where the lines of the last branches ends
        this.leafLabelRadius// radius where labels for leaves are positioned, (== leaflineRadius + leafOffset)
    
        this.branchRange;// size between the rootsize and the leafSize
    
        //style
        this.nodeCircleRadius = 3;
        this.nodeCircleRadiusActive = 8;
        this.updateDuration = 500
        this.highlightDuration = 50
        this.rootNodeTextSize = 25
    
        //set options
        for(let prop in options){
            this[prop] = options[prop]
        }
    
        // this.color = 'gray'
        this.color = 'rgb(150,150,150)'
        this.highlightColor = 'white'
    
    
        //trucs
        this.cluster;
        this.maxleaves;
    
        // on setup la data;
        this.rootNode = d3.hierarchy(data) 
        this.rootNode.descendants().forEach(node =>{
            node._children = node.children;
        })
        this.currentNode = this.rootNode
    
        this.checkboxContainer = d3.select(element)
        .append('div')
        .style('position','relative')
        .style('text-align','start')
        .style('align-self','start')
        .style('width', '200px')
        .append('div')
        .style('position', 'absolute')
        .style('top', 0)
        .style('left', 0)
        
        this.checkboxContainer.append('input')
        .attr('type', 'checkbox')
        .attr('id', 'toggleNames'+this.graphId)
        .on("click", e=>{
            this.toggleLeafNames(e.target.checked)
        });
        this.checkboxContainer.append('label')
        .attr('for', 'toggleNames'+this.graphId)
        .text('Show leaves name')
        
        this.svgElement = d3.select(element).append('svg')
        this.svg = this.svgElement.append("g")
    
        this.rootNodeText = this.svg.append('text')
        .attr('class', 'rootLabel')
        .attr('fill', this.highlightColor)
        .text('Bonjour')
        .attr('font-size',25)
        .attr('text-anchor', 'middle')
        .attr('alignment-baseline', 'middle')
    
        this.branchContainer = this.svg.append('g')
        this.highlightPathContainer = this.svg.append('g')
        
        this.svg.append('line')//parentNodeLine
        .attr('class', 'parentNodeLine')
        .attr('stroke', this.color)
        .attr('stroke-width', 1)
        .attr('y1',this.rootSize)
        .attr('y2',this.rootSize)
    
        this.rootCircle = this.svg.append('circle')
        .attr('class', 'rootCircle')
        .attr('stroke', this.color)
        .attr('fill','none')
        .attr("stroke-width",1);
    
        this.nodeCircleContainer = this.svg.append('g')
    
        this.svg.append('circle')//parentNodeCircle
        .attr('class', 'parentNodeCircle')
        .attr('fill', this.color)
        .on('click', (e)=>{
            if(this.currentNode.parent)
                this.update(this.currentNode.parent)
        })
        .on('mouseover', (e)=>{
    
            this.setRootNodeText(this.currentNode.parent.data.name)
    
            this.svg.select('.parentNodeLine')
            // .transition().duration(100)
            .attr('stroke', this.highlightColor)
            .attr('stroke-width', 1)
    
            this.svg.select('.parentNodeCircle')
            // .transition().duration(100)
            .attr('fill', this.highlightColor)
            .attr('r', this.nodeCircleRadiusActive)
    
            this.rootCircle.attr('stroke', this.highlightColor)
        })
        .on('mouseout', (e)=>{
            
            this.setRootNodeText(this.currentNode.data.name)
            
            this.svg.select('.parentNodeLine')
            // .transition("mouseout").duration(100)
            .attr('stroke', this.color)
            .attr('stroke-width', 1)
            
            this.svg.select('.parentNodeCircle')
            // .transition("mouseout").duration(100)
            .attr('fill' ,this.color)
            .attr('r', this.nodeCircleRadius)
    
            this.rootCircle.attr('stroke', this.color)
        })
    
     
    
        let resizeObserver = new ResizeObserver(entries =>{
            for (const entry of entries) {//should only be one.
                this.resize(entry.contentRect.width, entry.contentRect.height);
                this.update(this.currentNode);
            }
        });
        resizeObserver.observe(element)
    
        this.resize(element.clientWidth, element.clientHeight)
        this.update(this.rootNode);
    }
    
    MyRadialTree.prototype.update = function(data){
        
        this.currentNode = data;
        
        this.pruneNode(data)
        this.cluster(data)
    
        let descendants = data.descendants().reverse()
        let maxDepthDisplayed = descendants[0].depth;
        descendants.forEach(node =>{
            
            node.ptheta = node.theta
            node.pr = node.r
            node.pendPointR = node.endPointR
            node.ptheta1 = node.theta1
            node.ptheta2 = node.theta2
    
            node.theta = node.x;
            node.r = this.rootSize + Math.max(0, (node.depth-data.depth-1) / Math.max(1, maxDepthDisplayed-data.depth-1) * this.branchRange);
            
            if(node.children && node.children.length > 0){
                node.theta1 = node.children[0].theta
                node.theta2 = node.children[node.children.length-1].theta
                node.endPointR = node.children[0].r
            }else{
                node.theta1 = node.theta
                node.theta2 = node.theta
                node.endPointR = this.leafLineRadius
            }
            if(node == this.currentNode){//edge case si la currentnode n'a pas de children, pour pas que son trait se balade dans le vide 
                node.endPointR = this.rootSize;
            }
    
            node.x = node.r * -Math.sin(node.theta)
            node.y = node.r * Math.cos(node.theta)
        })
        
        // this.svg.select('.rootLabel').text(this.currentNode.data.name)
        this.setRootNodeText(this.currentNode.data.name)
        this.drawBranches(data)
        this.drawParentBranch(data)
        if(this.showLeafNames)
            this.drawNames(data)
    }
    
    MyRadialTree.prototype.drawBranches = function(node){
        
        let descendants = node.descendants();
    
        this.branchContainer.selectAll('.branch')
        .data(descendants, d=>d.data.TSN)
        .join(
            enter => {
                let g = enter.append('g')
                .each(function(d) { d.branchElem = this; })//set reference to the element (geste technique)
                .attr('class', 'branch')
                .attr('stroke', this.color)
                .attr('fill', this.color)
                .attr('stroke-width', 1)
    
                g.attr('opacity',0)
                .transition().duration(DURATION).delay(DURATION/2)//.delay(DURATION)
                .attr('opacity',1)
    
                g.append('path')
                .attr('class', 'nodePath')
                .attr('d', d=>this.getBranchPath(d))
                .attr('fill','none')
                
                g.append('line')
                .attr('class', 'nodeLine')
                .attr("x1", d=>d.x)
                .attr("y1", d=>d.y)
                .attr("x2", d =>this.getNodeLineEndPoint(d).x)
                .attr("y2", d => d.endPointR * Math.cos(d.theta))
    
                // return g
            },
            update => {
    
                update.select('.nodeLine')
                .transition().duration(DURATION)
                // .attr("x1", d=>d.x)
                // .attr("y1", d=>d.y)
                // .attr("x2", d =>this.getNodeLineEndPoint(d).x)
                // .attr("y2", d =>this.getNodeLineEndPoint(d).y)
                .attrTween('x1', d => radialInterpolatorX(d.pr, d.ptheta, d.r, d.theta))
                .attrTween('y1', d => radialInterpolatorY(d.pr, d.ptheta, d.r, d.theta))
                .attrTween('x2', d => radialInterpolatorX(d.pendPointR, d.ptheta, d.endPointR, d.theta))
                .attrTween('y2', d => radialInterpolatorY(d.pendPointR, d.ptheta, d.endPointR, d.theta))
    
                update.select('.nodePath')
                .transition().duration(DURATION)
                // .attr('d', d=>this.getBranchPath(d))
                .attrTween('d', d => (t)=>{
    
                    let r = (1-t)*d.pendPointR + t*d.endPointR
                    
                    let theta1 = (1-t)*d.ptheta1 + t*d.theta1
                    let theta2 = (1-t)*d.ptheta2 + t*d.theta2
    
                    let x1 = r * -Math.sin(theta1);
                    let y1 = r * Math.cos(theta1);
                    let x2 = r * -Math.sin(theta2);
                    let y2 = r * Math.cos(theta2);
    
                    let largearcflag = Math.abs(theta1 - theta2) > Math.PI ? 1 : 0
                    return `M ${x1} ${y1} A ${r} ${r} ${0} ${largearcflag} ${1} ${x2} ${y2} `;
                })
            },
            exit => {
                // let descendants = trueDescendants(this.currentNode)
                // let exitOut = exit.filter(d => descendants.includes(d))
                // let exitRoot = exit.filter(d => !descendants.includes(d))
    
                // console.log(exit, exitOut, exitRoot)
    
                // let exitRootTheta = 0
    
                // exitRoot.select('.nodeCircle')
                // .transition().duration(DURATION)
                // .attr('r', 0)
                // // .attr('cx', this.rootSize * -Math.sin(exitRootTheta))
                // // .attr('cy', this.rootSize * Math.cos(exitRootTheta))
                // .attrTween('cx', d => (t)=>{
                //     let angle = (1-t)*d.theta + t*exitRootTheta
                //     let r = (1-t)*d.r + this.rootSize
                //     return r * -Math.sin(angle);
                // })
                // .attrTween('cy', d => (t)=>{
                //     let angle = (1-t)*d.theta + t*exitRootTheta
                //     let r = (1-t)*d.r + this.rootSize
                //     return r * Math.cos(angle);
                // })
    
                // exitRoot.select('.nodePath')
                // .transition().duration(DURATION)
                // .attr('d', )
    
                // exitRoot.select('.nodeLine')
                // .transition().duration(DURATION)
                // .attr('x1',this.rootSize * -Math.sin(exitRootTheta))
                // .attr('y1',this.rootSize * Math.cos(exitRootTheta))
                // .attr('x2',this.rootSize * -Math.sin(exitRootTheta))
                // .attr('y2',this.rootSize * Math.cos(exitRootTheta))
    
                exit
                .transition().duration(DURATION)
                .attr('opacity',0)
                .remove()
                return exit
            }
        )
    
        this.nodeCircleContainer.selectAll('.nodeCircle')
        .data(descendants, d=>d.data.TSN)
        .join(
            enter => {
                let that = this;
                let nodeCircle = enter.append('circle')
                .each(function(d) { d.nodeCircleElem = this })
                .attr('class', 'nodeCircle')
                .attr('stroke', 'none')
                .attr('opacity', 0)
                .attr('fill', this.color)
                .attr('cx', d=>d.x)
                .attr('cy', d=>d.y)
                .attr('r', d=>this.getNodeCircleRadius(d))
                .on('mouseover', function(e,d){
                    console.log(e)
                    console.log(d)
                    that.setRootNodeText(d.data.name)
                    that.highlight(d)
                })
                .on('mouseout', function(e,d){
                    that.setRootNodeText(that.currentNode.data.name)// marche pas il faut l'update je crois
                    that.unhighlight(d)
                })
                .on('click', (e, d)=>{this.update(d)})
                .transition('enter').duration(DURATION).delay(DURATION/2)//.delay(DURATION)
                .attr('opacity',1)
            },
            update => {
                update.transition('update').duration(DURATION)
                .attr('r', d=>this.getNodeCircleRadius(d))
                // .attr('cx',d=>d.x)//old
                .attrTween('cx', d => (t)=>{
                    let angle = (1-t)*d.ptheta + t*d.theta
                    let r = (1-t)*d.pr + t*d.r
                    return r * -Math.sin(angle);
                })
                .attrTween('cy', d => (t)=>{
                    let angle = (1-t)*d.ptheta + t*d.theta
                    let r = (1-t)*d.pr + t*d.r
                    return r * Math.cos(angle);
                })
            },
            exit => exit
            .transition('exit').duration(DURATION)
            .attr('opacity',0)
            .remove()
        )
    }
    
    MyRadialTree.prototype.drawParentBranch = function(node){
        
        // if(this.currentNode.parent)
        if(node.parent){//==this.rootnode?
            this.svg.select('.parentNodeCircle')
            .transition().duration(DURATION)
            .attr('r',this.nodeCircleRadius)
            .attr('cy',this.rootSize/2)
            
            this.svg.select('.parentNodeLine')
            .transition().duration(DURATION)
            .attr('y2',this.rootSize/2)
        }else{
            this.svg.select('.parentNodeCircle')
            .transition().duration(DURATION)
            .attr('r',0)
            .attr('cy',this.rootSize)
    
            this.svg.select('.parentNodeLine')
            .transition().duration(DURATION)
            .attr('y2',this.rootSize)
        }
    }
    
    MyRadialTree.prototype.drawNames = function(node){
        
        this.svg.selectAll('.leafName')
        .data(node.leaves().filter(d=>d != this.currentNode), d=>d.data.TSN)
        .join(
            enter => {
                let that = this
    
                let g = enter.append('g')
                .each(function(d) { d.labelElem = this })
                .attr('class','leafName')
                .attr('transform', d=>"translate("+ this.leafLabelRadius* -Math.sin(d.theta) +","+ this.leafLabelRadius* Math.cos(d.theta)+")")
                .attr('fill', this.color)
                
                g.attr('opacity',0)
                .transition().duration(DURATION).delay(DURATION/2)//.delay(DURATION)
                .attr('opacity',1)
    
                g.append('text')
                .attr('alignment-baseline', 'middle')
                .attr('text-anchor', d => d.theta < Math.PI ? 'end':'start')
                .attr('transform', d=>{
                    let angle = d.theta < Math.PI ? d.theta+Math.PI : d.theta
                    return "rotate("+ (angle/(2*Math.PI)*360 +90) +")"
                })
                .text(d=>d.data.name)
                .on('mouseover', function(e,d){
                    // that.setRootNodeText(d.data.name)
                    that.highlight(d)
                })
                .on('mouseout', function(e,d){
                    // that.setRootNodeText(that.currentNode.data.name)// marche pas il faut l'update je crois
                    that.unhighlight(d)
                })
                .on('click', (e, d)=>{
                    that.unhighlight(d)
                    this.update(d)
                })
                // return label
            },
    
            update => {
                update.transition('enter').duration(DURATION)
                .attrTween('transform', d =>(t)=>{
                    let angle = (1-t)*d.ptheta + t*d.theta
    
                    let x = this.leafLabelRadius* -Math.sin(angle)
                    let y = this.leafLabelRadius* Math.cos(angle)
                    return "translate("+ x +","+ y+")"
                })
    
                update.select('text')
                .transition('update').duration(DURATION)
                // .attr('transform', d=>{
                //     let angle = d.theta < Math.PI ? d.theta+Math.PI : d.theta
                //     return "rotate("+ (angle/(2*Math.PI)*360 +90) +")"
                // })
                // .attr('text-anchor', d => d.theta < Math.PI ? 'end':'start')
                .attrTween('transform', d=>t=>{
                    let angle = (1-t)*d.ptheta + t*d.theta
                    angle = angle < Math.PI ? angle+Math.PI : angle
                    return "rotate("+ (angle/(2*Math.PI)*360 +90) +")"
                })
                .attrTween('text-anchor', d => t=>{
                    let angle = (1-t)*d.ptheta + t*d.theta
                    return angle < Math.PI ? 'end':'start'
                 })
            },
    
            exit => {            
                exit
                .transition('exit').duration(DURATION)
                .attr('opacity',0)
                .remove()
                return exit
            }
        )
    }
    
    MyRadialTree.prototype.setRootNodeText = function(name){
        
        this.rootNodeText.text(name)
        .attr('font-size', this.rootNodeTextSize)
        this.rootNodeText.attr('font-size', ()=>{
            let textWidth = this.rootNodeText.node().clientWidth//_groups[0][0])
            let ratio = (this.rootSize-10)/textWidth
            
            return ratio < 1 ? this.rootNodeTextSize*ratio : this.rootNodeTextSize
        })
    }
    
    MyRadialTree.prototype.highlight = function(node){
      
        // d3.select(node.nodeCircleElem)
        // .transition().duration(this.highlightDuration)
        // .attr('r', this.nodeCircleRadiusActive)
    
        //highlight children branches and nodes
        node.descendants().forEach((nd)=>{
            d3.select(nd.branchElem)
            .attr('stroke', this.highlightColor)
            // .attr('stroke-width',2)
    
            d3.select(nd.nodeCircleElem)
            .attr('fill', this.highlightColor)
        })
        
        //highlight leaves labels
        node.leaves().forEach((nd)=>{
            d3.select(nd.labelElem)
            .attr('fill', this.highlightColor)
        })
    
        //highlight ancestors node circles
        node.ancestors().forEach((nd) =>{
            d3.select(nd.nodeCircleElem)
            .attr('fill', this.highlightColor)
        })
    
        this.rootCircle.attr('stroke',this.highlightColor)
        
        //highlight ancestors
        let d = `M ${node.x} ${node.y} `
        let nd = node.parent;
        let pnd = node
    
        while( nd != this.currentNode){
    
            let largearcflag = Math.abs(pnd.theta - nd.theta) > Math.PI ? 1 : 0
            let sweepflag = nd.theta > pnd.theta ? 1 : 0
            let endpointx = nd.endPointR * -Math.sin(nd.theta);
            let endpointy = nd.endPointR * Math.cos(nd.theta);
    
            d += `A ${nd.endPointR} ${nd.endPointR} ${1} ${largearcflag} ${sweepflag} ${endpointx} ${endpointy} L ${nd.x} ${nd.y} `
            
            pnd = nd;
            nd = nd.parent;
        }
    
        //append ancestor path
        this.highlightPathContainer.append('path')
        .attr('class','ancestorHighlightPath')
        .attr('d', d)
        .attr('fill', 'none')
        .attr('stroke', this.highlightColor)
    }
    
    MyRadialTree.prototype.unhighlight = function(node){
    
        // d3.select(node.nodeCircleElem)
        // .transition().duration(this.highlightDuration)
        // .attr('r', this.nodeCircleRadius)
        
        node.descendants().forEach((nd)=>{
            d3.select(nd.branchElem)
            .attr('stroke', this.color)
            // .attr('stroke-width',1)
    
            d3.select(nd.nodeCircleElem)
            .attr('fill', this.color)
        })
    
        node.leaves().forEach((nd)=>{
            d3.select(nd.labelElem)
            .attr('fill', this.color)
        })
    
        node.ancestors().forEach((nd) =>{
            d3.select(nd.nodeCircleElem)
            .attr('fill', this.color)
        })
    
        this.rootCircle.attr('stroke',this.color)
    
        this.highlightPathContainer.select('.ancestorHighlightPath').remove()
    }
    
    MyRadialTree.prototype.toggleLeafNames = function(value){
        this.showLeafNames = value
        if(this.showLeafNames){
            this.leafNameSize = 200;
        }else{
            this.svg.selectAll('.leafName')
            .transition().duration(DURATION)
            .attr('opacity',0)
            .remove()
            this.leafNameSize = 0;
        }
        this.resize(this.width, this.height)
        this.update(this.currentNode)
    }
    
    MyRadialTree.prototype.pruneNode = function(node){
    
        let offsets = []//le nombre de branches sans enfants car avec cluster, ils serront au même niveau que les dernières feuilles, donc il faudra les ajouter
        let leaves = []
        
        function countChildren(node, depth){//en fait on peut pas utiliser le node.depth parce que on doit forcément partir d'une depth de 0 même avec une sous branche dont la depth vaut +
            
            if(!offsets[depth]) offsets[depth]=0;
            if(!leaves[depth]) leaves[depth]=0;
    
            if(!node._children){
                offsets[depth]+=1;
            }else{
                leaves[depth]+=node._children.length
                node._children.forEach(child => {
                    countChildren(child, depth+1)
                });
            }
        }
        countChildren(node, 0)
    
        for(let i = 1; i< offsets.length; i++)// on cumule les offsets
            offsets[i] += offsets[i-1];
    
        for(let i = 0; i < leaves.length; i ++){
            leaves[i] += offsets[i];
        }
    
        let displayableDepth = 0;
        while(leaves[displayableDepth] && leaves[displayableDepth] <= this.maxleaves){
            displayableDepth++
        }
        // console.log(displayableDepth)
    
    
        node.descendants().forEach(elem=>{
            elem.children = elem._children;
        })
        node.descendants().forEach(elem=>{
            if(elem.depth == displayableDepth + node.depth){
                elem.children = null
            }
        })
    }
    
    MyRadialTree.prototype.resize = function(containerWidth, containerHeight){
        
        this.width = Math.min(containerWidth,containerHeight);
        this.height = this.width
        
        
        // this.rootSize = Math.min(100, this.height/6)
        this.leafLineRadius = this.height/2 - this.leafNameSize - this.leafOffset
        this.leafLabelRadius = this.leafLineRadius + this.leafOffset
        this.branchRange = this.leafLineRadius - this.leafLineSize - this.rootSize
    
        this.svgElement
        .attr("width", this.width)
        .attr("height", this.height)
        
        this.svg.attr("transform", "translate(" + (this.width/2+this.margin) + "," + (this.height/2+this.margin) +")")
        this.svg.select('.rootCircle')
        .attr('r', this.rootSize);
    
        this.cluster = d3.cluster()
        .size([2*Math.PI , this.branchRange])
        .separation((a, b) => 1);
    
        // this.maxleaves = 400;
        // maxleaf = 2*Math.PI* (this.heifht/2 - unoffsetselonlalongueurmaxdunnom?) / tailledepolice?
        this.maxleaves = 2*Math.PI* this.leafLineRadius / 20
        // console.log("maxleaves = "+this.maxleaves)
    }
    
    MyRadialTree.prototype.getNodeCircleRadius = function(node){
        if(node == this.currentNode)
            return 0;
        else
            return this.nodeCircleRadius;
    }
    
    MyRadialTree.prototype.getBranchPath = function(d){
    
        if(d.children && d.children.length > 0){
            let firstchild = d.children[0]
            let lastchild = d.children[d.children.length-1]
    
            let largearcflag = Math.abs(lastchild.theta - firstchild.theta) > Math.PI ? 1 : 0
            return `M ${firstchild.x} ${firstchild.y} A ${firstchild.r} ${firstchild.r} ${0} ${largearcflag} ${1} ${lastchild.x} ${lastchild.y} `
        }else{
            return `M ${this.leafLineRadius * -Math.sin(d.theta)} ${this.leafLineRadius * -Math.sin(d.theta)} `//il interpole le flag du coup il warn
            //check out curveto "C" instead 
        }
    }
    
    MyRadialTree.prototype.getNodeLineEndPoint = function(d){
        let P = {};
        if(d.children && d.children.length > 0){//pas sur qu'il faille check la length, btw il check pas le 2è statement si il y a pas children donc pas d'err
            P.x = d.children[0].r * -Math.sin(d.theta);
            P.y = d.children[0].r * Math.cos(d.theta);
        }else{
            P.x = this.leafLineRadius * -Math.sin(d.theta);
            P.y = this.leafLineRadius * Math.cos(d.theta);
        }
        return P;
    }
    
    function trueDescendants(node){//since I use _children trick,
        let trueDescendants = []
    
        function truc(nd){
            if(nd._children){
                nd._children.forEach(n => {
                    trueDescendants.push(n);
                    truc(n);
                });
            }
        }
        truc(node);
        return trueDescendants;
    }  
    
    function radialInterpolatorX(r1, theta1, r2, theta2){
        return (t)=>{
            let angle = (1-t)*theta1 + t*theta2
            let r = (1-t)*r1 + t*r2
            return r * -Math.sin(angle);
        }
    }
    function radialInterpolatorY(r1, theta1, r2, theta2){
        return (t)=>{
            let angle = (1-t)*theta1 + t*theta2
            let r = (1-t)*r1 + t*r2
            return r * Math.cos(angle);
        }
    }