2017/1/9

D3.js 基本的使用方式 part 1


D3.js 在 2016/7/28 釋出 v4.0.0 版,現在已經更新到 v4.4.1,大部分的書本還是以 v3 為主,因此我們嘗試測試將書本的範例調整為 v4 版本。


D3.js 可以生成及處理資料,處理過程經歷以下的步驟:


  1. 把資料載入到瀏覽器的 memory
  2. 把資料綁定到 DOM 的元素,根據需要建立新元素
  3. 解析每個元素的範圍資料 (bound datum),並為其設置相應的可視化屬性,實現元素的轉換 (transforming)
  4. 套用使用者輸入的,實現元素狀態的動態過渡 (transitioning)

D3 不適合產生探索型的視覺圖形,擅長產生解釋型的視覺圖形,探索型的視圖工具可以根據相同的資料,產生多個視圖。


D3 擅長處理 SVG 及 GeoJSON,不處理類似 google map 的地圖貼片。


所有的數據資料都必須傳送到客戶端的瀏覽器,如果數據資料有分享的疑慮,就不應該使用 D3.js。


以下的範例都是由 數據可視化實戰:使用D3設計交互式圖表 這本書取得的。


資料處理


  • 產生網頁 DOM 元素

d3  // 引用 D3 物件
.select("body")  // 取得 body 元素
.append("p")    // 產生 p
.text("New paragraph!")     // 放入文字到 p 元素中

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <script type="text/javascript" src="../d3/d3-4.2.2.min.js"></script>
    </head>
    <body>
        <script type="text/javascript">
            d3.select("body")
                .append("p")
                .text("New paragraph!");
        </script>
    </body>
</html>

大部分的 d3 method 會傳回正在操作的 DOM 元素,所以可以連續呼叫 method。


  • 載入 csv 資料

d3.csv 是非同步的 method,後面需要一個 callback function 處理接收的資料。如果前面多了一個 error 參數,則是在處理下載 csv 失敗時的狀況。


d3.csv("food.csv", function(data) {
        console.log(data);
    });
            
var dataset;
d3.csv("food.csv", function(error, data) {
    if (error) {
        console.log(error); // 輸出錯誤
    } else {
        console.log(data); // 輸出資料
        dataset = data;
    }
});

  • D3 的常見問題:如何使用還不存在的元素

var dataset = [ 5, 10, 15, 20, 25 ];

d3.select("body")
    .selectAll("p") // 取得 body 裡面所有的 <p>,如果還不存在,就建立一個新的 <p>
    .data(dataset)  // 根據 dataset 資料的 5 個元素,後面的城市,會執行 5 次
    .enter()    // 分析目前的<p> 及 dataset,如果資料比 DOM 元素多,就建立一個新的元素,傳給下面的 method
    .append("p")    // 將 enter 產生的空元素,加入一個 <p>
    .text("New paragraph!");    // 在 <p> 裡面加入 text

console.log(d3.selectAll("p")) 查詢所有的段落,並找到剛剛的 dataset



  • 調整 dataset 的方法

修改最後一行,可以知道如何在 callback function 中使用 dataset 裡面的元素。


d3.select("body").selectAll("p")
    .data(dataset)
    .enter()
    .append("p")
    .text(function(d) { return d; });

.style("color","red") 把段落文字變成紅色


d3.select("body").selectAll("p")
    .data(dataset)
    .enter()
    .append("p")
    .text(function(d) {
        return "I can count up to " + d;
    })
    .style("color", "red");

在 style 的處理中,也可以根據 dataset 的原始資料,進行條件判斷,產生不同的文字顏色


var dataset = [ 5, 10, 15, 20, 25 ];

d3.select("body").selectAll("p")
    .data(dataset)
    .enter()
    .append("p")
    .text(function(d) {
        return "I can count up to " + d;
    })
    .style("color", function(d) {
        if (d > 15) {
            return "red";
        } else {
            return "black";
        }
    });

根據資料繪製圖形


準備一個長條矩形的 css style,將 div 變成長條圖


        <style type="text/css">
        
            div.bar {
                display: inline-block;
                width: 20px;
                height: 75px;   /* 會被 d3 的 style 覆寫 */
                margin-right: 2px;
                background-color: teal;
            }
        
        </style>
        <script type="text/javascript">
        
            var dataset = [ 25, 7, 5, 26, 11 ];
            
            d3.select("body").selectAll("div")
                .data(dataset)
                .enter()
                .append("div")
                .attr("class", "bar")
                .style("height", function(d) {
                    var barHeight = d * 5;
                    return barHeight + "px";
                });
            
        </script>

以亂數的方式產生 dataset


        <script type="text/javascript">

            var dataset = [];                        //Initialize empty array
            for (var i = 0; i < 25; i++) {           //Loop 25 times
                //var newNumber = Math.random() * 30;  //New random number (0-30)
                var newNumber = Math.floor(Math.random() * 30);  //New random integer (0-29)
                dataset.push(newNumber);             //Add new number to array
            }

            d3.select("body").selectAll("div")
                .data(dataset)
                .enter()
                .append("div")
                .attr("class", "bar")
                .style("height", function(d) {
                    var barHeight = d * 5;
                    return barHeight + "px";
                });

        </script>

繪製 SVG 圖形


  • 類似剛剛的 dataset 產生長條圖的方法,用同樣的方式產生 svg

        <script type="text/javascript">
            //Width and height
            var w = 500;
            var h = 50;
            
            //Data
            var dataset = [ 5, 10, 15, 20, 25 ];
            
            // 產生 svg 區塊
            var svg = d3.select("body")
                        .append("svg")
                        .attr("width", w)
                        .attr("height", h);

            // 在 svg 中產生 circle
            var circles = svg.selectAll("circle")
                .data(dataset)
                .enter()
                .append("circle");

            // 為每個 circles 設定屬性
            circles.attr("cx", function(d, i) {
                        return (i * 50) + 25;
                    })
                   .attr("cy", h/2)
                   .attr("r", function(d) {
                        return d;
                   })
                   .attr("fill", "yellow")
                   .attr("stroke", "orange")
                   .attr("stroke-width", function(d) {
                        return d/2;
                   });
        </script>

  • 以 svg 的方式產生長條圖

<script type="text/javascript">

            //Width and height
            var w = 500;
            var h = 100;
            var barPadding = 1;
            
            var dataset = [ 5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
                            11, 12, 15, 20, 18, 17, 16, 18, 23, 25 ];
            
            //Create SVG element
            var svg = d3.select("body")
                        .append("svg")
                        .attr("width", w)
                        .attr("height", h);

            // 產生 rect 方塊
            svg.selectAll("rect")
               .data(dataset)
               .enter()
               .append("rect")
               // 方塊的 x 位置,以 svg 圖片的寬度,跟 dataset 數量計算
               .attr("x", function(d, i) {
                    return i * (w / dataset.length);
               })
               // 方塊的 y 位置,(x,y) 座標點的計算是由左上角開始的,因此 y 的位置要由 svg 高度 扣掉資料的數值來決定, 資料的 4 倍可以讓圖形的相對差距變大,讓資料的差異在圖形的表現上更大
               .attr("y", function(d) {
                    return h - (d * 4);
               })
               // 矩形的寬度由 svg 寬度跟 dataset 數量決定,badPadding 是不同矩形之間的空白
               .attr("width", w / dataset.length - barPadding)
               // 高度由 dataset 數值放大 4 倍決定
               .attr("height", function(d) {
                    return d * 4;
               })
               // 以 rgb 動態將舉行填滿不同的顏色
               .attr("fill", function(d) {
                    return "rgb(0, 0, " + (d * 10) + ")";
               });

            // 在 svg 中產生 text 文字區塊,變成長條圖上面的文字標籤 
            svg.selectAll("text")
               .data(dataset)
               .enter()
               .append("text")
               .text(function(d) {
                    return d;
               })
               // 讓文字對齊中間
               .attr("text-anchor", "middle")
               // 設定 text 的 x 座標位置
               .attr("x", function(d, i) {
                    return i * (w / dataset.length) + (w / dataset.length - barPadding) / 2;
               })
               // 設定 text 的 y 座標位置
               .attr("y", function(d) {
                    return h - (d * 4) + 14;
               })
               // 因為字看不清楚
               .attr("font-family", "sans-serif")
               .attr("font-size", "11px")
               .attr("fill", "white");
        </script>

attr 可以只設定一個屬性或是將多個屬性組合在一起


svg.select("circle")
    .attr("cx", 0)
    .attr("cy", 0)
    .attr("fill", "red");
    
svg.select("circle")
    .attr({
        cx: 0,
        cy: 0,
        fill: "red"
    });

在多個屬性中,同時指定 callback functions


svg.selectAll("rect")
    .data(dataset)
    .enter()
    .append("rect")
    .attr({
        x: function(d, i) { return i * (w / dataset.length); },
        y: function(d) { return h - (d * 4); },
        width: w / dataset.length - barPadding,
        height: function(d) { return d * 4; },
        fill: function(d) { return "rgb(0, 0, " + (d * 10) + ")";}
    });

繪製散點圖 Scatter Plot


二維的資料,一般就先繪製 scatter plot


        <script type="text/javascript">

            //Width and height
            var w = 500;
            var h = 100;
            
            var dataset = [
                            [5, 20], [480, 90], [250, 50], [100, 33], [330, 95],
                            [410, 12], [475, 44], [25, 67], [85, 21], [220, 88]
                          ];
    
            //Create SVG element
            var svg = d3.select("body")
                        .append("svg")
                        .attr("width", w)
                        .attr("height", h);

            // 產生 circle
            svg.selectAll("circle")
               .data(dataset)
               .enter()
               .append("circle")
               // 圓心的位置
               .attr("cx", function(d) {
                    return d[0];
               })
               .attr("cy", function(d) {
                    return d[1];
               })
               // 圓的半徑
               .attr("r", function(d) {
                    return Math.sqrt(h - d[1]);
               });

            // 加上標籤
            svg.selectAll("text")
               .data(dataset)
               .enter()
               .append("text")
               .text(function(d) {
                    return d[0] + "," + d[1];
               })
               // 標籤的位置放在圓心的地方
               .attr("x", function(d) {
                    return d[0];
               })
               .attr("y", function(d) {
                    return d[1];
               })
               // 設定標籤的文字 style
               .attr("font-family", "sans-serif")
               .attr("font-size", "11px")
               .attr("fill", "red");
            
        </script>

比例尺 scale


scale 是將輸入資料映射為另一組輸出範圍的函數。


一般來說,原始的資料數據不可能會剛好是圖表中的像素,就像是剛剛的例子一樣,長條圖的高度,是由原始資料計算出來的。這個資料轉換的過程,
就是 scale。


對於線性比例尺來說,概念就像是 normalization 一樣。D3 可以先將原始資料映射到 scale.domain([100, 500]) 的值域,也就是根據值域進行 normalization,然後以 scale.range([10, 350]) 將 normalized 之後的值,對應到 range 的輸出範圍。


在 D3 v3 版是 d3.scale.linear() ,在 v4 版是 d3.scaleLinear()


var scale = d3.scaleLinear().domain([100, 500]).range([10, 350]);

scale(100); // -> 10
scale(300); // -> 180
scale(500); // -> 350
// [100, 300, 500] -> [10, 180, 350]

<script type="text/javascript">

            //Width and height
            var w = 500;
            var h = 100;

            var dataset = [
                            [5, 20], [480, 90], [250, 50], [100, 33], [330, 95],
                            [410, 12], [475, 44], [25, 67], [85, 21], [220, 88]
                          ];

            //Create scale functions
            var xScale = d3.scaleLinear()
                            // 以 d3.max 取得 dataset d[0] 的 max, normalized 為 0 ~ max(d[0])
                             .domain([0, d3.max(dataset, function(d) { return d[0]; })])
                            // 映射到 0 ~ w
                             .range([0, w]);

            // 映射的範圍是相反的 (h,0)
            var yScale = d3.scaleLinear()
                                 .domain([0, d3.max(dataset, function(d) { return d[1]; })])
                                 .range([h, 0]);

            //Create SVG element
            var svg = d3.select("body")
                        .append("svg")
                        .attr("width", w)
                        .attr("height", h);

            svg.selectAll("circle")
               .data(dataset)
               .enter()
               .append("circle")
               .attr("cx", function(d) {
                    return xScale(d[0]);
               })
               .attr("cy", function(d) {
                    return yScale(d[1]);
               })
               .attr("r", function(d) {
                    return Math.sqrt(h - d[1]);
               });

            svg.selectAll("text")
               .data(dataset)
               .enter()
               .append("text")
               .text(function(d) {
                    return d[0] + "," + d[1];
               })
               .attr("x", function(d) {
                    return xScale(d[0]);
               })
               .attr("y", function(d) {
                    return yScale(d[1]);
               })
               .attr("font-family", "sans-serif")
               .attr("font-size", "11px")
               .attr("fill", "red");
        </script>

因為接近外框的圓形被切掉了一部分,加上 padding=20,讓 svg 保留一部分外框。


            //Create scale functions
            var xScale = d3.scaleLinear()
                                 .domain([0, d3.max(dataset, function(d) { return d[0]; })])
                                 .range([padding, w - padding * 2]);

            var yScale = d3.scaleLinear()
                                 .domain([0, d3.max(dataset, function(d) { return d[1]; })])
                                 .range([h - padding, padding]);

將圓形的半徑也使用 scale 進行映射到 [2,5]


            var rScale = d3.scaleLinear()
                                 .domain([0, d3.max(dataset, function(d) { return d[1]; })])
                                 .range([2, 5]);

所有的數值都是動態計算的,如果把 svg 放大,也可以讓 dataset 依照同樣的方式,利用到 svg 的整個範圍。


<script type="text/javascript">

            //Width and height
            var w = 500;
            var h = 300;
            var padding = 20;

            var dataset = [
                            [5, 20], [480, 90], [250, 50], [100, 33], [330, 95],
                            [410, 12], [475, 44], [25, 67], [85, 21], [220, 88],
                            [600, 150]
                          ];

            //Create scale functions
            var xScale = d3.scaleLinear()
                                 .domain([0, d3.max(dataset, function(d) { return d[0]; })])
                                 .range([padding, w - padding * 2]);

            var yScale = d3.scaleLinear()
                                 .domain([0, d3.max(dataset, function(d) { return d[1]; })])
                                 .range([h - padding, padding]);

            var rScale = d3.scaleLinear()
                                 .domain([0, d3.max(dataset, function(d) { return d[1]; })])
                                 .range([2, 5]);

            //Create SVG element
            var svg = d3.select("body")
                        .append("svg")
                        .attr("width", w)
                        .attr("height", h);

            svg.selectAll("circle")
               .data(dataset)
               .enter()
               .append("circle")
               .attr("cx", function(d) {
                    return xScale(d[0]);
               })
               .attr("cy", function(d) {
                    return yScale(d[1]);
               })
               .attr("r", function(d) {
                    return rScale(d[1]);
               });

            svg.selectAll("text")
               .data(dataset)
               .enter()
               .append("text")
               .text(function(d) {
                    return d[0] + "," + d[1];
               })
               .attr("x", function(d) {
                    return xScale(d[0]);
               })
               .attr("y", function(d) {
                    return yScale(d[1]);
               })
               .attr("font-family", "sans-serif")
               .attr("font-size", "11px")
               .attr("fill", "red");

        </script>

d3.scaleLinear() 的其他常用 methods


  1. nice() : 在映射到 range() 時,把兩端的值擴展到最接近的短數值,ex: [0.20147987687960267, 0.996679553296417] -> [0.2, 1]

  2. rangeRound() : 取代 range,會將比例尺輸出為最接近的整數

  3. clamp() : scale 預設可以傳回超過範圍之外的值,如果加上 clamp(true),會讓超過範圍的數值,變成範圍的最高或最低值


其他的比例尺


  1. sqrt : 平方根比例尺

  2. pow : 冪比例尺,指數變化的 dataset

  3. log : 對數比例尺

  4. quantize : 輸出範圍為獨立的值的線性比例尺,適合把資料分類的情形

  5. ordinal : 使用非定量值(ex: 類別名稱) 作為輸出的序數比例尺,非常適合比較蘋果和橘子

  6. d3.scale.category10() d3.scale.category20() d3.scale.category20b() d3.scale.category20c() : 能夠輸出10到20種類別顏色的預設序數比例尺

  7. d3.time.scale() : 針對日期和時間值的一個比例尺方法,可以對日期刻度進行特殊處理


軸 x & y axis


呼叫 axis method 不會有回傳值,而是產生與 axis 相關的可見元素,例如 軸線、標籤與刻度。axis method 只適用於 svg 圖形。


  • axis 在 v3 跟 v4 的差異

// v3: Define X axis
var xAxis = d3.svg.axis()
                  .scale(xScale)
                  .orient("bottom");

// v4: Define X axis
var xAxis = d3.axisBottom()
                  .scale(xScale);

  • 產生 x axis 的方法,先以 xScale 比例尺產生 xAxis,然後在 svg 後面加上 g 元素,並呼叫 call(xAxis)。 g 是個 grouping 元素,有兩種用途 (1) 包含其他元素 (2) 對整個分組應用進行變換

            //Define X axis
            var xAxis = d3.axisBottom()
                              .scale(xScale);
                              
            //Create X axis
            svg.append("g")
                .call(xAxis);

也可以合併成一行


            //Create X axis
            svg.append("g")
                .call(d3.axisBottom().scale(xScale));

上面這個產生的 x 軸,是出現在圖形的上方,如果要換到下面,要利用平移 transform 移動


            //Define X axis
            var xAxis = d3.axisBottom()
                              .scale(xScale);
                              
           svg.append("g")
                .attr("class", "axis")
                .attr("transform", "translate(0," + (h - padding) + ")")
                .call(xAxis);

可以為 axis 利用 css 進行視覺調整


        <style type="text/css">

            .axis path,
            .axis line {
                fill: none;
                stroke: black;
                shape-rendering: crispEdges;
            }

            .axis text {
                font-family: sans-serif;
                font-size: 11px;
            }

        </style>

  • tick 刻度

D3 會自動根據 scale 計算 ticks,也可以呼叫 ticks(5) 改為 5 個刻度線,但實際上 tick 只是個參考值,不是絕對值,D3 會自動調整為最適當的又接近 tick 數值的刻度數量。


            //Define X axis
            var xAxis = d3.axisBottom()
                              .scale(xScale)
                              .ticks(5);

  • y 軸

為了讓 y 放在圖形的左邊,並產生 padding,還是需要 transform 進行平移


            var padding = 30;
            
            //Define X axis
            var xAxis = d3.axisBottom()
                              .scale(xScale)
                              .ticks(5);

            //Define Y axis
            var yAxis = d3.axisLeft()
                              .scale(yScale)
                              .ticks(5);

            //Create X axis
            svg.append("g")
                .attr("class", "axis")
                .attr("transform", "translate(0," + (h - padding) + ")")
                .call(xAxis);

            //Create Y axis
            svg.append("g")
                .attr("class", "axis")
                .attr("transform", "translate(" + padding + ",0)")
                .call(yAxis);

  • 格式化軸刻度

".1%" 就是將 200 顯示為 200.0%


            var formatAsPercentage = d3.format(".1%");

            //Define X axis
            var xAxis = d3.axisBottom()
                              .scale(xScale)
                              .ticks(5)
                              .tickFormat(formatAsPercentage);

            //Define Y axis
            var yAxis = d3.axisLeft()
                              .scale(yScale)
                              .ticks(5)
                              .tickFormat(formatAsPercentage);

  • 完整的軸線處理程式碼

        <style type="text/css">

            .axis path,
            .axis line {
                fill: none;
                stroke: black;
                shape-rendering: crispEdges;
            }

            .axis text {
                font-family: sans-serif;
                font-size: 11px;
            }

        </style>
        
        <script type="text/javascript">

            //Width and height
            var w = 500;
            var h = 300;
            var padding = 30;

            /*
            //Static dataset
            var dataset = [
                            [5, 20], [480, 90], [250, 50], [100, 33], [330, 95],
                            [410, 12], [475, 44], [25, 67], [85, 21], [220, 88],
                            [600, 150]
                          ];
            */

            //Dynamic, random dataset
            var dataset = [];                   //Initialize empty array
            var numDataPoints = 50;             //Number of dummy data points to create
            var xRange = Math.random() * 1000;  //Max range of new x values
            var yRange = Math.random() * 1000;  //Max range of new y values
            for (var i = 0; i < numDataPoints; i++) {                   //Loop numDataPoints times
                var newNumber1 = Math.floor(Math.random() * xRange);    //New random integer
                var newNumber2 = Math.floor(Math.random() * yRange);    //New random integer
                dataset.push([newNumber1, newNumber2]);                 //Add new number to array
            }

            //Create scale functions
            var xScale = d3.scaleLinear()
                                 .domain([0, d3.max(dataset, function(d) { return d[0]; })])
                                 .range([padding, w - padding * 2]);

            var yScale = d3.scaleLinear()
                                 .domain([0, d3.max(dataset, function(d) { return d[1]; })])
                                 .range([h - padding, padding]);

            var rScale = d3.scaleLinear()
                                 .domain([0, d3.max(dataset, function(d) { return d[1]; })])
                                 .range([2, 5]);

            //Define X axis
            var xAxis = d3.axisBottom()
                              .scale(xScale)
                              .ticks(5);

            //Define Y axis
            var yAxis = d3.axisLeft()
                              .scale(yScale)
                              .ticks(5);

            //Create SVG element
            var svg = d3.select("body")
                        .append("svg")
                        .attr("width", w)
                        .attr("height", h);

            //Create circles
            svg.selectAll("circle")
               .data(dataset)
               .enter()
               .append("circle")
               .attr("cx", function(d) {
                    return xScale(d[0]);
               })
               .attr("cy", function(d) {
                    return yScale(d[1]);
               })
               .attr("r", function(d) {
                    return rScale(d[1]);
               });

            /*
            //Create labels
            svg.selectAll("text")
               .data(dataset)
               .enter()
               .append("text")
               .text(function(d) {
                    return d[0] + "," + d[1];
               })
               .attr("x", function(d) {
                    return xScale(d[0]);
               })
               .attr("y", function(d) {
                    return yScale(d[1]);
               })
               .attr("font-family", "sans-serif")
               .attr("font-size", "11px")
               .attr("fill", "red");
            */

            //Create X axis
            svg.append("g")
                .attr("class", "axis")
                .attr("transform", "translate(0," + (h - padding) + ")")
                .call(xAxis);

            //Create Y axis
            svg.append("g")
                .attr("class", "axis")
                .attr("transform", "translate(" + padding + ",0)")
                .call(yAxis);

        </script>


序數比例尺


序數就是有固定順序的一個數列,ex: 週一、週二、週三..,新生、大二、大三、大四


scaleBand 跟 scaleLinear 不同,使用的是離散的數據資料。


domain 用來指定輸入的值域


.domain([" 新生 ", " 大二 ", " 大三 ", " 大四 "])

.domain([0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
    10, 11, 12, 13, 14, 15, 16, 17, 18, 19])

.domain(d3.range(20))

.domain(d3.range(dataset.length))

range 用來做映射,前面是映射的範圍,第二個參數指定間距


.range([0,w], 0.2)

D3 的序數比例尺 ordinal,在 v3 跟 v4 有些 API 呼叫的差異。


// v3
var xScale = d3.scale.ordinal()
                .domain(d3.range(dataset.length))
                .rangeRoundBands([0, w], 0.05);
                
// v4
var xScale = d3.scaleBand()
                .domain(d3.range(dataset.length))
                .range([0, w], 0.05);

// v3
xScale.rangeBand()

// v4
xScale.bandwidth()

        <script type="text/javascript">

            //Width and height
            var w = 600;
            var h = 250;
            var barPadding = 1;

            var dataset = [ 5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
                            11, 12, 15, 20, 18, 17, 16, 18, 23, 25 ];

            // var xScale = d3.scale.ordinal()
            //              .domain(d3.range(dataset.length))
            //              .rangeRoundBands([0, w], 0.05);

            var xScale = d3.scaleBand()
                        .domain(d3.range(dataset.length))
                        .range([0, w], 0.05);

            var yScale = d3.scaleLinear()
                            .domain([0, d3.max(dataset)])
                            .range([0, h]);

            //Create SVG element
            var svg = d3.select("body")
                        .append("svg")
                        .attr("width", w)
                        .attr("height", h);

            //Create bars
            svg.selectAll("rect")
               .data(dataset)
               .enter()
               .append("rect")
               .attr("x", function(d, i) {
                    return xScale(i);
               })
               .attr("y", function(d) {
                    return h - yScale(d);
               })
               .attr("width", xScale.bandwidth()-barPadding)
               .attr("height", function(d) {
                    return yScale(d);
               })
               .attr("fill", function(d) {
                    return "rgb(0, 0, " + (d * 10) + ")";
               });

            //Create labels
            svg.selectAll("text")
               .data(dataset)
               .enter()
               .append("text")
               .text(function(d) {
                    return d;
               })
               .attr("text-anchor", "middle")
               .attr("x", function(d, i) {
                    // xScale(i) 是傳回 index 為 i 的原始資料
                    return xScale(i) + xScale.bandwidth() / 2;
               })
               .attr("y", function(d) {
                    return h - yScale(d) + 14;
               })
               .attr("font-family", "sans-serif")
               .attr("font-size", "11px")
               .attr("fill", "white");

        </script>

References


D3: Data-Driven Documents - Michael Bostock, Vadim Ogievetsky and Jeffrey Heer


《D3 API 詳解》隨書源碼 後面的 Refereces 有很多 D3.js 的網頁資源


用 D3.js v4 看 Pokemon 屬性表 D3.js v3 到 v4 的 migration 差異


Update d3.js scripts from V3 to V4


D3 Tips and Tricks v4.x


Mike Bostock’s Blocks


OUR D3.JS 數據可視化專題站


數據可視化與D3.js,數據可視化D3.js


讀書筆記 - 數據可視化實踐