美文网首页转载的~一些辅助工具库D3
D3学习系列(一) 基础知识与柱形图绘制

D3学习系列(一) 基础知识与柱形图绘制

作者: 卷福不卷 | 来源:发表于2017-03-26 13:39 被阅读546次

    「前言」

    最开始的初衷是想画个弦图(chord)与桑基图(sankey),真的很炫有没有!然而D3零基础的我表示源码看不懂,受到1万点暴击(+﹏+)~ 于是果断去恶补D3的基础知识,并加以整理同时加深对自己的印象。

    「基础概念」

    选择集
    使用 d3.select() 或 d3.selectAll() 选择元素后返回的对象,就是选择集

    无名函数
    function(d, i) 这个函数以后经常要使用到

    • d 代表数据,也就是与某元素绑定的数据。
    • i 代表索引,代表数据的索引号,从 0 开始。

    「数据绑定」

    D3可以用两种函数来绑定数据:

    • datum(): 绑定一个数据到选择集上,这里的数据并非一定要是number(数值型),也可以是string(字符串)、bollean(布尔型)和object(对象)
    • data(): 绑定一个数组到选择集上,数组的各项值分别与选择集的各元素绑定,更常用

    data() 函数的常用语法

    var dataset = [10,20,30,40,50];
    var body = d3.select("body");
    
    body.selectAll("p")  //选择body中的所有p,但是目前还没有,所以是空集
        .data(dataset)  //绑定数组
        .enter()  //指定选择集的enter部分
        .append("p")
        .text(function(d){ 
            return d; 
        })
    

    这里要解释下 Enter 的概念,它与Update、Exit是D3中三个非常重要的概念,处理的是当选择集和数据的数量关系不确定的情况。

    如果数组为 [3, 6, 9, 12, 15],将此数组绑定到三个 p 元素的选择集上。可以想象,会有两个数据没有元素与之对应,这时候 D3 会建立两个空的元素与数据对应,这一部分就称为 Enter。而有元素与数据对应的部分称为 Update。如果数组为 [3],则会有两个元素没有数据绑定,那么没有数据绑定的部分被称为 Exit。示意图如下所示。


    「柱形图」

    Bar Chart一般包括:矩形、坐标轴与文字。

    矩形

    这里我们直接定义一个数组,用数组项对应矩形的长短(然而这种方法并不理想)。

    var dataset = [50, 43, 120, 87, 99, 167, 142];
    

    定义一块SVG的绘制区域:

    var width = 600;    // SVG的宽度
    var height = 600;   // SVG的长度
    
    var svg = d3.select("body")
                .append('svg')  // body中添加SVG
                .attr('width', width)
                .attr('height', height);
    

    定义三个我们要用的变量

    var padding = {top: 20, right: 20, bottom: 20, left: 20};
    var rectStep = 35;
    var rectWidth = 30;
    

    padding是svg内的最外一层区域,留一段空白宽度是为了防止图形绘制带svg区域外。rectStep表示前一个矩形到下一个矩形的距离(包括空白间隔),而rectWidth是矩形实际的宽度。说了这么多还是看图更易懂:


    添加矩形元素

    var rect = svg.selectAll("rect")
                  .data(dataset)
                  .enter()  //获取enter部分
                  .append("rect")   //添加rect元素,使其与绑定数组的长度一致
                  .attr("fill","steelblue")
                  .attr("x",function(d,i){  //设置X坐标
                      return padding.left + i * rectStep;
                  })
                  .attr("y",function(d,i){  //设置Y坐标
                      return height - padding.bottom - d;
                  })
                  .attr("width",rectWidth)  //设置矩形宽度,之前定义的
                  .attr("height",function(d){   //设置矩形高度,即为数组中的各项值
                      return d
                  });
    

    因为数组dataset的长度为7,所以最后生成7个矩形。x与y坐标是矩形的左上角顶点。 这个坐标是相对应svg绘图区域来讲的,坐标原点位于左上角(0,0)。

    一张图直接说明:


    标签文字

    var text = svg.selectAll(text)
                    .data(dataset)
                    .enter()
                    .append("text")
                    .attr("fill","white")
                    .attr("font-size","14px")
                    .attr("text-anchor","middle")
                    .attr("x",function(d,i){    //与矩形的X坐标一样
                        return padding.left + i * rectStep;
                    })
                    .attr("y",function(d){
                        return height - padding.bottom - d;
                    })
                    .attr('dx', rectWidth/2)    //x轴相对平移距离
                    .attr('dy', "1em")  //em单位表示的是当前文字所占一行的高度
                    .text(function(d){  //要显示的文字内容
                        return d;   
                    });
    

    添加文字标签的方法与添加矩形元素方法相类似,不过颜色要与矩形的颜色区分。通过设置元素的text-anchor、x、y、dx与dy五个属性,让文字显示在每个矩形的正中心

    其中dx,dy表示相对(x,y)平移的大小,所以文本会从(x+dx,y+dy)位置开始显示,这个位置也叫<u>起始位置</u>。属性text-anchor有三个值:start、middle、end,,这里用middle表示文字中心位于<u>起始位置</u>上。

    还是上图说明问题:


    效果图:


    坐标轴

    坐标轴的主直线由path构成,刻度由line绘制,刻度文字用text完成。之前我们直接用数值的大小来表示像素的大小,这里我们使用比例尺,定义如下:

    // SVG画布
    var width = 600;
    var height = 600;
    var svg = d3.select("body").append('svg')
                .attr('width', width)
                .attr('height', height);
    
    // 坐标轴的线性比例尺
    var xScale = d3.scale.linear()
                    .domain([0,10]) //定义域
                    .range([0,300]);    //值域
    
    // 定义坐标轴
    var axis = d3.svg.axis()
                .scale(xScale)
                .orient("bottom")
                .ticks(5);  //刻度的数量,这里显示5个
    
    //在 SVG 中添加一个分组元素,再将坐标轴的其他元素添加到里面
    var gAxis = svg.append("g")
                    .attr("transform","translate(80,80)");
                    .call(axis)
    
    gAxis.attr('class', 'axis');    //添加一些样式,否则太太太丑了...
    

    call()函数的使用十分常见,这里使用的参数是前面定义的坐标轴axis,等价于axis(gAxis);的形式。效果图如下:

    「柱形图的坐标轴」

    对初学者而言,这里的坑更多(老司机请无视)。主要是因为使用了比例尺之后,XY坐标轴、矩形长宽、刻度都要与之相对应。不要问我为什么知道这么多,都是泪......

    为矩形图定义比例尺

    var xAxisWidth = 300;   //x轴宽度
    var yAxisWidth = 300;   //y轴宽度
    
    var xScale = d3.scale.ordinal() //x轴比例尺(序数比例尺)
                    .domain(d3.range(dataset.length))
                    .rangeRoundBands([0,xAxisWidth],0.2);
    var yScale = d3.scale.linear()  //y轴比例尺(线性比例尺)
                    .domain([0,d3.max(dataset)])
                    .range([0,yAxisWidth]); 
    

    定义完比例尺之后,矩形的高度、位置都要用比例尺来计算。如此之后,仅需简单修改比例尺,图表就能自动伸缩,所以前面的<u>矩形元素</u><u>矩形文字</u>的代码都需要修改

    矩形元素修改部分

    .attr("x",function(d,i){    
        return padding.left + xScale(i);    // return padding.left + i * rectStep;
    })
    .attr("y",function(d,i){    
        return height - padding.bottom - yScale(d); // return height - padding.bottom - d;
    })
    .attr("width",xScale.rangeBand()) 
    .attr("height",function(d){
        return yScale(d);
    })
    

    标签文字修改部分

    .attr("x",function(d,i){    //与矩形的X坐标一样
        return padding.left + xScale(i);
    })
    .attr("y",function(d){
        return height - padding.bottom - yScale(d);
    })
    .attr('dx', xScale.rangeBand()/2)   //x轴相对平移距离
    .attr('dy', "1em")  //em单位表示的是当前文字所占一行的高度
    .text(function(d){  //要显示的文字内容
        return d;
    });
    

    定义坐标轴

    var xAxis = d3.svg.axis()
                .scale(xScale)
                .orient("bottom");
    
    yScale.range([yAxisWidth,0]);   //值域相反
    
    var yAxis = d3.svg.axis()
                .scale(yScale)
                .orient("left");
    

    此外还要注意,y轴坐标的值域要与原来相反,从最大值到最小值,否则最后会出现下面这种情况:
    <img src="http:https://img.haomeiwen.com/i4762054/04b43eb28412b558.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" style="width: 35px;"/>

    添加坐标轴元素

    //添加x轴
    svg.append("g") 
        .attr("class","axis")
        .attr("transform","translate(" + padding.left + "," + (height - padding.bottom) + ")")
        .call(xAxis);
    
    //添加y轴
    svg.append("g")
        .attr("class","axis")
        .attr("transform","translate(" + padding.left + "," + (height - padding.bottom - yAxisWidth) + ")")
        .call(yAxis);
    

    这里要小心x轴、y轴平移到目标位置的距离,以及你设置padding前后左右的宽度,防止坐标轴跑到外面去(又是血与泪的教训)。

    最后效果图:

    完整源代码

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8">
            <title></title>
            <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
            <style>
                .axis path,
                .axis line{
                    fill: none;
                    stroke: black;
                    shape-rendering: crispEdges;
                }
                .axis text{
                    font-family: sans-serif;
                    font-size: 11px;
                }
            </style>
        </head>
        <body>
            <script >
                // 添加SVG画布
                var dataset = [50, 43, 120, 87, 99, 167, 142];
                var width = 600;    // SVG的宽度
                var height = 600;   // SVG的长度
                var svg = d3.select("body")
                            .append('svg')  // body中添加SVG
                            .attr('width', width)
                            .attr('height', height);
                var padding = {top: 20, right: 20, bottom: 20, left: 30};
    
                // 定义数据与比例尺
                var xAxisWidth = 300;   //x轴宽度
                var yAxisWidth = 300;   //y轴宽度
                var xScale = d3.scale.ordinal() //x轴比例尺(序数比例尺)
                                .domain(d3.range(dataset.length))
                                .rangeRoundBands([0,xAxisWidth],0.2);
                var yScale = d3.scale.linear()  //y轴比例尺(线性比例尺)
                                .domain([0,d3.max(dataset)])
                                .range([0,yAxisWidth]);
    
                // 添加矩形和文字元素
                var rect = svg.selectAll("rect")
                                .data(dataset)
                                .enter()  //获取enter部分
                                .append("rect") //添加rect元素,使其与绑定数组的长度一致
                                .attr("fill","steelblue")
                                .attr("x",function(d,i){    //设置X坐标
                                    // return padding.left + i * rectStep;
                                    return padding.left + xScale(i);
    
                                })
                                .attr("y",function(d,i){    //设置Y坐标
                                    // return height - padding.bottom - d;
                                    return height - padding.bottom - yScale(d);
                                })
                                .attr("width",xScale.rangeBand())    //设置矩形宽度
                                .attr("height",function(d){
                                    return yScale(d);
                                })
                var text = svg.selectAll(text)
                                .data(dataset)
                                .enter()
                                .append("text")
                                .attr("fill","white")
                                .attr("font-size","14px")
                                .attr("text-anchor","middle")
                                .attr("x",function(d,i){    //与矩形的X坐标一样
                                    return padding.left + xScale(i);
                                })
                                .attr("y",function(d){
                                    return height - padding.bottom - yScale(d);
                                })
                                .attr('dx', xScale.rangeBand()/2)   //x轴相对平移距离
                                .attr('dy', "1em")  //em单位表示的是当前文字所占一行的高度
                                .text(function(d){  //要显示的文字内容
                                    return d;
                                });
    
                // 定义坐标轴
                var xAxis = d3.svg.axis()
                            .scale(xScale)
                            .orient("bottom");
                yScale.range([yAxisWidth,0]);
                var yAxis = d3.svg.axis()
                            .scale(yScale)
                            .orient("left");
                            
                // 添加坐标轴
                svg.append("g")
                  .attr("class","axis")
                  .attr("transform","translate(" + padding.left + "," + (height - padding.bottom) + ")")
                  .call(xAxis);
    
                svg.append("g")
                  .attr("class","axis")
                  .attr("transform","translate(" + padding.left + "," + (height - padding.bottom - yAxisWidth) + ")")
                  .call(yAxis);
            </script>
        </body>
    </html>
    

    「参考资料」

    Learning D3.JS
    D3.js:Update、Enter、Exit
    D3.js - 初体验
    D3.js数据可视化系列教程

    相关文章

      网友评论

        本文标题:D3学习系列(一) 基础知识与柱形图绘制

        本文链接:https://www.haomeiwen.com/subject/ydotottx.html