D3.js的V5版本-Vue框架中使用-树状图

一. api简介

d3.tree(),创建一个树状图生成器
d3.tree().size(),定义树的大小
d3.hierarchy(),层级布局,需要和tree生成器一起使用,来得到绘制树所需要的节点数据和边数据
node.descendants()得到所有的节点,已经经过转换的数据
node.links(),得到所有的边,已经经过转换的数据

二.Vue中使用

*点击节点可展开收缩


<template lang='pug'>
    div.tree-container(:id="id")
        svg.d3-tree 
            g.container
</template>
<script>
/**
 * 树状图
 */
//数据
const dataset = {
    name:"中国",
    children:[
        {
            name:"浙江",
            children:[
                {name:"杭州" ,value:100},
                {name:"宁波",value:100},
                {name:"温州",value:100},
                {name:"绍兴",value:100}
            ]
        },
        {
            name:"新疆" , 
            children:
            [
                {name:"乌鲁木齐"},
                {name:"克拉玛依"},
                {name:"吐鲁番"},
                {name:"哈密"}
            ]
        },
        {
            name:"新疆" , 
            children:
            [
                {name:"乌鲁木齐"},
                {name:"克拉玛依"},
                {name:"吐鲁番"},
                {name:"哈密"}
            ]
        }
    ]
}

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: 20, right: 90, bottom: 30, left: 90 }
        }
    },
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
        },
clickNode (d) {
            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)                
                }
            )
        },

 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 () {
            // 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')
                .on('click', this.clickNode)
                .attr('transform', d => {
                    return 'translate(' + source.y0 + ',' + source.x0 + ')'
                })  
            nodeEnter.append("circle")
                .attr("r", 10)
                .style("fill", function(d) { return d.children || d._children ? "lightsteelblue" : "#fff"; });
 
            nodeEnter.append("text")
                .attr("x", function(d) { return d.children || d._children ? -15 : 15; })
                .attr("dy", ".35em")
                .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
                .text(function(d) { return d.data.name })
                .style("fill-opacity", 1e-6);
            // Transition nodes to their new position.
            let nodeUpdate = nodeEnter.merge(node)
                .transition()
                .duration(this.duration)
                .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
 
            nodeUpdate.select("circle")
                .attr("r", 10)
                .attr("fill", "white")
                .attr("stroke", "blue")
                .attr("stroke-width", 1)
                .style("fill", function(d) { return d.children || d._children ? "lightsteelblue" : "#fff"; });
 
            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.y + "," + source.x + ")"; })
                .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
            })
        },
        /** 
         * @description control the canvas zoom to up or down
         */
        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')
        // init zoom behavior, which is both an object and function
        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='scss' scoped>
.tree-container {
    width: 100%;
    height: 1000px;
}
 
.d3-tree {
    .node {
        cursor: pointer;
    }
 
    .node circle {
        fill: #fff;
        stroke: steelblue;
        stroke-width: 1.5px;
    }
 
    .node text {
        font: 18px sans-serif;
    }
 
    .link {
        fill: none;
        stroke: #ccc;
        stroke-width: 1.5px;
    }
}
</style>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容