D3學習系列(一) 基礎(chǔ)知識與柱形圖繪制

「前言」

最開始的初衷是想畫個弦圖(chord)與桑基圖(sankey),真的很炫有沒有!然而D3零基礎(chǔ)的我表示源碼看不懂,受到1萬點暴擊(+﹏+)~ 于是果斷去惡補D3的基礎(chǔ)知識,并加以整理同時加深對自己的印象。

「基礎(chǔ)概念」

選擇集
使用 d3.select() 或 d3.selectAll() 選擇元素后返回的對象,就是選擇集

無名函數(shù)
function(d, i) 這個函數(shù)以后經(jīng)常要使用到

  • d 代表數(shù)據(jù),也就是與某元素綁定的數(shù)據(jù)。
  • i 代表索引,代表數(shù)據(jù)的索引號,從 0 開始。

「數(shù)據(jù)綁定」

D3可以用兩種函數(shù)來綁定數(shù)據(jù):

  • datum(): 綁定一個數(shù)據(jù)到選擇集上,這里的數(shù)據(jù)并非一定要是number(數(shù)值型),也可以是string(字符串)、bollean(布爾型)和object(對象)
  • data(): 綁定一個數(shù)組到選擇集上,數(shù)組的各項值分別與選擇集的各元素綁定,更常用

data() 函數(shù)的常用語法

var dataset = [10,20,30,40,50];
var body = d3.select("body");

body.selectAll("p")  //選擇body中的所有p,但是目前還沒有,所以是空集
    .data(dataset)  //綁定數(shù)組
    .enter()  //指定選擇集的enter部分
    .append("p")
    .text(function(d){ 
        return d; 
    })

這里要解釋下 Enter 的概念,它與Update、Exit是D3中三個非常重要的概念,處理的是當選擇集和數(shù)據(jù)的數(shù)量關(guān)系不確定的情況。

如果數(shù)組為 [3, 6, 9, 12, 15],將此數(shù)組綁定到三個 p 元素的選擇集上??梢韵胂螅瑫袃蓚€數(shù)據(jù)沒有元素與之對應(yīng),這時候 D3 會建立兩個空的元素與數(shù)據(jù)對應(yīng),這一部分就稱為 Enter。而有元素與數(shù)據(jù)對應(yīng)的部分稱為 Update。如果數(shù)組為 [3],則會有兩個元素沒有數(shù)據(jù)綁定,那么沒有數(shù)據(jù)綁定的部分被稱為 Exit。示意圖如下所示。


「柱形圖」

Bar Chart一般包括:矩形、坐標軸與文字。

矩形

這里我們直接定義一個數(shù)組,用數(shù)組項對應(yīng)矩形的長短(然而這種方法并不理想)。

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

定義一塊SVG的繪制區(qū)域:

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內(nèi)的最外一層區(qū)域,留一段空白寬度是為了防止圖形繪制帶svg區(qū)域外。rectStep表示前一個矩形到下一個矩形的距離(包括空白間隔),而rectWidth是矩形實際的寬度。說了這么多還是看圖更易懂:


添加矩形元素

var rect = svg.selectAll("rect")
              .data(dataset)
              .enter()  //獲取enter部分
              .append("rect")   //添加rect元素,使其與綁定數(shù)組的長度一致
              .attr("fill","steelblue")
              .attr("x",function(d,i){  //設(shè)置X坐標
                  return padding.left + i * rectStep;
              })
              .attr("y",function(d,i){  //設(shè)置Y坐標
                  return height - padding.bottom - d;
              })
              .attr("width",rectWidth)  //設(shè)置矩形寬度,之前定義的
              .attr("height",function(d){   //設(shè)置矩形高度,即為數(shù)組中的各項值
                  return d
              });

因為數(shù)組dataset的長度為7,所以最后生成7個矩形。x與y坐標是矩形的左上角頂點。 這個坐標是相對應(yīng)svg繪圖區(qū)域來講的,坐標原點位于左上角(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){  //要顯示的文字內(nèi)容
                    return d;   
                });

添加文字標簽的方法與添加矩形元素方法相類似,不過顏色要與矩形的顏色區(qū)分。通過設(shè)置元素的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構(gòu)成,刻度由line繪制,刻度文字用text完成。之前我們直接用數(shù)值的大小來表示像素的大小,這里我們使用比例尺,定義如下:

// 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);  //刻度的數(shù)量,這里顯示5個

//在 SVG 中添加一個分組元素,再將坐標軸的其他元素添加到里面
var gAxis = svg.append("g")
                .attr("transform","translate(80,80)");
                .call(axis)

gAxis.attr('class', 'axis');    //添加一些樣式,否則太太太丑了...

call()函數(shù)的使用十分常見,這里使用的參數(shù)是前面定義的坐標軸axis,等價于axis(gAxis);的形式。效果圖如下:

「柱形圖的坐標軸」

對初學者而言,這里的坑更多(老司機請無視)。主要是因為使用了比例尺之后,XY坐標軸、矩形長寬、刻度都要與之相對應(yīng)。不要問我為什么知道這么多,都是淚......

為矩形圖定義比例尺

var xAxisWidth = 300;   //x軸寬度
var yAxisWidth = 300;   //y軸寬度

var xScale = d3.scale.ordinal() //x軸比例尺(序數(shù)比例尺)
                .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){  //要顯示的文字內(nèi)容
    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軸坐標的值域要與原來相反,從最大值到最小值,否則最后會出現(xiàn)下面這種情況:
<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軸平移到目標位置的距離,以及你設(shè)置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};

            // 定義數(shù)據(jù)與比例尺
            var xAxisWidth = 300;   //x軸寬度
            var yAxisWidth = 300;   //y軸寬度
            var xScale = d3.scale.ordinal() //x軸比例尺(序數(shù)比例尺)
                            .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元素,使其與綁定數(shù)組的長度一致
                            .attr("fill","steelblue")
                            .attr("x",function(d,i){    //設(shè)置X坐標
                                // return padding.left + i * rectStep;
                                return padding.left + xScale(i);

                            })
                            .attr("y",function(d,i){    //設(shè)置Y坐標
                                // return height - padding.bottom - d;
                                return height - padding.bottom - yScale(d);
                            })
                            .attr("width",xScale.rangeBand())    //設(shè)置矩形寬度
                            .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){  //要顯示的文字內(nèi)容
                                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數(shù)據(jù)可視化系列教程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 這里講一下怎么樣用d3.js,輸入一個數(shù)據(jù)list,根據(jù)數(shù)據(jù)畫一個帶有坐標軸的簡單直方圖.以下是目標效果. 直方圖...
    Kaidi_G閱讀 4,965評論 1 3
  • 本教程是一個簡單的入門教程,能夠幫助初學者快速掌握D3的基礎(chǔ)知識。 本節(jié)內(nèi)容介紹了添加元素、綁定數(shù)據(jù)、使用數(shù)據(jù)、矢...
    笨笨的笨小孩閱讀 2,167評論 0 1
  • D3是用于數(shù)據(jù)可視化的Javascript庫。使用SVG,Canvas和HTML。結(jié)合強大的可視化技術(shù)和數(shù)據(jù)驅(qū)動的...
    Evelynzzz閱讀 7,965評論 7 5
  • 1 前言 一直想沿著圖像處理這條線建立一套完整的理論知識體系,同時積累實際應(yīng)用經(jīng)驗。因此有了從使用AVFounda...
    RichardJieChen閱讀 5,928評論 5 12
  • 《小酌怡情》 (一)今天戰(zhàn)友小聚,晚上小酌了幾杯,喝的是戰(zhàn)友帶回來的俄羅斯啤酒,每人兩瓶剛剛好。我以前是個很討厭喝...
    沐乘風閱讀 1,294評論 0 0

友情鏈接更多精彩內(nèi)容