2017年1月16日

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

update, transition

  • 當資料會隨著時間變化,就需要動態更新這些資料,視覺處理以 transition 動畫展現。
            d3.select("p")
                .on("click", function() {

                    //New values for dataset 更新資料
                    dataset = [ 11, 12, 15, 20, 18, 17, 16, 18, 23, 25,
                                5, 10, 13, 19, 21, 25, 22, 18, 15, 13 ];

                    //Update all rects  更新矩形的大小
                    svg.selectAll("rect")
                       .data(dataset)
                       .transition()    // <-- 加上這一個 method 就會有更新過程的動畫
                       .duration(5000)  // 設定動畫更新時間 5s
                       .attr("y", function(d) {
                            return h - yScale(d);
                       })
                       .attr("height", function(d) {
                            return yScale(d);
                       })
                       .attr("fill", function(d) {
                            return "rgb(0, 0, " + (d * 10) + ")";
                       });

                    //Update all labels 更新 label
                    svg.selectAll("text")
                       .data(dataset)
                       .transition()    // <-- 加上這一個 method 就會有更新過程的動畫
                       .duration(5000)  // 設定動畫更新時間 5s
                       .text(function(d) {
                            return d;
                       })
                       .attr("x", function(d, i) {
                            return xScale(i) + xScale.bandwidth() / 2;
                       })
                       .attr("y", function(d) {
                            return h - yScale(d) + 14;
                       });

                });
  • 延遲時間

dealy() 設定固定的時間,延遲幾毫秒後開始進行動畫,也可以用匿名函數動態設定延遲時間

ease() 設定動畫改變的加速模型,有 linear, circle, elastic, bounce...

// v3 的寫法
.ease("linear")

//v4
.ease(d3.easeLinear)
.ease(d3.easeCircle)
.ease(d3.easeBounce)

在更新矩形或是 label 時,加上 ease 就可以了

                //Update all rects
                    svg.selectAll("rect")
                       .data(dataset)
                       .transition()
                       .duration(2000)
                       .ease(d3.easeBounce)
                       .attr("y", function(d) {
                            return h - yScale(d);
                       })
                       .attr("height", function(d) {
                            return yScale(d);
                       })
                       .attr("fill", function(d) {
                            return "rgb(0, 0, " + (d * 10) + ")";
                       });
.delay(1000)      // 固定延遲時間


.delay(function(d, i) {
    return i * 100;     // 後面的動畫開始時間比前一個晚 100ms
})
.duration(500)          // 總時間縮短,避免動畫時間過長


// 這種方式,可以確保 dataset 不管有多少個,動畫時間都是合理的長度
.delay(function(d, i) {
    return i / dataset.length * 1000;   // 先將 i/data.length 做 normalized,然後再放大 1000 倍
})
.duration(500)
  • 套用亂數產生的 dataset
        <p>Click on this text to update the chart with new data values as many times as you like!</p>

        <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.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) {
                    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");

            //On click, update with new data
            d3.select("p")
                .on("click", function() {

                    //New values for dataset
                    var numValues = dataset.length;
                    var maxValue = 100; //Highest possible new value
                    dataset = [];
                    for (var i = 0; i < numValues; i++) {
                        var newNumber = Math.floor(Math.random() * maxValue);   //New random integer (0-100)
                        dataset.push(newNumber);
                    }

                    // 因為 dataset 的資料範圍改變了, 要重新計算 yScale 的 domain
                    //Recalibrate the scale domain, given the new max value in dataset
                    yScale.domain([0, d3.max(dataset)]);

                    //Update all rects
                    svg.selectAll("rect")
                       .data(dataset)
                       .transition()
                       .delay(function(d, i) {
                           return i / dataset.length * 1000;
                       })
                       .duration(500)
                       .attr("y", function(d) {
                            return h - yScale(d);
                       })
                       .attr("height", function(d) {
                            return yScale(d);
                       })
                       .attr("fill", function(d) {
                            return "rgb(0, 0, " + (d * 10) + ")";
                       });

                    //Update all labels
                    svg.selectAll("text")
                       .data(dataset)
                       .transition()
                       .delay(function(d, i) {
                           return i / dataset.length * 1000;
                       })
                       .duration(500)
                       .text(function(d) {
                            return d;
                       })
                       .attr("x", function(d, i) {
                            return xScale(i) + xScale.bandwidth() / 2;
                       })
                       .attr("y", function(d) {
                            return h - yScale(d) + 14;
                       });

                });
        </script>

  • 換成 二維 dataset
        <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>
        <p>Click on this text to update the chart with new data values as many times as you like!</p>

        <script type="text/javascript">

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

            //Dynamic, random dataset
            var dataset = [];
            var numDataPoints = 50;
            var maxRange = Math.random() * 1000;
            for (var i = 0; i < numDataPoints; i++) {
                var newNumber1 = Math.floor(Math.random() * maxRange);
                var newNumber2 = Math.floor(Math.random() * maxRange);
                dataset.push([newNumber1, newNumber2]);
            }

            //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]);

            //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", 2);

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

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



            //On click, update with new data
            d3.select("p")
                .on("click", function() {

                    //New values for dataset
                    var numValues = dataset.length;
                    var maxRange = Math.random() * 1000;
                    dataset = [];
                    for (var i = 0; i < numValues; i++) {
                        var newNumber1 = Math.floor(Math.random() * maxRange);
                        var newNumber2 = Math.floor(Math.random() * maxRange);
                        dataset.push([newNumber1, newNumber2]);
                    }

                    //Update scale domains
                    xScale.domain([0, d3.max(dataset, function(d) { return d[0]; })]);
                    yScale.domain([0, d3.max(dataset, function(d) { return d[1]; })]);

                    //Update all circles
                    svg.selectAll("circle")
                       .data(dataset)
                       .transition()
                       .duration(1000)
                       .attr("cx", function(d) {
                            return xScale(d[0]);
                       })
                       .attr("cy", function(d) {
                            return yScale(d[1]);
                       });

                    //Update X axis
                    svg.select(".x.axis")
                        .transition()
                        .duration(1000)
                        .call(xAxis);

                    //Update Y axis
                    svg.select(".y.axis")
                        .transition()
                        .duration(1000)
                        .call(yAxis);

                });

        </script>

  • transition 開始與結束的 callback function
// v3
.each("start", function() {
   d3.select(this)
     .attr("fill", "magenta")
     .attr("r", 3);
})

// v4
.on("start", function() {
   d3.select(this)
     .attr("fill", "magenta")
     .attr("r", 3);
})
                    //Update all circles
                    svg.selectAll("circle")
                       .data(dataset)
                       .transition()
                       .duration(1000)
                       // 開始時執行
                       .on("start", function() {
                           d3.select(this)
                             .attr("fill", "magenta")
                             .attr("r", 3);
                       })
                       .attr("cx", function(d) {
                            return xScale(d[0]);
                       })
                       .attr("cy", function(d) {
                            return yScale(d[1]);
                       })
                       // 結束後執行
                       .on("end", function() {
                           d3.select(this)
                             .attr("fill", "black")
                             .attr("r", 2);
                       });

在 start 時,不能再加上其他的 transition,因為 D3 限制任何元素同一時間,只能有一個 transition,新的 transition 會覆蓋舊的,這跟 jQuery 的設計不同,jQuery 會把動畫效果排隊依序執行。

// 錯誤的用法
.each("start", function() {
    d3.select(this)
        .transition()
        .duration(250)
        .attr("fill", "magenta")
        .attr("r", 3);
})

// end 可以加上另一個 transition
.on("end", function() {
   d3.select(this)
     .transition()
     .duration(1000)
     .attr("fill", "black")
     .attr("r", 2);
});

也可以在 svg 中 transition + on + transition + on 的方式,進行連續的動畫。

                    //Update all circles
                    svg.selectAll("circle")
                       .data(dataset)
                       .transition()
                       .duration(1000)
                       .on("start", function() {
                           d3.select(this)
                             .attr("fill", "magenta")
                             .attr("r", 7);
                       })
                       .attr("cx", function(d) {
                            return xScale(d[0]);
                       })
                       .attr("cy", function(d) {
                            return yScale(d[1]);
                       })
                       .transition()
                       .duration(1000)
                       .attr("fill", "black")
                       .attr("r", 2);
  • clipPath

剛剛的轉場動畫中,因為將散點圖的圓形放大,因此在接近軸線的地方,圓形會超過軸線,超過中間的圖形區塊。

可以利用 D3 的 clipPath 製造一塊遮罩板片,遮住超過該區塊的圖形。

            //Define clipping path 產生 clip path,id 為 chart-area,矩形區塊
            svg.append("clipPath")
                .attr("id", "chart-area")
                .append("rect")
                .attr("x", padding)
                .attr("y", padding)
                .attr("width", w - padding * 3)
                .attr("height", h - padding * 2);

            //Create circles 產生的散點放在 chart-area 這個 clipPath 裡面
            svg.append("g")
               .attr("id", "circles")
               .attr("clip-path", "url(#chart-area)")
               .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", 2);
  • 新增資料到舊的 dataset

先調整 dataset 將,新資料放進去,接下來產生新的 rect 及 text,並將初始位置訂在畫面的右邊,一開始就看不到新的 rect, text。

然後再將全部的 rect, text 以 transition 移動到新的位置。


        <p id="add">Add a new data value</p>
        <p id="remove">Remove a data value</p>

        <script type="text/javascript">

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

            // dataset 改為 key, value pair
            var dataset = [ { key: 0, value: 5 },       //dataset is now an array of objects.
                            { key: 1, value: 10 },      //Each object has a 'key' and a 'value'.
                            { key: 2, value: 13 },
                            { key: 3, value: 19 },
                            { key: 4, value: 21 },
                            { key: 5, value: 25 },
                            { key: 6, value: 22 },
                            { key: 7, value: 18 },
                            { key: 8, value: 15 },
                            { key: 9, value: 13 },
                            { key: 10, value: 11 },
                            { key: 11, value: 12 },
                            { key: 12, value: 15 },
                            { key: 13, value: 20 },
                            { key: 14, value: 18 },
                            { key: 15, value: 17 },
                            { key: 16, value: 16 },
                            { key: 17, value: 18 },
                            { key: 18, value: 23 },
                            { key: 19, value: 25 } ];

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

            // d3.max 改為 使用 dataset 的 value
            var yScale = d3.scaleLinear()
                            .domain([0, d3.max(dataset, function(d) { return d.value; })])
                            .range([0, h]);

            //定義用來 bind data 的 key function
            var key = function(d) {
                return d.key;
            };

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

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

            //Create labels
            svg.selectAll("text")
               .data(dataset, key)
               .enter()
               .append("text")
               .text(function(d) {
                    return d.value;
               })
               .attr("text-anchor", "middle")
               .attr("x", function(d, i) {
                    return xScale(i) + xScale.bandwidth() / 2;
               })
               .attr("y", function(d) {
                    return h - yScale(d.value) + 14;
               })
               .attr("font-family", "sans-serif")
               .attr("font-size", "11px")
               .attr("fill", "white");

            //On click, update with new data
            d3.selectAll("p")
                .on("click", function() {

                    // 判斷點擊了 add / remove
                    var paragraphID = d3.select(this).attr("id");

                    //Decide what to do next
                    if (paragraphID == "add") {
                        //Add a data value
                        var maxValue = 25;
                        var newNumber = Math.floor(Math.random() * maxValue+1);
                        // 新的 key 以 dataset 最後一個元素的 key +1 來設定
                        var lastKeyValue = dataset[dataset.length - 1].key;
                        console.log(lastKeyValue);
                        dataset.push({
                            key: lastKeyValue + 1,
                            value: newNumber
                        });
                    } else {
                        //Remove a value, 移除 dataset 最前面那個元素
                        dataset.shift();
                    }

                    //Update scale domains
                    xScale.domain(d3.range(dataset.length));
                    yScale.domain([0, d3.max(dataset, function(d) { return d.value; })]);

                    //Select…
                    var bars = svg.selectAll("rect")
                        .data(dataset, key);

                    // exit 會回傳被移除的元素
                    bars.exit().remove();
                    // bars.exit()
                    //  .transition()
                    //  .duration(500)
                    //  .attr("x", -xScale.bandwidth())
                    //  .remove();

                    //Enter…
                    var newbars = bars.enter()
                        .append("rect")
                        .attr("x", w)
                        .attr("y", function(d) {
                            return h - yScale(d.value);
                        })
                        .attr("width", xScale.bandwidth()-barPadding)
                        .attr("height", function(d) {
                            return yScale(d.value);
                        })
                        .attr("fill", function(d) {
                            return "rgb(0, 0, " + (d.value * 10) + ")";
                        });

                    //Update…
                    // bars.transition()
                    svg.selectAll("rect").transition()
                        .duration(500)
                        .attr("x", function(d, i) {
                            return xScale(i);
                        })
                        .attr("y", function(d) {
                            return h - yScale(d.value);
                        })
                        .attr("width", xScale.bandwidth()-barPadding)
                        .attr("height", function(d) {
                            return yScale(d.value);
                        });


                    //Update all labels

                    //Select…
                    var labels = svg.selectAll("text")
                        .data(dataset, key);

                    labels.exit().remove();

                    //Enter…
                    labels.enter()
                        .append("text")
                        .text(function(d) {
                            return d.value;
                        })
                        .attr("text-anchor", "middle")
                        .attr("x", w)
                        .attr("y", function(d) {
                            return h - yScale(d.value) + 14;
                        })
                       .attr("font-family", "sans-serif")
                       .attr("font-size", "11px")
                       .attr("fill", "white");

                    //Update…
                    // labels.transition()
                    svg.selectAll("text").transition()
                        .duration(500)
                        .attr("x", function(d, i) {
                            return xScale(i) + xScale.bandwidth() / 2;
                        });
                });


        </script>

互動式圖表

  • event listener

以 on method 綁定 evnet listener,在 callback function 中調整畫面。

// 以 on 綁定 click 事件
.on("click", function(d) {
    console.log(d);
})

css mouse hover

// 加上 css,讓滑鼠 hover 時,改變顏色
        <style type="text/css">

            rect:hover {
                fill: orange;
            }

        </style>

mouseover, mouseout

// 以 on 綁定 mouseover 事件
.on("mouseover", function() {
    // this 就是目前這個操作的元素
    d3.select(this)
        .attr("fill", "orange");
})
// 以 on 綁定 mouseout 事件
.on("mouseout", function(d) {
   d3.select(this)
        .attr("fill", "rgb(0, 0, " + (d * 10) + ")");
})

在 mouseout 以 transition 方式改回原本的顏色,讓畫面更流暢

// 以 on 綁定 mouseover 事件
.on("mouseover", function() {
    d3.select(this)
        .attr("fill", "orange");
})
// 以 on 綁定 mouseout 事件, 以 transition 方式改回原本的顏色,讓畫面更流暢
.on("mouseout", function(d) {
   d3.select(this)
        .transition()
        .duration(250)
        .attr("fill", "rgb(0, 0, " + (d * 10) + ")");
})

完整的範例

        <script type="text/javascript">

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

            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.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())
               .attr("height", function(d) {
                    return yScale(d);
               })
               .attr("fill", function(d) {
                    return "rgb(0, 0, " + (d * 10) + ")";
               })
               // 以 on 綁定 mouseover 事件
               .on("mouseover", function() {
                    d3.select(this)
                        .attr("fill", "orange");
               })
               // 以 on 綁定 mouseout 事件, 以 transition 方式改回原本的顏色,讓畫面更流暢
               .on("mouseout", function(d) {
                   d3.select(this)
                        .transition()
                        .duration(250)
                        .attr("fill", "rgb(0, 0, " + (d * 10) + ")");
               });

            //Create labels
            svg.selectAll("text")
               .data(dataset)
               .enter()
               .append("text")
               .text(function(d) {
                    return d;
               })
               // 加上這個 css style,讓滑鼠移動到 label 時,不會變成鍵盤輸入的游標
               .style("pointer-events", "none")
               .attr("text-anchor", "middle")
               .attr("x", function(d, 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>
  • sorting

可以在 on action listener 中,呼叫 sortBars function,可針對矩形及 label 進行排序。

但上面最後一個例子中,綁定了 mouseover 及 mouseout 進行 hover 顏色變化的處理,如果在 sorting 時,移動了滑鼠,因為 D3 預設會覆蓋動畫,這會造成 mouseover 及 mouseout 的元素,會停留在滑鼠指到的地方的問題。

要解決這個問題,必須將 hover 的顏色處理,回歸讓 css 來調整。

            //Define sort function
            var sortBars = function() {

                svg.selectAll("rect")
                   .sort(function(a, b) {
                       return d3.ascending(a, b);
                    })
                   .transition()
                   .duration(1000)
                   .attr("x", function(d, i) {
                        return xScale(i);
                   });

                svg.selectAll("text")
                    .sort(function(a, b) {
                       return d3.ascending(a, b);
                    })
                    .transition()
                    .duration(1000)
                    .attr("x", function(d, i) {
                        return xScale(i) + xScale.bandwidth() / 2;
                    });
            };

以下為實例,排序會在順序及倒序兩個一直變換。

        <style type="text/css">

            rect:hover {
                fill: orange;
            }

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

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

            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.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())
               .attr("height", function(d) {
                    return yScale(d);
               })
               .attr("fill", function(d) {
                    return "rgb(0, 0, " + (d * 10) + ")";
               })
               .on("click", function() {
                    sortBars();
               });

            //Create labels
            svg.selectAll("text")
               .data(dataset)
               .enter()
               .append("text")
               .text(function(d) {
                    return d;
               })
               .attr("text-anchor", "middle")
               .attr("x", function(d, 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");

            //Define sort order flag
            var sortOrder = false;

            //Define sort function
            var sortBars = function() {

                //Flip value of sortOrder
                sortOrder = !sortOrder;

                svg.selectAll("rect")
                   .sort(function(a, b) {
                        if (sortOrder) {
                            return d3.ascending(a, b);
                        } else {
                            return d3.descending(a, b);
                        }
                    })
                   .transition()
                   // 加上 delay 會減慢變化的過程
                   //.delay(function(d, i) {
                    //   return i * 50;
                   //})
                   .duration(1000)
                   .attr("x", function(d, i) {
                        return xScale(i);
                   });

                svg.selectAll("text")
                    .sort(function(a, b) {
                       if (sortOrder) {
                            return d3.ascending(a, b);
                        } else {
                            return d3.descending(a, b);
                        }
                    })
                    .transition()
                    // 加上 delay 會減慢變化的過程
                   //.delay(function(d, i) {
                    //   return i * 50;
                   //})
                    .duration(1000)
                    .attr("x", function(d, i) {
                        return xScale(i) + xScale.bandwidth() / 2;
                    });
            };

        </script>
  • tooltip

瀏覽器標準 tooltip: 在產生矩形時以 title 及 text 產生 tooltip

            //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())
               .attr("height", function(d) {
                    return yScale(d);
               })
               .attr("fill", function(d) {
                    return "rgb(0, 0, " + (d * 10) + ")";
               })
               .on("click", function() {
                    sortBars();
               })
               // 在矩形中以 title 產生 tooltip
               .append("title")
               .text(function(d) {
                    return "This value is " + d;
               });

svg tooltip: mouseover 中,動態產生 svg 的 text 區塊,然後在 mouseout 移除

            //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())
               .attr("height", function(d) {
                    return yScale(d);
               })
               .attr("fill", function(d) {
                    return "rgb(0, 0, " + (d * 10) + ")";
               })
               .on("mouseover", function(d) {

                    //Get this bar's x/y values, then augment for the tooltip
                    var xPosition = parseFloat(d3.select(this).attr("x")) + xScale.bandwidth() / 2;
                    var yPosition = parseFloat(d3.select(this).attr("y")) + 14;

                    //Create the tooltip label
                    svg.append("text")
                       .attr("id", "tooltip")
                       .attr("x", xPosition)
                       .attr("y", yPosition)
                       .attr("text-anchor", "middle")
                       .attr("font-family", "sans-serif")
                       .attr("font-size", "11px")
                       .attr("font-weight", "bold")
                       .attr("fill", "black")
                       .text(d);

               })
               .on("mouseout", function() {

                    //Remove the tooltip
                    d3.select("#tooltip").remove();

               })
               .on("click", function() {
                    sortBars();
               });

div tooltip: 跟 svg 一樣,mouseover 中,動態產生 div 區塊,然後在 mouseout 隱藏

            //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())
               .attr("height", function(d) {
                    return yScale(d);
               })
               .attr("fill", function(d) {
                    return "rgb(0, 0, " + (d * 10) + ")";
               })
               .on("mouseover", function(d) {

                    //Get this bar's x/y values, then augment for the tooltip
                    var xPosition = parseFloat(d3.select(this).attr("x")) + xScale.bandwidth() / 2;
                    var yPosition = parseFloat(d3.select(this).attr("y")) / 2 + h / 2;

                    //Update the tooltip position and value
                    d3.select("#tooltip")
                        .style("left", xPosition + "px")
                        .style("top", yPosition + "px")
                        .select("#value")
                        .text(d);

                    //Show the tooltip
                    d3.select("#tooltip").classed("hidden", false);

               })
               .on("mouseout", function() {

                    //Hide the tooltip
                    d3.select("#tooltip").classed("hidden", true);

               })
               .on("click", function() {
                    sortBars();
               });

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

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