2017/1/23

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


layout


D3 的 layout:


  1. Bundle: 把霍爾頓的分層捆綁算法應用到連線
  2. Chord: 根據矩陣關係生成弦形圖
  3. Cluster: 聚集實體生成系統樹圖
  4. Force: 根據物理模擬定位鏈接的節點
  5. Hierarchy: 派生自定義的系統佈局實現
  6. Histogram: 基於量化的分組計算數據分佈
  7. Pack: 基於遞歸原型填充產生分層佈局
  8. Partition: 遞歸細分節點數,呈射線或冰掛狀
  9. Pie: 計算一系列堆疊的條形或面積圖的基線
  10. Stack: 計算一系列堆疊的條形或面積圖的基線
  11. Tree: 計算一系列堆疊的條形或面積圖的基線
  12. Tree: 整齊的定位樹節點
  13. Treemap: 基於遞歸空間細分來顯示節點樹

pie chart


// v3
var arc = d3.svg.arc()
                .innerRadius(innerRadius)
                .outerRadius(outerRadius);

var pie = d3.layout.pie();
var color = d3.scaleCategory10();

// v4
var arc = d3.arc()
    .innerRadius(innerRadius)
    .outerRadius(outerRadius);
var pie = d3.pie();
var color = d3.scaleOrdinal(d3.schemeCategory10);

在 developer console 中,以 dataset 跟 pie(dataset) 查看資料轉換前後的差異


> dataset
[5, 10, 20, 45, 6, 25]
> pie(dataset)
[Objectdata: 5
endAngle: 6.283185307179586
index: 5
padAngle: 0
startAngle: 6.000158941991317
value: 5
__proto__: Object, Objectdata: 10endAngle: 5.660527303765393index: 3padAngle: 0startAngle: 5.094474573388854value: 10__proto__:
Object, Object, Object, Object, Object]

        <style type="text/css">

            text {
                font-family: sans-serif;
                font-size: 12px;
                fill: white;
            }

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

            //Width and height
            var w = 300;
            var h = 300;

            var dataset = [ 5, 10, 20, 45, 6, 25 ];

            var outerRadius = w / 2;
            var innerRadius = 0;
            // 調整 innerRadius 就可以變成環狀圖
            //var innerRadius = w/3;
            var arc = d3.arc()
                            .innerRadius(innerRadius)
                            .outerRadius(outerRadius);

            var pie = d3.pie();

            //Easy colors accessible via a 10-step ordinal scale
            var color = d3.scaleOrdinal(d3.schemeCategory10);

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

            //Set up groups
            var arcs = svg.selectAll("g.arc")
                          .data(pie(dataset))
                          .enter()
                          .append("g")
                          .attr("class", "arc")
                          .attr("transform", "translate(" + outerRadius + "," + outerRadius + ")");

            //Draw arc paths
            arcs.append("path")
                .attr("fill", function(d, i) {
                    return color(i);
                })
                .attr("d", arc);

            //Labels
            arcs.append("text")
                .attr("transform", function(d) {
                    return "translate(" + arc.centroid(d) + ")";
                })
                .attr("text-anchor", "middle")
                .text(function(d) {
                    return d.value;
                });

        </script>


stack chart


        <script type="text/javascript">

            //Width and height
            var w = 500;
            var h = 300;
            var barPadding = 5;

var mydata = [
{ apples: 5, oranges: 10, grapes: 22 },
{ apples: 4, oranges: 12, grapes: 28 },
{ apples: 2, oranges: 19, grapes: 32 },
{ apples: 7, oranges: 23, grapes: 35 },
{ apples: 23, oranges: 17, grapes: 43 }
];

// 資料轉換後,才能繪製 stack chart
var mystack = d3.stack()
    .keys(["apples", "oranges", "grapes"]);
    // .order(d3.stackOrderNone)
    // .offset(d3.stackOffsetNone);

var dataset = mystack(mydata);

//資料轉換後的結果
// JSON.stringify(dataset, null, '\t');
// "[
//  [
//      [0,5],
//      [0,4],
//      [0,2],
//      [0,7],
//      [0,23]
//  ],
//  [
//      [5,15],
//      [4,16],
//      [2,21],
//      [7,30],
//      [23,40]
//  ],
//  [
//      [15,37],
//      [16,44],
//      [21,53],
//      [30,65],
//      [40,83]
//  ]
// ]"

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

            var yScale = d3.scaleLinear()
                .domain([0,
                    d3.max(dataset, function(d) {
                        return d3.max(d, function(d) {
                            // 找到 d[1] 的最大值,就是圖形 y 軸的最大值

                            // console.log("d="+d);
                            // d=0,5
                            // d=0,4
                            // d=0,2
                            // d=0,7
                            // d=0,23
                            // -------
                            // d=5,15
                            // d=4,16
                            // d=2,21
                            // d=7,30
                            // d=23,40
                            // -------
                            // d=15,37
                            // d=16,44
                            // d=21,53
                            // d=30,65
                            // d=40,83
                            //return d.y0 + d.y;
                            return d[1];
                        });
                    })
                ])
                // 對應 svg 的高度
                .range([0, h]);

            //Easy colors accessible via a 10-step ordinal scale
            var colors = d3.scaleOrdinal(d3.schemeCategory10);

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

            // Add a group for each row of data
            // 建立每一行的資料
            var groups = svg.selectAll("g")
                .data(dataset)
                .enter()
                .append("g")
                .style("fill", function(d, i) {
                    return colors(i);
                });

            // Add a rect for each data value
            // 產生矩形
            var rects = groups.selectAll("rect")
                .data(function(d) { return d; })
                .enter()
                .append("rect")
                // x 軸的位置要對應到 xScale
                .attr("x", function(d, i) {
// console.log("attr x i="+i+", d[0]="+d[0]+", d[1]="+d[1]+", xScale(i)="+xScale(i));
// 03_stacked_bar.html:105 attr x i=0, d[0]=0, d[1]=5, xScale(i)=0
// 03_stacked_bar.html:105 attr x i=1, d[0]=0, d[1]=4, xScale(i)=100
// 03_stacked_bar.html:105 attr x i=2, d[0]=0, d[1]=2, xScale(i)=200
// 03_stacked_bar.html:105 attr x i=3, d[0]=0, d[1]=7, xScale(i)=300
// 03_stacked_bar.html:105 attr x i=4, d[0]=0, d[1]=23, xScale(i)=400
// 03_stacked_bar.html:105 attr x i=0, d[0]=5, d[1]=15, xScale(i)=0
// 03_stacked_bar.html:105 attr x i=1, d[0]=4, d[1]=16, xScale(i)=100
// 03_stacked_bar.html:105 attr x i=2, d[0]=2, d[1]=21, xScale(i)=200
// 03_stacked_bar.html:105 attr x i=3, d[0]=7, d[1]=30, xScale(i)=300
// 03_stacked_bar.html:105 attr x i=4, d[0]=23, d[1]=40, xScale(i)=400
// 03_stacked_bar.html:105 attr x i=0, d[0]=15, d[1]=37, xScale(i)=0
// 03_stacked_bar.html:105 attr x i=1, d[0]=16, d[1]=44, xScale(i)=100
// 03_stacked_bar.html:105 attr x i=2, d[0]=21, d[1]=53, xScale(i)=200
// 03_stacked_bar.html:105 attr x i=3, d[0]=30, d[1]=65, xScale(i)=300
// 03_stacked_bar.html:105 attr x i=4, d[0]=40, d[1]=83, xScale(i)=400
                    return xScale(i);
                })
                // y 軸的起點位置要對應到 yScale
                .attr("y", function(d) {
// console.log("attr y, d[0]="+d[0]+", d[1]="+d[1]+" yScale(d[0])="+yScale(d[0]));
// attr y, d[0]=0, d[1]=5 yScale(d[1])=18.072289156626507
// 03_stacked_bar.html:168 attr y, d[0]=0, d[1]=4 yScale(d[1])=14.457831325301205
// 03_stacked_bar.html:168 attr y, d[0]=0, d[1]=2 yScale(d[1])=7.228915662650603
// 03_stacked_bar.html:168 attr y, d[0]=0, d[1]=7 yScale(d[1])=25.301204819277107
// 03_stacked_bar.html:168 attr y, d[0]=0, d[1]=23 yScale(d[1])=83.13253012048193
// 03_stacked_bar.html:168 attr y, d[0]=5, d[1]=15 yScale(d[1])=54.21686746987952
// 03_stacked_bar.html:168 attr y, d[0]=4, d[1]=16 yScale(d[1])=57.83132530120482
// 03_stacked_bar.html:168 attr y, d[0]=2, d[1]=21 yScale(d[1])=75.90361445783132
// 03_stacked_bar.html:168 attr y, d[0]=7, d[1]=30 yScale(d[1])=108.43373493975903
// 03_stacked_bar.html:168 attr y, d[0]=23, d[1]=40 yScale(d[1])=144.57831325301206
// 03_stacked_bar.html:168 attr y, d[0]=15, d[1]=37 yScale(d[1])=133.73493975903614
// 03_stacked_bar.html:168 attr y, d[0]=16, d[1]=44 yScale(d[1])=159.03614457831327
// 03_stacked_bar.html:168 attr y, d[0]=21, d[1]=53 yScale(d[1])=191.56626506024094
// 03_stacked_bar.html:168 attr y, d[0]=30, d[1]=65 yScale(d[1])=234.93975903614458
// 03_stacked_bar.html:168 attr y, d[0]=40, d[1]=83 yScale(d[1])=300
                    return yScale(d[0]);
                })
                // 矩形的高度就是 d[1] 及 d[0] 的差異
                .attr("height", function(d) {
                    return yScale(d[1]-d[0]);
                })
                // 矩形的寬度要扣掉一點點 padding,讓長條之間留下一些空白
                .attr("width", xScale.bandwidth()-barPadding);

        </script>


force chart


<script type="text/javascript">
            //ref: http://bl.ocks.org/mbostock/4062045

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

            //Original data
            var dataset = {
                nodes: [
                    { name: "Adam" },
                    { name: "Bob" },
                    { name: "Carrie" },
                    { name: "Donovan" },
                    { name: "Edward" },
                    { name: "Felicity" },
                    { name: "George" },
                    { name: "Hannah" },
                    { name: "Iris" },
                    { name: "Jerry" }
                ],
                // edges 描述 起點及終點的線段
                edges: [
                    // Adam -> Bob
                    { source: 0, target: 1 },
                    { source: 0, target: 2 },
                    { source: 0, target: 3 },
                    { source: 0, target: 4 },
                    { source: 1, target: 5 },
                    { source: 2, target: 5 },
                    { source: 2, target: 5 },
                    { source: 3, target: 4 },
                    { source: 5, target: 8 },
                    { source: 5, target: 9 },
                    { source: 6, target: 7 },
                    { source: 7, target: 8 },
                    { source: 8, target: 9 }
                ]
            };

            //Initialize a default force layout, using the nodes and edges in dataset
            // var force = d3.forceSimulation()
            //                   .nodes(dataset.nodes)
            //                   .links(dataset.edges)
            //                   .size([w, h])
            //                   .linkDistance([50])
            //                   .charge([-100])
            //                   .start();
            var force = d3.forceSimulation(dataset.nodes)
                            .force("link", d3.forceLink(dataset.edges))
                            // 讓端點(戴上電荷)之間分開更遠
                            .force("charge",d3.forceManyBody())
                            .force("center", d3.forceCenter(w / 2, h / 2))
                            ;

            var colors = d3.scaleOrdinal(d3.schemeCategory10);

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

            //Create edges as lines
            var edges = svg.selectAll("line")
                .data(dataset.edges)
                .enter()
                .append("line")
                .style("stroke", "#ccc")
                .style("stroke-width", 1);

            //Create nodes as circles
            var nodes = svg.selectAll("circle")
                .data(dataset.nodes)
                .enter()
                .append("circle")
                .attr("r", 10)
                // 將端點設定為不同顏色
                .style("fill", function(d, i) {
                    return colors(i);
                })
                // 在端點設定拖動的功能
                .call(d3.drag()
                      .on("start", dragstarted)
                      .on("drag", dragged)
                      .on("end", dragended));

            //Every time the simulation "ticks", this will be called
            //每次 tick , 取得每條直線和每個圓形的新 x/y 值, 在 DOM 中更新它們
            force.on("tick", function() {

                edges.attr("x1", function(d) {
                        return d.source.x;
                     })
                     .attr("y1", function(d) { return d.source.y; })
                     .attr("x2", function(d) { return d.target.x; })
                     .attr("y2", function(d) { return d.target.y; });

                nodes.attr("cx", function(d) { return d.x; })
                     .attr("cy", function(d) { return d.y; });

            });

            function dragstarted(d) {
              if (!d3.event.active) force.alphaTarget(0.3).restart();
              d.fx = d.x;
              d.fy = d.y;
            }

            function dragged(d) {
              d.fx = d3.event.x;
              d.fy = d3.event.y;
            }

            function dragended(d) {
              if (!d3.event.active) force.alphaTarget(0);
              d.fx = null;
              d.fy = null;
            }

        </script>


Map


D3 使用 GeoJSON 搭配不同的投影演算法,能夠很方便就畫出地圖。


us-states.json 是美國的 GeoJSON 資料。


{"type":"FeatureCollection","features":[

{"type":"Feature","id":"01","properties":{"name":"Alabama"},
"geometry":{"type":"Polygon","coordinates":[[[-87.359296,35.00118],[-85.606675,34.984749],[-85.431413,34.124869],[-85.184951,32.859696],[-85.069935,32.580372],[-84.960397,32.421541],[-85.004212,32.322956],[-84.889196,32.262709],[-85.058981,32.13674],[-85.053504,32.01077],[-85.141136,31.840985],[-85.042551,31.539753],[-85.113751,31.27686],[-85.004212,31.003013],[-85.497137,30.997536],[-87.600282,30.997536],[-87.633143,30.86609],[-87.408589,30.674397],[-87.446927,30.510088],[-87.37025,30.427934],[-87.518128,30.280057],[-87.655051,30.247195],[-87.90699,30.411504],[-87.934375,30.657966],[-88.011052,30.685351],[-88.10416,30.499135],[-88.137022,30.318396],[-88.394438,30.367688],[-88.471115,31.895754],[-88.241084,33.796253],[-88.098683,34.891641],[-88.202745,34.995703],[-87.359296,35.00118]]]}},
{"type":"Feature","id":"02","properties":{"name":"Alaska"},"geometry":{ ....

        <script type="text/javascript">

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

            //Define map projection
            // Albers USA 是一種復合投影, 可以把阿拉斯加和夏威夷整合到西南地區的下方。
            var projection = d3.geoAlbersUsa()
                            // 這裡是把投影平移到了 SVG 圖形的中央
                            .translate([w/2, h/2])
                            //預設的縮放值是 1000, 比這個值小就會縮小地圖, 比這個值大就會放大地圖。
                            .scale([500]);

            //Define path generator
            var path = d3.geoPath()
                             .projection(projection);

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

            //Load in GeoJSON data
            // us-states.json 是 美國地圖的 geojson 資料,需要的是 geometry 資料
            d3.json("us-states.json", function(json) {

                //Bind data and create one path per GeoJSON feature
                svg.selectAll("path")
                   .data(json.features)
                   .enter()
                   // 根據 GeoJson 產生 Path
                   .append("path")
                   .attr("d", path)
                   // 填上 steelblue 顏色
                   .style("fill", "steelblue");

            });

        </script>


  • 等值區域地圖

選舉時常用,可以把相同數值的區域塗上一樣的顏色。


以量化的比例尺函數作為線性比例尺, 但比例尺輸出的則是離散的數值範圍。 這裡輸出的值可以是數值、顏色或是其他你需要的值。 這個比例尺適合把值分組( bucket),這裡只分了 5 個組, 實際上你想分幾個就分幾個。


var color = d3.scaleQuantize()
        .range(["rgb(237,248,233)","rgb(186,228,179)","rgb(116,196,118)","rgb(49,163,84)","rgb(0,109,44)"]);

us-ag-productivity-2004.csv


state,value
Alabama,1.1791
Arkansas,1.3705
Arizona,1.3847
California,1.7979
Colorado,1.0325
Connecticut,1.3209
Delaware,1.4345
Florida,1.6304
Georgia,1.3891
Iowa,1.5297
Idaho,1.4285
Illinois,1.5297
Indiana,1.4220
Kansas,1.0124
Kentucky,0.9403
Louisiana,0.9904
Maine,1.3877
Maryland,1.2457
Massachusetts,1.1458
Michigan,1.1058
Minnesota,1.2359
Missouri,1.0212
Mississippi,1.1306
Montana,0.8145
North Carolina,1.3554
North Dakota,1.0278
Nebraska,1.1619
New Hampshire,1.0204
New Jersey,1.2831
New Mexico,0.8925
Nevada,0.9640
New York,1.1327
Ohio,1.2075
Oklahoma,0.7693
Oregon,1.3154
Pennsylvania,1.0601
Rhode Island,1.4192
South Carolina,1.1247
South Dakota,1.0760
Tennessee,0.7648
Texas,0.8873
Utah,0.9638
Virginia,0.9660
Vermont,1.0762
Washington,1.1457
Wisconsin,1.1130
West Virginia,0.5777
Wyoming,0.5712

<script type="text/javascript">

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

            //Define map projection
            var projection = d3.geoAlbersUsa()
                                   .translate([w/2, h/2])
                                   .scale([500]);

            //Define path generator
            var path = d3.geoPath()
                             .projection(projection);

            //Define quantize scale to sort data values into buckets of color
            var color = d3.scaleQuantize()
                                .range(["rgb(237,248,233)","rgb(186,228,179)","rgb(116,196,118)","rgb(49,163,84)","rgb(0,109,44)"]);
                                //Colors taken from colorbrewer.js, included in the D3 download

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

            //Load in agriculture data
            d3.csv("us-ag-productivity-2004.csv", function(data) {

                //Set input domain for color scale
                // 設定顏色的值域,以 csv 的 value 欄位的 max, min 來設定
                color.domain([
                    d3.min(data, function(d) { return d.value; }),
                    d3.max(data, function(d) { return d.value; })
                ]);

                //Load in GeoJSON data
                d3.json("us-states.json", function(json) {

                    //Merge the ag. data and GeoJSON
                    // loop GeoJson 的每一個 state
                    for (var i = 0; i < data.length; i++) {

                        //Grab state name 取得州的名稱
                        var dataState = data[i].state;

                        //Grab data value, and convert from string to float
                        // 取得 csv 的 對應 value
                        var dataValue = parseFloat(data[i].value);

                        //Find the corresponding state inside the GeoJSON
                        for (var j = 0; j < json.features.length; j++) {

                            var jsonState = json.features[j].properties.name;

                            if (dataState == jsonState) {

                                //Copy the data value into the JSON
                                // 把 value 複製到 GeoJSON 裡面
                                json.features[j].properties.value = dataValue;

                                //Stop looking through the JSON
                                break;

                            }
                        }
                    }

                    //Bind data and create one path per GeoJSON feature
                    svg.selectAll("path")
                       .data(json.features)
                       .enter()
                       .append("path")
                       .attr("d", path)
                       // 建立 Path 的顏色,以 properties.value 決定顏色
                       .style("fill", function(d) {
                            //Get data value
                            var value = d.properties.value;

                            if (value) {
                                //If value exists…
                                return color(value);
                            } else {
                                //缺少資料以固定的顏色設定
                                return "#ccc";
                            }
                       });

                });

            });

        </script>


  • 在地圖上標記事件點

us-cities.csv


rank,place,population,lat,lon
1,New York city,8175133,40.71455,-74.007124
2,Los Angeles city,3792621,34.05349,-118.245323
3,Chicago city,2695598,45.37399,-92.888759
4,Houston city,2099451,41.337462,-75.733627
5,Philadelphia city,1526006,37.15477,-94.486114
6,Phoenix city,1445632,32.46764,-85.000823
7,San Antonio city,1327407,37.706576,-122.440612
8,San Diego city,1307402,37.707815,-122.466624
9,Dallas city,1197816,40.636,-91.168309
...

                    //Load in cities data
                    d3.csv("us-cities.csv", function(data) {

                        svg.selectAll("circle")
                           .data(data)
                           .enter()
                           .append("circle")
                           // 城市的座標位置要經過投影才能正確繪製在地圖上
                           .attr("cx", function(d) {
                               return projection([d.lon, d.lat])[0];
                           })
                           .attr("cy", function(d) {
                               return projection([d.lon, d.lat])[1];
                           })
                           // 依照人口的數量,決定半徑的大小
                           .attr("r", function(d) {
                                return Math.sqrt(parseInt(d.population) * 0.00004);
                           })
                           .style("fill", "yellow")
                           .style("opacity", 0.75);

                    });


  • 世界地圖的海洋

更換 GeoJSON 以及 Mercator 投影法,就可以畫出世界地圖。


        <script type="text/javascript">

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

            //Define map projection
            var projection = d3.geoMercator()
                                   .translate([w/2, h/2])
                                   .scale([100]);

            //Define path generator
            var path = d3.geoPath()
                             .projection(projection);

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

            //Load in GeoJSON data
            d3.json("oceans.json", function(json) {

                //Bind data and create one path per GeoJSON feature
                svg.selectAll("path")
                   .data(json.features)
                   .enter()
                   .append("path")
                   .attr("d", path)
                   .style("fill", "steelblue");

            });

        </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


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