d3可视化展示-d3.layout.pack-relation层级关系数量可视化

1、先看效果。如下图所示,

在线演示:

视频链接(带讲解):

截图:

2、数据及可视化含义。

这是一个可视化demo,使用d3.js v3开发;

可视化含义:全国参加南京篮球赛的运动员的来源(户籍分布)。6个代表省(直辖市)的泡泡固定不动,人数最多的前5个省份显示其省(直辖市)名称,泡泡直径映射其人数多少(因为人数差异很大,所以使用指数标尺);省(直辖市)内部的动态泡泡映射其下属区县人数最多的5个区县;省级和区县级的泡泡人数排名第5之后的人数总和映射为“其他”泡泡。

3、代码分析及讲解(讲解请看注释,注释会逐步丰富完善)。

<!DOCTYPE html>
<meta charset="utf-8">
<style>
    #bubbles{
        width: 100%;
        height: 920px;
    }
</style>
<body>
<script src="https://cdn.bootcss.com/d3/3.5.6/d3.min.js"></script>
<script src="https://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<div id="bubbles">
</div>
<script>
    //模拟的数据
    var monidata = {
        //这个name值可以随便写
        "name": "root",
        "children": [
            {
                "name": "江苏省",
                "count": 2367,
                "maxchild": 1367,
                "child": [
                    {
                        "name": "南京市",
                        "count": 1367,
                    },
                    {
                        "name": "常州市",
                        "count": 367,
                    },
                    {
                        "name": "扬州市",
                        "count": 307,
                    },
                    {
                        "name": "镇江市",
                        "count": 167,
                    },
                    {
                        "name": "徐州市",
                        "count": 100,
                    },
                    {
                        "name": "其他",
                        "count": 59,
                    },
                ]
            },
            {
                "name": "安徽省",
                "count": 867,
                "maxchild": 467,
                "child": [
                    {
                        "name": "合肥市",
                        "count": 467,
                    },
                    {
                        "name": "芜湖市",
                        "count": 320,
                    },
                    {
                        "name": "池州市",
                        "count": 80,
                    }
                ]
            },
            {
                "name": "浙江省",
                "count": 667,
                "maxchild": 367,
                "child": [
                    {
                        "name": "杭州市",
                        "count": 367,
                    },
                    {
                        "name": "湖州市",
                        "count": 220,
                    },
                    {
                        "name": "宁波市",
                        "count": 80,
                    }
                ]
            },
            {
                "name": "山东省",
                "count": 267,
                "maxchild": 120,
                "child": [
                    {
                        "name": "济南市",
                        "count": 220,
                    },
                    {
                        "name": "青岛市",
                        "count": 20,
                    },
                    {
                        "name": "日照市",
                        "count": 47,
                    }
                ]
            },
            {
                "name": "上海市",
                "count": 167,
                "maxchild": 0,
                "child": []
            },
            {
                "name": "其他省份",
                "count": 597,
                "maxchild": 0,
                "child": []
            },
        ]
    };

    /*
    * 绘制泡泡
    * id,绘制泡泡的dom的id
    * width,绘制泡泡的dom的宽度
    * height,绘制泡泡的dom的高度
    * bubblesData,输入的数据,格式同模拟数据一样
    * 注意,d3的版本是3.5.6,使用v4或者v5的版本的话不能正常显示
    * */
    function drawBubbles(width,height,bubblesData,id) {

        //泡泡的颜色数据
        var colors = [
            {
                pfill: 'rgba(255, 44, 0,0.04)',
                pstroke: 'rgba(255, 44, 0,0.4)',
                fill: 'rgba(255, 44, 0,0.1)',
                stroke: 'rgba(255, 44, 0,1)',
            },
            {
                pfill: 'rgba(23, 0, 255,0.04)',
                pstroke: 'rgba(23, 0, 255,0.4)',
                fill: 'rgba(23, 0, 255,0.1)',
                stroke: 'rgba(23, 0, 255,1)',
            },
            {
                pfill: 'rgba(255, 188, 3,0.04)',
                pstroke: 'rgba(255, 188, 3,0.4)',
                fill: 'rgba(255, 188, 3,0.1)',
                stroke: 'rgba(255, 188, 3,1)',
            },
            {
                pfill: 'rgba(226, 119, 175,0.04)',
                pstroke: 'rgba(226, 119, 175,0.4)',
                fill: 'rgba(226, 119, 175,0.1)',
                stroke: 'rgba(226, 119, 175,1)',
            },
            {
                pfill: 'rgba(35, 15, 192,0.04)',
                pstroke: 'rgba(35, 15, 192,0.4)',
                fill: 'rgba(35, 15, 192,0.1)',
                stroke: 'rgba(35, 15, 192,1)',
            },
            {
                pfill: 'rgba(187, 238, 36,0.04)',
                pstroke: 'rgba(187, 238, 36,0.4)',
                fill: 'rgba(187, 238, 36,0.1)',
                stroke: 'rgba(187, 238, 36,1)',
            }
        ];

        //生成随机数,运动泡泡的似一次的位置
        var random = function(min, max) {
            if (max == null) {
                max = min;
                min = 0;
            }
            return min + Math.floor(Math.random() * (max - min + 1));
        };


        //全局的r标尺
        var rmin = 10,
            rmax = 50;
        //全局的子泡泡的标尺,使用指数标尺
        var rScale = d3.scale.pow().exponent(0.5)
            .domain([0, monidata.children[0].maxchild])
            .rangeRound([rmin, rmax]);

        //因为参赛者来源的省份人数差异实在太大了,可视化出来不美观,所以对参赛者来源的省份人数进行映射
        var countArr = [];
        for(var i = 0;i<bubblesData.children.length;i++){
            countArr.push(bubblesData.children[i].count);
        }
        //所以对参赛者来源的省份人数进行映射
        var countScale = d3.scale.pow().exponent(0.5)
            .domain(d3.extent(countArr))
            .rangeRound([d3.extent(countArr)[1]/10, d3.extent(countArr)[1]]);


        //绘制泡泡的画布尺寸
        var width = width,
            height = height;

        var pack = d3.layout.pack()
            .sort(null)
            .size([width, height])
            .padding(2);

        var svg = d3.select("#"+id).append("svg")
            .attr("width", width)
            .attr("height", height)
            .append("g")

        var node = svg.selectAll(".node")
            .data(pack.nodes(flatten(bubblesData))
                .filter(function(d) {
                    return !d.children;
                }))
            .enter().append("g")
            .attr("class", "node")
            .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

        node.append("circle")
            .attr("r", function(d) {
                return d.r*0.8;
            })
            .style("fill", function (d,i) {
                return colors[i].pfill;
            })
            .style("stroke", function (d,i) {
                //绘制其对应的子泡泡
                drawChildBubble(d,i)
                return colors[i].pstroke;
            })
            .attr("stroke-width","2")
            .style("stroke-dasharray", '10, 10');

        node.append("text")
            .text(function(d) { return d.name; })
            .style("font-size", "12px")
            .style("font-weight", "bold")
            .style("text-anchor", "middle")
            .attr("dy", ".35em");


        // 返回一个展平的层次结构,其中包含根目录下的所有叶节点。
        function flatten(root) {
            var nodes = [];

            function recurse(node) {
                if (node.children) node.children.forEach(recurse);
                else nodes.push({name: node.name, value: countScale(node.count),child:node.child});
            }

            recurse(root);
            return {children: nodes};
        }


        //绘制每个大泡泡里边的子泡泡
        function drawChildBubble(d,index) {
            //如果子泡泡只有1个,则不显示
            if(d.child.length == 1){
                return;
            }
            // 初始化
            var containerWidth;
            var containerHeight;
            var container = d3.select('#'+id).select('svg');
            var svgContainer = container
                .append('g')
                .attr('width', containerWidth)
                .attr('height', containerHeight)

            if(d.r < 70){
                containerWidth = 2*d.r+160;
                containerHeight = 2*d.r+160;
                svgContainer
                    .attr("transform", "translate(" + (d.x-d.r-80) + "," + (d.y-d.r-80) + ")")
            }else if(d.r >= 70 && d.r < 120){
                containerWidth = 2*d.r+140;
                containerHeight = 2*d.r+140;
                svgContainer
                    .attr("transform", "translate(" + (d.x-d.r-70) + "," + (d.y-d.r-70) + ")")
            }else{
                containerWidth = 2*d.r+120;
                containerHeight = 2*d.r+120;
                svgContainer
                    .attr("transform", "translate(" + (d.x-d.r-60) + "," + (d.y-d.r-60) + ")")
            }

            var data = [];

            var topWordCount = d.child;
            if(topWordCount.length == 0){
                return;
            }
            //初始化泡泡的位置、半径、颜色、速度
            for(var i = 0;i < topWordCount.length;i ++){
                var count = topWordCount[i].count;
                data.push({
                    text: topWordCount[i].name,
                    category: count,
                    x: random(containerWidth/2 - rmax, containerWidth/2 + rmax),
                    y: random(containerHeight/2 - rmax, containerHeight/2 + rmax),
                    r: rScale(count),
                    fill: colors[index].fill,
                    stroke: colors[index].stroke,
                    get v() {
                        var d = this;
                        return {x: d.x - d.px || 0, y: d.y - d.py || 0}
                    },
                    set v(v) {
                        var d = this;
                        d.px = d.x - v.x;
                        d.py = d.y - v.y;
                    },
                    get s() {
                        var v = this.v;
                        return Math.sqrt(v.x * v.x + v.y * v.y)
                    },
                    set s(s1){
                        var s0 = this.s, v0 = this.v;
                        if(!v0 || s0 == 0) {
                            var theta = Math.random() * Math.PI * 2;
                            this.v = {x: Math.cos(theta) * s1, y: Math.sin(theta) * s1}
                        } else this.v = {x: v0.x * s1/s0, y: v0.y * s1/s0};
                    },
                    set sx(s) {
                        this.v = {x: s, y: this.v.y}
                    },
                    set sy(s) {
                        this.v = {y: s, x: this.v.x}
                    },
                });
            }

            // 碰撞检测
            function collide(alpha, s0) {
                var quadtree = d3.geom.quadtree(data);
                return function(d) {
                    var drt = d.rt;
                    boundaries(d, drt);
                    var r = drt + rmax,
                        nx1 = d.x - r,
                        nx2 = d.x + r,
                        ny1 = d.y - r,
                        ny2 = d.y + r;
                    quadtree.visit(function(quad, x1, y1, x2, y2) {
                        if (quad.point && (quad.point !== d)) {
                            var x = d.x - quad.point.x,
                                y = d.y - quad.point.y,
                                l = Math.sqrt(x * x + y * y),
                                r = drt + quad.point.rt;
                            if (l < r) {
                                l = (l - r) / l * (1 + alpha);
                                d.x -= x *= l;
                                d.y -= y *= l;
                                quad.point.x += x;
                                quad.point.y += y;
                            }
                        }
                        return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
                    });
                };

                function boundaries(d, _drt) {
                    var moreThanX,moreThanY, v0;

                    //计算是否超出边界
                    function exceed(origin,target,r) {
                        var distance = Math.sqrt(Math.pow((origin[0]-target[0]),2)+Math.pow((origin[1]-target[1]),2));
                        return distance > r?true:false;
                    }
                    //如果超出边界,计算应该放置的位置
                    function place(origin,target,r) {
                        var newPos = [];
                        var angle = Math.atan2((target[1]-origin[1]), (target[0]-origin[0]));
                        newPos[0] = origin[0] + r*Math.cos(angle);
                        newPos[1] = origin[1] + r*Math.sin(angle);
                        return newPos;
                    }

                    var origin = [containerWidth/2,containerHeight/2];
                    var target = [d.x,d.y];
                    var r = containerWidth/2-100;

                    moreThanX = target[0] > origin[0];
                    moreThanY = target[1] > origin[1];

                    //圆形边界
                    if(exceed(origin,target,r)){
                        d.escaped |= 1;
                        // 如果物体在边界之外
                        // 管理其x速度分量的符号,以确保其移回边界
                        //~~它代表双非按位取反运算符,如果你想使用比Math.floor()更快的方法,那就是它了。需要注意,对于正数,它向下取整;对于负数,向上取整;非数字取值为0;
                        if(~~d.v.x) d.sx = d.v.x /** (moreThanX ? -1 : 1)*/;
                        //如果vx太小,则将其引导回
                        else d.sx = (~~Math.abs(d.v.y) || Math.min(s0, 1)*2) * (moreThanX ? -1 : 1);

                        d.x = place(origin,target,r)[0];

                        if(~~d.v.y) d.sy = d.v.y /** (moreThanY ? -1 : 1)*/;
                        else d.sy = (~~Math.abs(d.v.x) || Math.min(s0, 1)*2) * (moreThanY ? -1 : 1);
                        d.y = place(origin,target,r)[1];
                        //清除边界而不影响速度
                        v0 = d.v;

                        //给予速度值
                        d.v = v0;
                    }else {
                        d.escaped &= ~1;
                    }
                }
            }
            //准备布局
            var force = d3.layout
                .force()
                .size([containerWidth, containerHeight])
                .gravity(0.0001)
                .charge(-20)
                .friction(.8)

            //加载数据
            force.nodes(data)
                .start();

            // 创建group
            var node = svgContainer.selectAll('.node')
                .data(data)
                .enter()
                .append('g')
                .attr('class', 'node')
                .call(force.drag);

            // 创建圆
            var circles = node.append('circle')
                .classed('circle', true)
                .attr('r', function (d) {
                    return d.r;
                })
                .style('fill', function (d) {
                    return d.fill;
                })
                .style('stroke', function (d) {
                    return d.stroke;
                })
                .style('cursor','pointer')
                .attr("stroke-width","2")
                .each(function(d){
                    // 添加动态获取器
                    var n= d3.select(this);
                    Object.defineProperty(d, "rt", {get: function(){
                            return +n.attr("r")
                        }})
                });

            // 创建标签
            node.append('text')
                .text(function(d) {
                    return d.text
                })
                .style({
                    'fill': '#000000',
                    'text-anchor': 'middle',
                    'font-size': '12px',
                    'font-weight': 'bold',
                    'text-transform': 'uppercase',
                    'font-family': 'Tahoma, Arial, sans-serif',
                })
                .attr('x', function (d) {
                    return 0;
                })
                .attr('y', function (d) {
                    return 6;
                })

            // 使圆运动
            force.on('tick', function t(e){
                var s0 = 0.25, k = 0.3;

                // var a = e.alpha ? e.alpha : force.alpha();
                var a = 0.002;

                for ( var i = 0; i < 2; i++) {
                    circles
                        .each(collide(a, s0));
                }

                //调节圆的运动速度
                data.forEach(function reg(d){
                    if(!d.escaped) d.s =  (s0 - d.s * k) / (1 - k);
                });

                node.attr("transform", function position(d){return "translate(" + [d.x, d.y] + ")"});

                force.alpha(0.01);
            });
        }
    }

    drawBubbles(920,920,monidata,"bubbles");
</script>

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值