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

「前言」

最开始的初衷是想画个弦图(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://upload-images.jianshu.io/upload_images/4762054-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数据可视化系列教程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容

  • 这里讲一下怎么样用d3.js,输入一个数据list,根据数据画一个带有坐标轴的简单直方图.以下是目标效果. 直方图...
    Kaidi_G阅读 4,810评论 1 3
  • 本教程是一个简单的入门教程,能够帮助初学者快速掌握D3的基础知识。 本节内容介绍了添加元素、绑定数据、使用数据、矢...
    笨笨的笨小孩阅读 2,015评论 0 1
  • D3是用于数据可视化的Javascript库。使用SVG,Canvas和HTML。结合强大的可视化技术和数据驱动的...
    Evelynzzz阅读 7,885评论 7 5
  • 1 前言 一直想沿着图像处理这条线建立一套完整的理论知识体系,同时积累实际应用经验。因此有了从使用AVFounda...
    RichardJieChen阅读 5,649评论 5 12
  • 《小酌怡情》 (一)今天战友小聚,晚上小酌了几杯,喝的是战友带回来的俄罗斯啤酒,每人两瓶刚刚好。我以前是个很讨厌喝...
    沐乘风阅读 1,199评论 0 0