d3.js树状图

1 篇文章 0 订阅
1 篇文章 0 订阅

d3.js画树状图

我们在项目开发过程中,会遇到项目要求:可视化的树状图,1:满足收缩展开。2:点击跳转。 3:放大缩小,平移。4:鼠标移到某一范围,弹框信息提示 我 研究了一下echart.js中的树状图,echart.js可满足 1:收缩展开,2:放大缩小,平移。3:鼠标移到某一范围, 弹框信息提示。**而且echart.js中展开收缩展开和点击跳转事件冲突 **,考虑以上种种, 我就使用 d3.js构建树状图了。
效果图:满足点击缩放,点击视图跳转,鼠标移上显示具体信息,拖动,鼠标滚轮放大缩小

直接上代码

<template>
  <div :id="id" class="tree-container">    
      <svg class="d3-tree ">
          <g class="container"></g>
      </svg> 
      <!-- 加字图片  -->
      <svg id="mySvg_jia" width="0" height="0" >
        <defs id="mdef">
            <pattern id="image_jia" x="0" y="0" height="20" width="20">
            <image x="0" y="0" width="20" height="20" xlink:href="@/assets/images/taskManage/jia.svg"></image>
            </pattern>
        </defs>
       </svg> 
       <!-- 减字图片  -->
      <svg id="mySvg_jian" width="0" height="0" >
        <defs id="mdef">
            <pattern id="image_jian" x="0" y="0" height="20" width="20">
            <image x="0" y="0" width="20" height="20" xlink:href="@/assets/images/taskManage/jian.svg"></image>
            </pattern>
        </defs>
       </svg> 
      <div class=" tip_content" v-if="visible"  :style="{left:clientX+'px',top:clientY+'px'}">
          <div>任务名称:{{popoverData.taskName?popoverData.taskName:''}}</div> 
          <div>{{popoverData.taskLevel===1||popoverData.taskLevel===2?"创建人":"分解人"}}:{{popoverData.createName?popoverData.createName:''}}</div>          
          <div>{{popoverData.taskLevel===1?'审核人:':'上级审核人:'}}{{popoverData.superAuditorName?popoverData.superAuditorName:''}}</div>
          <div>处理人:{{popoverData.taskHandlerUserStr?popoverData.taskHandlerUserStr:''}}</div> 
          <div>任务完成进度:{{popoverData.taskProgress?popoverData.taskProgress:'0/0'}}</div> 
          <div>当前任务状态:{{popoverData.taskStatusName?popoverData.taskStatusName:''}}</div> 
          <div>当前反馈起止时间:{{popoverData.taskStartTime?popoverData.taskStartTime:'无'}} - {{popoverData.taskEndTime?popoverData.taskEndTime:'无'}}</div> 
          <div :class="popper_arrow==='right'?'arrow_right':'arrow_left'" ></div>
      </div>   
      <trajectoryTable :dialogShow.sync="dialogShow" v-if="dialogShow" :clickData="clickData" :orderMes="orderMes"></trajectoryTable> 
      <!-- 新增任务,编辑任务 -->
      <addMes  :dialogShow.sync="addShow" v-if="addShow"   :orderMes="trajectoryMes" :organizationObj="organizationObj" @close="closeDia" ></addMes>
 </div>
</template>
mounted () {
        this.findTaskTrack()
        //创建svg画布
        this.width = document.getElementById(this.id).clientWidth
        this.height = document.getElementById(this.id).clientHeight
        const svg = d3.select(this.$el).select('svg.d3-tree');
        const transform = d3.zoomIdentity.translate(this.width/2, 140).scale(1) ;        
        // init zoom behavior, which is both an object and function
        this.zoom = d3.zoom()         
            .scaleExtent([1 / 8, 8])        
            .on('zoom', (e)=>{                  
                this.visible = false;            
                this.x =  d3.event.transform.x ;                  
                this.y = d3.event.transform.y ; 
                this.scale = d3.event.transform.k                      
                d3.select(this.$el).select('g.container').attr('transform', d3.event.transform)
            })
        svg.call(this.zoom)
        svg.transition().duration(750).call(this.zoom.transform, transform)     
    },   
    computed: {
        treemap () {          
            return d3.tree().size([this.height, this.width]).nodeSize([240, 200]);
        }
    },
  methods: {  
        // 编辑  分解
    editMes(row,type){           
        this.trajectoryMes = {      
            type: type,
            data: {...row,id:row.taskId},
            taskMethod: 1,
            verifyPermissions: 'none'
        };
        this.addShow = true;
    },   
    // 关闭弹框 
    closeDia(bol){
        this.addShow = false;
        this.findTaskTrack()
        //    this.visibleShow = false;
        //    this.showMes = false;
        //    bol?this.handleSearch():this.handleSearchClear()
    },  
    uuid () {
        function s4 () {
            return Math.floor((1 + Math.random()) * 0x10000)
                .toString(16)
                .substring(1)
        }
        return (
            s4() + s4() + '-' + s4() + '-' + s4() +  '-' + s4() + '-' + s4() + s4() + s4()
        )
    },
    /**
     * @description 获取构造根节点
     */
    getRoot () {
        let root = d3.hierarchy(this.treeData, d => { 
            return d.subTaskList
        })
        root.x0 =  this.width / 2
        root.y0 = 140
        return root
    },
    /**
     * @description 点击节点,展开or收缩
     */
    clickNode (d) {   
        this.stopDialog = true;    
        if (!d._children && !d.children)
            return
        if (d.children) {
            this.$set(d, '_children', d.children)          
            d.children = null
        } else {
            this.$set(d, 'children', d._children)
            d._children = null
        }
        this.$nextTick(
            () => {
                this.update(d)                
            }
        )
    },
    // 点击穿透
    clickUrl(d){
        console.log(d)
        if(!this.planOrGroundBol) return;
        this.clickData = d.data;
        this.dialogShow = true;            
    },
    // 点击分解按钮
    decompose(d){
        var e = d3.event;                         
        if(e.stopPropagation){
            e.stopPropagation();
        }else{
            window.event.returnValue == false;
        }
        this.clickData = d.data;
        this.editMes(d.data,'dis')
    },
    // 鼠标移上效果
    addVisible(d){           
        if(this.stopDialog) return this.stopDialog = false;           
        this.visible = true;          
        this.popoverData = d.data;                     
        if(this.x+(d.x+110)*this.scale>this.width-320) {
            this.popper_arrow = 'left';
            this.clientX = this.x+(d.x-110)*this.scale-336;
        }else{
            this.popper_arrow = 'right';
            this.clientX =  16+this.x+(d.x+110)*this.scale;
        }   
        this.clientY = this.y+(d.y-120)*this.scale;
    },
    delVisible(d){
        this.visible = false
    },
    diagonal (s, d) {              
    //    `M ${s.x} ${s.y}
    //             C ${(s.x + d.x) / 2} ${s.y},
    //             ${(s.x + d.x) / 2} ${d.y},
    //             ${d.x} ${d.y}`  
        return `M ${s.x} ${s.y}
                C ${s.x} ${d.y},
                ${s.x} ${d.y},
                ${d.x} ${d.y}`
    },
    /**
     * @description 获取构造的node数据和link数据
     */
    getNodesAndLinks () {
        // treemap generate new x、y coordinate according to root node, 
        // so can‘t use computed propter of vue
        this.dTreeData = this.treemap(this.root)
        this.nodes = this.dTreeData.descendants()
        this.links = this.dTreeData.descendants().slice(1)
    },
    /** 
     * @description 数据与Dom进行绑定
     */
    update (source) {         
        this.getNodesAndLinks()
        this.nodes.forEach(d => {               
            d.y = d.depth * 180
        })
        // *************************** Nodes section *************************** //
        // Update the nodes...
        const svg = d3.select(this.$el).select('svg.d3-tree')
        const container = svg.select('g.container')
        let node = container.selectAll('g.node')
            .data(this.nodes, d => {
                return d.id || (d.id = ++this.index)
            }) 
        // Enter any new sources at the parent's previous position.
        let nodeEnter = node.enter().append('g')
            .attr('class', 'node')               
            .attr('transform', d => {                   
                return 'translate(' + source.x0 + ',' + source.y0 + ')'
        })          
        nodeEnter.append("circle")
            .attr("r", 10)
            .attr("stroke", "#fff")
            .attr("stroke-width", 1)
            .on('click', this.clickNode)                  
            .style("fill", function(d) { 
                if(d.children) return  "url(#image_jian)";
                if(d._children) return  "url(#image_jia)";
                return "#fff"; 
            });
        // 创建一个    
        let rectText = nodeEnter.append("g")  
                .on('click', this.clickUrl)
                .on('mouseenter', this.addVisible)               
                .on('mouseleave', this.delVisible)     
                .style("fill-opacity", 1)
                .style("cursor", 'pointer')             
                .attr("transform", function(d) { return "translate(" + 0 + "," + 0 + ")"; });
        // 节点背景图 
        rectText.append("rect")              
            .attr("x", -110)
            .attr("y", -120)
            .attr("rx", 6)
            .attr("ry", 6)
            .attr("width", 220)
            .attr("height", 120)
            .attr("fill", "white")              
            .attr("stroke", (d)=>{
                if(d.data.taskLevel===1){
                        return "#33a39c"
                    }else{
                        return "#999"
                    }
            })  
            // .html(function(d,i) { "#33a39c"
            //     return 'some text' + '<p class="eqe">1545</p>' + d.data.name;
            // })                           
            .style("fill-opacity", 1);

        // 标题区域
        let titleText = rectText.append("g")                             
            .attr("transform", function(d) { return "translate(" +-110 + "," + -120 + ")"; });
            titleText.append("rect")              
            .attr("width", 220)
            .attr("height", 40)
            .attr("rx", 6)
            .attr("ry",6)
            .attr("fill", "#33a39c")
            .attr("stroke", "#33a39c")  
            // .html(function(d,i) {
            //     return 'some text' + '<p class="eqe">1545</p>' + d.data.name;
            // })                           
            .style("fill-opacity", 1);
        // 标题
        titleText.append("text")
            .attr("x",(d)=>{
                return this.orderMes.source==='all'&&d.data.taskLevel!==1?90:110
            })
            .attr("y", 0)            
            .attr("dy", 26)  
            .attr("fill", "#fff")                                      
                .attr("text-anchor", 'middle' )
                .text(function(d) { 
                    if(d.data.taskName&&d.data.taskName.length>=7){
                        return `${d.data.taskLevel===1?'':'子'}任务名称:${d.data.taskName.substring(0,6)}...`
                    }else{
                        return `${d.data.taskLevel===1?'':'子'}任务名称:${d.data.taskName}`
                    }                    
                })                         
            .style("fill-opacity", 1e-6);
        // 分解按钮
        titleText.append("text")
            .on('click', this.decompose)
            .attr("x",160)
            .attr("y", 0)            
            .attr("dy", 26)  
            .attr("fill", "#0000dd")                                   
                .attr("text-anchor", 'right' )
                .text('(分解)')  
                .style("display",(d)=>{
                    //return 'block'
                    return this.orderMes.source==='all'&&d.data.taskLevel!==1?'block':'none'
                })        
                .style("text-decoration","underline")                    
            .style("fill-opacity", 1);

        // 创建人  
        rectText.append("text")
            .attr("x",-100)
            .attr("y", -75)            
            .attr("dy", 20)            
            .attr("text-anchor", 'left' )
                .text(function(d) { return '创建人:'+d.data.createName })                         
            .style("fill-opacity", 1);
        // 处理人  
        rectText.append("text")
            .attr("x",-100)
            .attr("y", -40)            
            .attr("dy", 20)              
            .attr("text-anchor", 'left' )
            .text(function(d) {
                    if(d.data.taskHandlerUserStr&&d.data.taskHandlerUserStr.length>=7){
                        return '处理人 :'+d.data.taskHandlerUserStr.substring(0,6)+'...'
                    }else{
                        return '处理人 :'+d.data.taskHandlerUserStr
                    }                    
                })                         
            .style("fill-opacity", 1);
        // 进度条
        let circleText = rectText.append("g")                    
            .attr("transform", function(d) { return "translate(" + 80 + "," + -50 + ")"; });
        circleText.append("circle")
            .attr("r", 16)              
            .style("fill", function(d) {  
                if(d.data.taskStatus===1){
                    return "#f7c692"; 
                }else if(d.data.taskStatus===2){
                    return "#FA8C15"; 
                }else if(d.data.taskStatus===3){
                    return "#64D16D"; 
                } else if(d.data.taskStatus===4){
                    return "#1592E6"; 
                } else if(d.data.taskStatus===5){
                    return "#666565"; 
                }else{
                    return "#E62412"; 
                }               
                
            });  
       // 进度值
        circleText.append("text")                                 
            .attr("y", 40)              
            .attr("text-anchor", 'middle' )
            .attr("fill", function(d) {  
                if(d.data.taskStatus===1){
                    return "#f7c692"; 
                }else if(d.data.taskStatus===2){
                    return "#FA8C15"; 
                }else if(d.data.taskStatus===3){
                    return "#64D16D"; 
                } else if(d.data.taskStatus===4){
                    return "#1592E6"; 
                } else if(d.data.taskStatus===5){
                    return "#666565"; 
                }else{
                    return "#E62412"; 
                }               
                
            })
                .text(function(d) { return d.data.taskProgress })                       
            .style("fill-opacity", 1);

        // Transition nodes to their new position.
        let nodeUpdate = nodeEnter.merge(node)
            .transition()
            .duration(this.duration)
            .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
        // 节点更新
        nodeUpdate.select("circle")
            .attr("r", 10)              
            .attr("stroke", "#fff")
            .attr("stroke-width", 1)
            .style("fill", function(d) { 
                if(d.children) return  "url(#image_jian)";
                if(d._children) return  "url(#image_jia)";
                return "#fff"; 
                })
            .attr("transform", function(d) { return "translate(" + 0 + "," + 10 + ")"; });
        nodeUpdate.select("text")
            .style("fill-opacity", 1)               

        // Transition exiting nodes to the parent's new position.
        let nodeExit = node.exit()
            .transition()
            .duration(this.duration)
            .attr("transform", function(d) {                  
                    return "translate(" + source.x + "," + source.y + ")"; 
            })
            .remove();

        nodeExit.select("circle")
            .attr("r", 1e-6);

        nodeExit.select("text")
            .style("fill-opacity", 1e-6);

        // *************************** Links section *************************** //
        // Update the links…
        let link = container.selectAll('path.link')
            .data(this.links, d => { return d.id })
        
        // Enter any new links at the parent's previous position.
        let linkEnter = link.enter().insert("path", "g")
            .attr("class", "link")
            .attr("d", d => {
                let o = {x: source.x0, y: source.y0};
                return this.diagonal(o, o)
            })
            .attr("fill", 'none')
            .attr("stroke-width", 1)
            .attr('stroke', '#ccc')
        // Transition links to their new position.
        let linkUpdate = linkEnter.merge(link)
        linkUpdate.transition()
            .duration(this.duration)
            .attr('d', d => { return this.diagonal(d, d.parent) })

        // Transition exiting nodes to the parent's new position.
        link.exit().transition()
            .duration(this.duration)
            .attr("d", d => {
                let o = {x: source.x, y: source.y};
                return this.diagonal(o, o)
            })
            .remove();

        // Stash the old positions for transition.
        this.nodes.forEach(d => {
            d.x0 = d.x
            d.y0 = d.y
        })
    },
``
欢迎互相学习, 不动了发我邮箱  ty_html5@163.com
  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 19
    评论
d3.js树状图具有交互效果的展开折叠可以通过以下步骤来实现: 1. 定义树形布局,设置节点大小和节点之间的间隔: ``` const treeLayout = d3.tree().nodeSize([height, width]); ``` 2. 加载数据,生成树形结构: ``` const root = d3.hierarchy(data); const treeData = treeLayout(root); ``` 3. 定义节点和连线的生成器: ``` const nodeGenerator = d3 .linkHorizontal() .x((d) => d.y) .y((d) => d.x); const linkGenerator = d3 .linkHorizontal() .x((d) => d.y) .y((d) => d.x); ``` 4. 绘制节点: ``` const nodes = svg .selectAll("g.node") .data(treeData.descendants()) .enter() .append("g") .attr("class", "node") .attr("transform", (d) => `translate(${d.y},${d.x})`) .on("click", (d) => { if (d.children) { d._children = d.children; d.children = null; } else { d.children = d._children; d._children = null; } update(d); }); nodes.append("circle").attr("r", 10); nodes .append("text") .attr("dx", 12) .attr("dy", 4) .text((d) => d.data.name); ``` 5. 绘制连线: ``` const links = svg .selectAll("path.link") .data(treeData.links()) .enter() .append("path") .attr("class", "link") .attr("d", linkGenerator); ``` 6. 定义更新函数,用于更新节点和连线: ``` function update(source) { const nodes = svg.selectAll("g.node").data(treeData.descendants()); const links = svg.selectAll("path.link").data(treeData.links()); nodes .enter() .append("g") .attr("class", "node") .attr("transform", (d) => `translate(${source.y0},${source.x0})`) .merge(nodes) .transition() .duration(500) .attr("transform", (d) => `translate(${d.y},${d.x})`); nodes.exit().remove(); nodes .select("circle") .attr("r", 10) .attr("class", (d) => (d._children ? "collapsed" : "")); links .enter() .append("path") .attr("class", "link") .attr("d", () => { const o = { x: source.x0, y: source.y0 }; return linkGenerator({ source: o, target: o }); }) .merge(links) .transition() .duration(500) .attr("d", linkGenerator); links.exit().remove(); treeData.descendants().forEach((d) => { d.x0 = d.x; d.y0 = d.y; }); } ``` 7. 定义CSS样式,用于控制节点的展开和折叠: ``` .collapsed { fill: white; stroke: steelblue; stroke-width: 2px; } ``` 以上是实现具有交互效果的展开折叠的树状图的基本步骤,具体的实现还需要根据实际需求进行调整和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值