使用vue.js+d3生成树以及遇到的坑

1. vue.js

安装vue.js

最新稳定版本
$ npm install vue
最新稳定 CSP 兼容版本
$ npm install vue@csp

2. d3

安装d3

$ npm i d3 --save

vue中引入

import * as d3 from 'd3'

3. 代码部分

(1)生成树实现

参考:https://www.jianshu.com/p/4e517d4c3885
参考文章里实现了点击节点会收起子节点功能,由于我的需求里没有用到就删除了那部分,如果有需要的去原文章里看哦~
(在实现中根据需求我将树的圆形结点改为了矩形结点,需要圆形结点可将)

nodeEnter.append("rect")

改为

nodeEnter.append("circle")

生成树部分代码

<script>
const dataset = {
    name:"标题1",
    children:[
    			{
    				name:"标题2标题2标题2标",
    				children:[
    					{
    						name:"标题3",
    						children:[
    							{name:"标题4",value:100},
    						]
    					},
    					{name:"标题3",value:100},
            			{name:"标题3",value:100},
    				]
    			},
    			{
    				name:"标题2标题2标",
    				children:[
    					{name:"标题3" ,value:100},
    					{name:"标题3",value:100},
            			{name:"标题3",value:100},
    				]
    			}
    		]
}
import * as d3 from 'd3'
export default {
	name: 'Scale',
	data() {
			return {
				id: '',
				zoom: null,
				index: 0,
				duration: 750,
				root: null,
				nodes: [],
				links: [],
				dTreeData: null,
				transform: null,
				margin: { top: 0, right: 90, bottom: 30, left: 180 }
				
			}
	},
	methods: {
		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(dataset, d => { 
                return d.children
            })
            root.x0 = this.height / 2
            root.y0 = 0
            return root
        },
        clicktext (d) {
            this.$alert(d.data.name, '标题名称', {
            confirmButtonText: '确定',
          });
        },
        diagonal (s, d) {
            return `M ${s.y} ${s.x}
                    C ${(s.y + d.y) / 2} ${s.x},
                    ${(s.y + d.y) / 2} ${d.x},
                    ${d.y} ${d.x}`
        },

        /**
         * @description 获取构造的node数据和link数据
         */
        getNodesAndLinks () {
            // 树状图根据根节点生成新的x、y坐标, 
            // 所以不能使用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 * 140
            })
            // *************************** Nodes section *************************** //
            // 更新节点
            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)
                }) 
            // 在父级之前的位置输入任何新源
            let nodeEnter = node.enter().append('g')
                .attr('class', 'node')
                //.on('click', this.clickNode)
                .attr('transform', d => {
                    return 'translate(' + source.y0 + ',' + source.x0 + ')'
                })  
            nodeEnter.append("rect")
                .attr("width", 85)
                .attr("height",30)
                .attr("x",function(d){
                    return d.children?-50:-1;
                  })
                .attr("y",-16)
                .attr("dy",10)
                .attr("rx",5)
                .attr("ry",5)
                .attr("fill","#0076B3")
                .attr("stroke","#0076B3")
                .attr("stroke-width",1)
 
            nodeEnter.append("foreignObject")
                .attr("x",function(d){
                      return d.children?-30:20;
                    })
                .attr("y",-10)
                .attr("dy",10)
                .attr("width","50")
                .attr("height","20")
                .on("click", this.clicktext)
                .text(function(d){
                    return d.data.name;
                })
                .style("font-size","14px");

            let nodeUpdate = nodeEnter.merge(node)
                .transition()
                .duration(this.duration)
                .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
 
 
            // *************************** Links section *************************** //
            // 更新链接
            let link = container.selectAll('path.link')
                .data(this.links, d => { return d.id })
            
            // 在父级之前的位置输入任何新链接
            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')
            // 过渡链接到他们的新位置
            let linkUpdate = linkEnter.merge(link)
            linkUpdate.transition()
                .duration(this.duration)
                .attr('d', d => { return this.diagonal(d, d.parent) })
 
        },
        /** 
         * @description 控制画布放大或缩小
         */
        zoomed () {
            d3.select(this.$el).select('g.container').attr('transform', d3.event.transform)
        }
	},
	created() {
        this.id = this.uuid()
    },
    mounted () {
        //创建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')
            .attr('width', this.width)
            .attr('height', this.height)
        const transform = d3.zoomIdentity.translate(this.margin.left, this.margin.top).scale(1)    
        const container = svg.select('g.container')
        // 初始化缩放行为,它既是对象又是函数
        this.zoom = d3.zoom()
            .scaleExtent([1 / 2, 8])
            .on('zoom', this.zoomed)
        container.transition().duration(750).call(this.zoom.transform, transform)
        //svg.call(this.zoom) 可随意移动树的位置
        this.root = this.getRoot()
        this.update(this.root)
    },
    computed: {
        treemap () {
            return d3.tree().size([this.height, this.width])
        }
    }

}

</script>

(2)如果想要树的连接线是直线

diagonal (s, d) {
            return `M ${s.y} ${s.x}
                      L ${(s.y + d.y) / 2} ${s.x},
                      L ${(s.y + d.y) / 2} ${d.x},
                      ${d.y} ${d.x}`
},

(3)处理文字溢出,溢出部分显示…

a. 坑1

需求是文字溢出要显示…,当光标移上去时显示所有文字
一看到这个我想这好办吖,overflow ellipsis 吧啦吧啦不就得了。
可是不管我怎么写都不行的时候我发现我错了,原来使用svg里面的text是没有width属性的,按传统css方法overflow不起作用,快把百度翻遍了也找不到一个合适的方法,突然看到一个回答说使用foreignObject,赶紧试了一下,样式终于起作用了!

.d3-tree 
	.node foreignObject 
		font: 14px sans-serif;
		color #fff;
		overflow: hidden;
		white-space: nowrap;
		text-overflow: ellipsis;
		&:hover
			overflow: visible;

(4)鼠标点击提示框显示完整标题

其实完全可以直接使用alert,但这里使用了element ui组件的提示框,我jiao的比较好看~
(使用要安装element-ui哦)

$ npm install element-ui -S

main.js

import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

Vue.use(ElementUI)
clicktext (d) {
            this.$alert(d.data.name, '标题名称', {
            confirmButtonText: '确定',
          });
}

在这里插入图片描述

(5)全部样式

<style lang='stylus'>
	.down{
		border: 1px solid rgb(196, 196, 196);
		width: 822px;
		height: 387px;
		margin-top: 24px;
		background-color:rgb(255, 255, 255);
		
	}
	.p_title
		width:48px;
		height:20px;
		font-size:14px;
		font-family:PingFangSC-Regular;
		font-weight:400;
		color:rgba(45,100,119,1);
		line-height:20px;
		margin: 30px 640px 0px 134px;

	.tree-container 
		width: 100%;
		height: 320px;

	.d3-tree 
		.node foreignObject 
			font: 14px sans-serif;
			color #fff;
			overflow: hidden;
			white-space: nowrap;
			text-overflow: ellipsis;
			&:hover
				overflow: visible;
</style>

a. 坑2

一开始在样式处加了 scoped,使用css修改树结点样式总是不起作用,发现原来是 scoped在作祟,最终删除 scoped就能正常显示了

//<style lang="stylus" scoped>
 <style lang="stylus">

(6)HTML部分

<template lang='pug'>
	<div class="down">
	<p class="p_title">目录树</p>
		div.tree-container(:id="id")
			svg.d3-tree 
				g.container
	</div>
</template>

a. 坑3

Cannot find module 'pug'

记得要安装pug吖
啥事是pug?
是一个HTML模板引擎,它是HAML在JavaScript上的实现,在这里是使用缩进排列替代成对标签。

4. 结果展示

在这里插入图片描述

5. 完整代码

<template lang='pug'>
	<div class="down">
	<p class="p_title">目录树</p>
		div.tree-container(:id="id")
			svg.d3-tree 
				g.container
	</div>
</template>
<script>
const dataset = {
    name:"标题1",
    children:[
    			{
    				name:"标题2标题2标题2标",
    				children:[
    					{
    						name:"标题3",
    						children:[
    							{name:"标题4",value:100},
    						]
    					},
    					{name:"标题3",value:100},
            			{name:"标题3",value:100},
    				]
    			},
    			{
    				name:"标题2标题2标",
    				children:[
    					{name:"标题3" ,value:100},
    					{name:"标题3",value:100},
            			{name:"标题3",value:100},
    				]
    			}
    		]
}
import * as d3 from 'd3'
export default {
	name: 'Scale',
	data() {
			return {
				id: '',
				zoom: null,
				index: 0,
				duration: 750,
				root: null,
				nodes: [],
				links: [],
				dTreeData: null,
				transform: null,
				margin: { top: 0, right: 90, bottom: 30, left: 180 }
				
			}
	},
	methods: {
		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(dataset, d => { 
                return d.children
            })
            root.x0 = this.height / 2
            root.y0 = 0
            return root
        },
        clicktext (d) {
            this.$alert(d.data.name, '标题名称', {
            confirmButtonText: '确定',
          });
        },
        diagonal (s, d) {
            return `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 `M ${s.y} ${s.x}
            //         L ${(s.y + d.y) / 2} ${s.x},
            //         L ${(s.y + d.y) / 2} ${d.x},
            //         ${d.y} ${d.x}`
        },

        /**
         * @description 获取构造的node数据和link数据
         */
        getNodesAndLinks () {
            // 树状图根据根节点生成新的x、y坐标, 
            // 所以不能使用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 * 140
            })
            // *************************** Nodes section *************************** //
            // 更新节点
            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)
                }) 
            // 在父级之前的位置输入任何新源
            let nodeEnter = node.enter().append('g')
                .attr('class', 'node')
                .attr('transform', d => {
                    return 'translate(' + source.y0 + ',' + source.x0 + ')'
                })  
            nodeEnter.append("rect")
                .attr("width", 85)
                .attr("height",30)
                .attr("x",function(d){
                    return d.children?-50:-1;
                  })
                .attr("y",-16)
                .attr("dy",10)
                .attr("rx",5)
                .attr("ry",5)
                .attr("fill","#0076B3")
                .attr("stroke","#0076B3")
                .attr("stroke-width",1)
 
            nodeEnter.append("foreignObject")
                .attr("x",function(d){
                      return d.children?-30:20;
                    })
                .attr("y",-10)
                .attr("dy",10)
                .attr("width","50")
                .attr("height","20")
                .on("click", this.clicktext)
                .text(function(d){
                    return d.data.name;
                })
                .style("font-size","14px");

            let nodeUpdate = nodeEnter.merge(node)
                .transition()
                .duration(this.duration)
                .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
 
 
            // *************************** Links section *************************** //
            // 更新链接
            let link = container.selectAll('path.link')
                .data(this.links, d => { return d.id })
            
            // 在父级之前的位置输入任何新链接
            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')
            // 过渡链接到他们的新位置
            let linkUpdate = linkEnter.merge(link)
            linkUpdate.transition()
                .duration(this.duration)
                .attr('d', d => { return this.diagonal(d, d.parent) })
 
        },
        /** 
         * @description 控制画布放大或缩小
         */
        zoomed () {
            d3.select(this.$el).select('g.container').attr('transform', d3.event.transform)
        }
	},
	created() {
        this.id = this.uuid()
    },
    mounted () {
        //创建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')
            .attr('width', this.width)
            .attr('height', this.height)
        const transform = d3.zoomIdentity.translate(this.margin.left, this.margin.top).scale(1)    
        const container = svg.select('g.container')
        // 初始化缩放行为,它既是对象又是函数
        this.zoom = d3.zoom()
            .scaleExtent([1 / 2, 8])
            .on('zoom', this.zoomed)
        container.transition().duration(750).call(this.zoom.transform, transform)
        //svg.call(this.zoom) 可随意移动树的位置
        this.root = this.getRoot()
        this.update(this.root)
    },
    computed: {
        treemap () {
            return d3.tree().size([this.height, this.width])
        }
    }

}

</script>

<style lang='stylus'>
	.down{
		border: 1px solid rgb(196, 196, 196);
		width: 822px;
		height: 387px;
		margin-top: 24px;
		background-color:rgb(255, 255, 255);
		
	}
	.p_title
		width:48px;
		height:20px;
		font-size:14px;
		font-family:PingFangSC-Regular;
		font-weight:400;
		color:rgba(45,100,119,1);
		line-height:20px;
		margin: 30px 640px 0px 134px;

	.tree-container 
		width: 100%;
		height: 320px;

	.d3-tree 
		.node foreignObject 
			font: 14px sans-serif;
			color #fff;
			overflow: hidden;
			white-space: nowrap;
			text-overflow: ellipsis;
			&:hover
				overflow: visible;
</style>

结语

初接触前端vue.js,有问题还请多多指出。
一点儿心得…遇到问题没关系,只要不放弃,总能解决…

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值