MaDi's Blog

一個紀錄自己在轉職軟體工程師路上的學習小空間

0%

[D3.js] 資料視覺化之前端神器

去年參加一堂D3.js的課程,在初步學完網頁前後端之後,發現在一堆資料中如何視覺化是一大學問,所以就開始D3.js的學習路程,簡單的紀錄一下筆記,給自己未來專案開發上的參考。

何謂D3.js?

Data-Driven Documents(資料驅動的文件)

缺點是難度高,優點是彈性高,可以更自由的客製化圖表

白話來說,就是用js函式資料文件中的視覺元素綁定在一起

HTML/CSS/Javascript

文件其實就是網站,也可以說是HTML:

  • HTML(HyperText Markup Language),超文本標籤語言
  • ML: 標籤語言,ex: <a><br>
  • HT: 超文本,不同的html透過標籤互相連結,ex: a.html 連到b.html

CSS基本選擇器有四類:

  1. 標籤選擇器

    • ex: div{color:red}
  2. 類別選擇器

    • ex: td.circle{background:yellow}選出具有circle類別的td
  3. ID選擇器

    • ex: #circle{background:yellow}
  4. 屬性選擇器

    • ex: [rowspan="2"][colspan="2"]{color:red}選出具有rowspan=”2”colspan=”2”的標籤

CSS進階選擇器:

  1. 孩子選擇器

    • ex: div>p{屬性1:值1,...} 選出div的下一層所有p
  2. 子孫選擇器:

    • ex: div p{屬性1:值1,...} 選出div底下所有p

CSS優先權:

  • 從左至右代表優先到籠統
  • Style/ID/Class,虛擬類別,屬性/標籤
  • 若是同階級,則是後者為大,後者決定CSS樣式
  • 最專一是使用 !important

javascript的random:

  1. 產生1至M之間的數: 1 ≤ Math.floor(Math.random()*M+1) ≤ M
  2. 產生N至M之間的數: N ≤ Math.floor(Math.random()*(M-N+1)+N) ≤ M

D3.js中常用的匿名函式:

1
var showMsg = function(msg1, msg2){...}

載入D3.js

引入d3.js,官網,V3的版本可以支援較好的寫法,建議用v3。

1
<script src="https://d3js.org/d3.v3.min.js"></script>

鏈結語法

1
d3.select('body').append('div').text("Hi");
1
2
3
<body>
<div>Hi</div>
</body>

增加class

1
2
3
d3.select("p")
.append("div")
.classed('ball',true) //給定一個叫ball的類別

增加屬性

1
2
3
d3.select("body")
.append("div")
.attr('class','ball') //新增一個class屬性,名稱為ball

SVG繪圖

1
2
3
<svg width="400" height="300">
圖案
</svg>

圖案又分為rectcircleellipse…等等,注意的是外圍一定要用svg包住才能顯示圖案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 長方形
<rect x="100" y="100" width="150" height=“50"></rect>

// 圓形
<circle cx="100" cy="100" r="50"></circle>

// 橢圓形
<ellipse cx="100" cy="100" rx="150" ry="50"></ellipse>

// 線,stroke="顏色"是必填
<line x1="50" y1="30" x2="150" y2="200" stroke="black" stroke-width="2"></line>

// 折線,三個點: (0,0)->(200,100)->(400,300)
<polyline points="0,0 200,100 400,300" stroke="black" fill="rgba(0,0,0,0)" strokewidth="2"></polyline>

// 文字,y的位置是baseline文字基線
<text x="100" y="200" fontsize="40" font-family="arial">要放的文字</text>

// 其他屬性: 填色(fill)與外框(stroke)

第四象限
width: 向右為正
height: 向下為正

繪圖工具轉成code

1. 繪製長條圖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//HTML
<script src="https://d3js.org/d3.v3.js"></script>
<svg width="400" height="300"></svg>

//JS
for(var i=0; i<20; i++){
var thisNum = random(20,300);
d3.select("svg")
.append("rect")
.attr({
x: 10,
y: 10+12*i,
width: thisNum,
height: 10,
fill: "red"
});
d3.select("svg")
.append("text")
.attr({
x: thisNum+15,
y: 20+12*i,
"font-size": 12
}).text(thisNum);
}
function random(n,m){
return Math.ceil(Math.random()*(m-n)+n);
}

2. 繪製折線圖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//HTML
<script src="https://d3js.org/d3.v3.js"></script>
<svg width="400" height="300"></svg>

//JS
var lastNum = 0;
var points = "10,10 ";

for(var i=0; i<20; i++){
var thisNum = random(20,300);

points = points+(10+thisNum)+","+(10+12*(i+1))+" ";

d3.select("svg")
.append('circle')
.attr({
cx: 10+thisNum,
cy: 10+12*(i+1),
r: 3,
fill: "red"
});

d3.select("svg")
.append("text")
.attr({
x: thisNum+15,
y: 25+12*i,
"font-size": 12
}).text(thisNum);
lastNum = thisNum ;
}
points = points+"10,"+(21*12+10);

d3.select("svg")
.append("polyline")
.attr({
"points": points,
"fill": "rgba(0,0,0,0)",
"stroke": "#333",
});

function random(n,m){
return Math.ceil(Math.random()*(m-n)+n);
}

D3讀取資料

csv

1
2
3
4
d3.csv("檔名.csv", function(dataSet){
console.table(dataSet); //用成table的方式印出
//dataSet[i].欄位名稱1...
});

若要處理csv的資料,要在d3.csv(){..}裏頭處理,不能丟到外面再處理,否則會有時間差,導致執行順序不同而有差錯。

json

1
2
3
4
d3.json("檔名.json", function(dataSet){
console.table(dataSet); //用成table的方式印出
//dataSet[i].欄位名稱1...
});

D3綁定資料

.datum()綁定**單筆資料(變數)**,可以存在data變數裏頭

1
2
3
4
5
6
7
8
9
10
11
var arr = [85, 60, 99, 49, 77, 82];

for(var i=0; i<arr.length; i++){
d3.select("body")
.append("div")
.datum(arr[i]) //綁定
.text(function(d){ //取出綁定的資料
return d;
});
}
console.log(selectAll("div"));

.data()綁定多筆資料(陣列)**,不再需要for迴圈,用selectAll()就好**

1
2
3
4
5
6
7
8
var arr = [85, 60, 99, 49, 77, 82];

d3.select("body")
.selectAll("div")
.data(arr)
.text(function(d){
return d;
});

綁定有三種情況:

  1. 資料數量 = 視覺元素數量
  2. 資料數量 < 視覺元素數量,靠enter(進入可視化)增加足量元素
  3. 資料數量 > 視覺元素數量,靠exit (離開可視化)移除多餘元素

舉例來說

1
2
3
4
5
6
7
8
9
10
11
12
// 第一種,數量剛好
var selection = d3.select("svg")
.selectAll("rect")
.data(dataSet);

// 第二種,資料太少
selection.enter().append("rect").text(function(d){
return d;
});

// 第三種,資料太多
selection.exit().remove();

將以上程式碼分類,用bind()render()兩個函式各司其職:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var arr = [85, 60, 99, 49, 77, 82];

// bind函式一次考慮好三種情況
function bind(data){
var selection = d3.select("body")
.selectAll("div")
.data(data);
selection.enter().append("div");
selection.exit().remove();
}

// render負責把視覺元素畫出來
function render(){
d3.selectAll("div").text(function(d, i){ //順序不可改,先data再index
return i+":"+d;
})
}

bind(arr); //執行綁定
render();

綁定資料的長條圖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
<body>
<input center type="button" value="新增" onclick="update()">
<input type="button" value="移除" onclick="remove()">

<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
var arr = [30, 80, 15, 60, 67, 99];
var w = 600;
var h = 400;
var p = 100;

//呼叫函式
svg();
bind(arr);
render();

//繪圖
function svg() {
d3.select("body")
.append("svg")
.attr({
"width": w,
"height": h
});
}

//綁定資料,每個圖案都要考慮三種狀況
function bind(data) {
//長方形
var selection = d3.select("svg")
.selectAll("rect")
.data(data);
selection.enter().append("rect");
selection.exit().remove();

//文字
var selection_text = d3.select("svg")
.selectAll("text")
.data(data);
selection_text.enter().append("text");
selection_text.exit().remove();
}

//渲染到視覺元素上
function render() {
//長方形
d3.selectAll("rect")
.attr({
"x": function (d, i) {
return p + 43 * i;
},
"y": function (d) {
return h - p - d;
},
"width": 40,
"height": function (d) {
return d;
},
"fill":function(d){
if(d>70){
return "red"
}
else{
return "lightgreen"
}
}
});

//文字
d3.selectAll("text")
.attr({
"x": function (d, i) {
return p + 43 * i+7;
},
"y": function (d) {
return h - p+25;
},
}).text(function(d){
return d;
});
}

//新增按鈕觸發
function update(){
var num = random(10,100);
arr.push(num);
bind(arr);
render();
}

//移除按鈕觸發
function remove(){
arr.pop();
bind(arr);
render();
}

function random(N,M){
return Math.ceil(Math.random()*(M-N)+N)
}
</script>
</body>

比例尺(Scale)

縮小or放大

domain是輸入資料的範圍,range是輸出資料的範圍,rangeRound則是把輸出變成四捨五入

1
2
3
4
5
var xScale = d3.scale.linear()
.domain([0, 1000]) //輸入
.range([0, 100]); //輸出

console.log(xScale(455)); //45.5

Clamp設定上下限

多增加一個clamp(左右夾緊)**,當輸入的值超過輸入範圍,會自動把輸出限於輸出上下限**

1
2
3
4
5
6
7
8
//超過範圍自動變成上下限
var xScale = d3.scale.linear()
.domain([0, 1000]) //輸入
.range([0, 100]) //輸出
.clamp(true); //上下限

console.log(xScale(-100)); //0
console.log(xScale(1200)); //1000

d3.scale.linear()修改random(N,M)亂數函式

1
2
3
4
5
6
7
8
function random(N, M){
var rScale = d3.scale.linear()
.domain([0,1])
.rangeRound([N,M]);

return rScale(Math.random());
}
console.log(random(20,100))

d3.min()/d3.max()

當不知道原始資料的範圍的時候,可以使用d3.min()d3.max()來找出最小值跟最大值

1
2
3
4
var arr = [40, 50, 88];
var xScale = d3.scale.linear()
.domain([d3.min(arr), d3.max(arr)])
.range([0, 255]);

還可以透過d3.min()d3.max()函式去比較物件內部其他的屬性

1
2
3
4
5
6
7
8
9
10
var dataSet = [
{name: "Eric", tall: 180, age: 25},
{name: "Ice", tall: 150, age: 15},
];

d3.max(dataSet, function(d){
return d.tall;
})

// 180

所以前面的xScale可以寫成這樣,讓d3自己去找資料的最小值跟最大值

1
2
3
4
5
6
var dataSet = {略...};

var xScale = d3.scale.linear()
.domain([d3.min(dataSet, function(d){return d.屬性;}),
d3.max(dataSet, function(d){return d.屬性;})])
.range([輸出最小值, 輸出最大值])

序數比例尺

d3.scale.ordinal()來做到序數的功能

1
2
3
4
5
6
7
8
9
10
var index = [0,2,4,6,8];
var color = ["red", "blue", "green", "yellow", "black"];

var xScale = d3.scale.ordinal()
.domain(index)
.range(color);

console.log(xScale(0)); // "red"
console.log(xScale(4)); // "green"
console.log(xScale(15)); // "red" -> 當index不在範圍內,會回傳第一個

d3內建填色序數,用d3.scale.category20()來產生前20個顏色,也有d3.scale.category10()來產生前10個顏色,採用認領的機制,先出現的元素先認領顏色

1
2
3
4
5
var fScale = d3.scale.category20();

console.log(fScale(1)); //"#1f77b4"
console.log(fScale(2)); //"#aec7e8"
console.log(fScale("3")); //"#ff7f0e"

日期比例尺

d3.time.scale()來製作日期的比例尺,日期並不是javascript的資料型態,只是一種物件資料型態,所以如果要產生日期,得用new Date(日期)

1
2
3
4
5
6
7
8
var xScale = d3.time.scale()
.domain([
new Date("2019-01-01"),
new Date("2020-08-01")
])
.rangeRound([0, 100]);

console.log(xScale(new Date("2020-01-05"))) //64

Axes-座標軸

<g>是group的縮寫,是用來將元素做分組

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<g>
<!-- 第⼀個刻度 -->
<g>
<line></line> <!-- 第⼀個刻度的直線 -->
<text></text> <!-- 第⼀個刻度的⽂文字 -->
</g>
<!-- 第⼆個刻度 -->
<g>
<line></line> <!-- 第⼆個刻度的直線 -->
<text></text> <!-- 第⼆個刻度的⽂文字 -->
</g>
...
<!-- 坐標軸的軸線 -->
<path></path>
</g>

D3內建有座標軸語法,d3.svg.axis(),共分為五個步驟

1
2
3
4
5
6
7
8
9
10
11
12
13
//第一步-產生軸線
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("right") //預設是bottom(刻度在底部),還有top,left,right可以選
.ticks(5); //第四步-刻度數量(參考值),D3會自動選擇,不一定會照給定的值

//第二步-畫在svg上
d3.select("svg")
.append("g")
.classed("axis",true) //第三步-調整CSS樣式(座標軸本⾝是由path,line,text構成)
.attr("transform","translate(0,"+(h-padding+25)+")") //第五步-軸線位移
.call(xAxis)

CSS

1
2
3
4
5
6
7
8
9
.axis path, .axis line {
fill: none;
stroke: black; /*線條顏色*/
shape-rendering: auto; /*讓線很細,比較像座標軸*/
}
.axis text {
font-size: 11px;
fill: blue;
}

當座標軸的刻度太擠,可以用.tickFormat()來調整

1
2
3
4
5
6
7
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("right")
.ticks(5)
.tickFormat(function(d){ //把座標值修正
return d/1000000+'G';
})

互動式資料視覺化

取出不重複的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var arr1=["A","B","A","C"];
var uniArr = unique(arr1);

console.log(uniArr); //["A", "B", "C"]

function unique(array){
var n = [];
for(var i = 0; i < array.length; i++){
if (n.indexOf(array[i]) == -1){
n.push(array[i]);
}
}
return n;
}

當陣列中是包含物件,則可以用map函式來對陣列中的各元素進行操作,並返回一個一樣大小的新陣列

1
2
3
4
5
6
7
8
9
10
11
12
13
var arr1 = [
{city: "台北市", cid: "A"},
{city: "台中市", cid: "B"},
{city: "台北市", cid: "A"},
{city: "基隆市", cid: "C"}
];

var arr2 = arr1.map(function(d){
return d.city;
});

var arr3 = unique(arr2);
console.log(arr3) //["台北市", "台中市", "基隆市"]

下拉式選單

1
2
3
4
5
6
7
8
9
10
11
12
<select>
 <option value="Taipei">台北</option>
 <option value="Taoyuan">桃園</option>
 <option value="Hsinchu">新⽵</option>
 <option value="Miaoli">苗栗</option>
 ...
</select>

d3.select("select").on("change",function(){
var value = d3.select("select").property("value"); //property:像是表單等等動態的元素屬性,與attr類似
console.log(value)
})

提示框

第一種方法,一般網頁顯示的提示框

1
2
3
4
// html原始就有提示框的功能->title
d3.selectAll("circle").append("title").text(function(d){
return d.city+"\r\n"+d.industry+"\r\n"+d.amount;
});

第二種方法,自製提示框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// HTML
<div id="tooltip" class="hidden">
<p><strong id="city">Hello</strong></p>
<p id="industry">tooltip</p>
</div>

// CSS
#tooltip{
position: absolute;
/* left: 20px; */
/* top: 100px; */
background: #fff;
width: 150px;
height: auto;
padding: 0px 10px;
border-radius: 5px;
box-shadow: 5px 5px 10px rgba(0,0,0,0.3);
}
#tooltip.hidden{
lay: none;
}

// Javascript
d3.selectAll("circle")
.attr({ cx: ... })
.on("mouseover", function(d){
var tooltip = d3.select("#tooltip")
.style({
left: x該放的位置+"px",
top: y該放的位置+"px"
})
//替換tool3p內容(選擇其id後,修改內容)
d3.select("#tooltip").classed("hidden",false);
})
.on("mouseout",function(d){
d3.select(“#tooltip").classed("hidden",true);
});

layout佈局

圓餅圖

d3.layout.pie()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//佈局
var pie = d3.layout.pie().value(function(d) {
return d.crude_rate; //資料占比依據
});

//綁定
var selection = d3.select("svg")
.selectAll("g.arc")
.data(pie(dataSet));

var g_arc = selection.enter().append("g").attr("class","arc");
g_arc.append("path");
g_arc.append("text");

selection.exit().remove();

//繪圓餅圖
var outerR = 300;
var innerR = 100;
var w = 900;
var h = 600;
var arc = d3.svg.arc()
.outerRadius(outerR)
.innerRadius(innerR);

var fScale = d3.scale.category20();

d3.selectAll("g.arc")
.attr("transform", "translate("+w/2+","+h/2+")") //圓餅圖圓心在網頁上的位置
.select("path")
.attr("d", arc)
.style("fill", function(d,i) { return fScale(i); });

//把綁定到g.arc裏頭的資料用arc的外觀屬性丟給path來畫
d3.selectAll("g.arc")
.select("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; }) //arc.centroid 計算並回傳此元素中心位置(重心)
.attr({
"text-anchor":"middle",
dy: 20, //y的移動距離
dx: 20 //x的移動距離
})
.text(function(d){
return d.data.category+ " "+d.data.crude_rate;
})

排序

d3.ascending(a, b): 升序
d3.descending(a, b): 降序

1
2
3
4
var ARRAY = [{num: 4},{num: 6},{num: 8}];
var newARR = ARRAY.sort(function(a, b){
return d3.descending(a.num, b.num);
});

泡泡圖

跟圓餅圖不一樣,要先產生結構再丟給他數據

d3.layout.pack()

1
2
3
4
5
6
var pack = d3.layout.pack()
.size([寬, ⾼]) //svg畫布的長寬
.padding( 泡泡間距 )
.value( 節點數值 )
.children( ⼦節點 )
.nodes(root) //丟資料

資料(root)需要先結構化(巢狀化),每一層都要有childrenvalue

1
2
3
4
5
6
7
8
9
10
var root = {
value: (數值大小),
children: [
{ value: … ,
children:[]
},
{…},
……
]
}

如何結構化? D3.js可以透過以下程式碼來將資料巢狀化

1
2
3
4
d3.nest()
.key() //分類節點,要傳入匿名函式
.key() //分類節點,要傳入匿名函式
.entries() //來源資料

舉例來說,一個arr裝有六筆資料,每筆資料有三個欄位:valuecharnum,今天用D3.nest將他巢狀化,裏頭的key用匿名函式決定以哪一個欄位做分類節點,第一層的key用char欄位,第二層的key用num欄位,巢狀化之後會回傳一個物件(Object)。

觀察這個物件的結構,可以看到會有兩層結構,每層結構都是一組鍵值配對(Key-Value pair)。

因為第一層用char分類完會只有三種ABC,所以第一個螢光筆處只有三筆,接著每一筆裏頭藍色的數量就是資料的數量,因為A只有一筆所以藍色只有一筆,B有兩筆所以藍色有兩筆,以此類推…

而第二層再用num分類,每一筆藍色的資料裏頭就是該組依照num分類的數量,因為A只有一筆,所以螢光筆處只有一筆,B有兩筆,所以螢光筆處有兩筆,以此類推…

最後,最裏頭的value就是該筆資料存放的數據。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var arr = [
{value: 1, char: "A", num: 1},
{value: 2, char: "B", num: 1},
{value: 3, char: "B", num: 2},
{value: 4, char: "C", num: 1},
{value: 5, char: "C", num: 2},
{value: 6, char: "C", num: 3},
];

var nested_a = d3.nest()
.key(function(d){
return d.char;
})
.key(function(d){
return d.num;
})
.entries(arr);

console.log(nested_a);

除此之外,資料巢狀化還有以下三種常見的作法

.rollup(function): 彙總
.sortKeys(function): key排序
.sortValues(function): Values排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//彙總
d3.nest()
.key()
.rollup( function(v){
return v.length; //也可以自己創造欄位,ex:{a:v.length, b:12}
})
.entries()

//key排序
d3.nest()
.key()
.sortKeys(d3.descending)
.entries()

//Values排序
d3.nest()
.key()
.sortValues(d3.descending)
.entries()
  1. 上例來說,以char分類後的values會是A,B,C三個的數量,[1,2,3]。彙總的物件結構:

  2. d3.descending將Key降序的物件結構

地圖

分為以下四個步驟

  1. 取得地理資料檔 SHP
  2. 資料格式轉換SHP->JSON
  3. D3讀入JSON再綁定<path>
  4. 開始操作: 填色、位置標示、互動

STEP1: 取得地理資料檔 SHP

世界地圖下載: https://www.naturalearthdata.com/
台灣縣市界線地圖下載: https://data.gov.tw/dataset/7442

進入世界地圖網站 > 點選Downloads分頁 > 選擇Cultural類別

STEP2: 資料格式轉換SHP->JSON

轉檔的網站: Mapshaper

(1) 上傳 .shp & .dbf
(2) Simplify 調整壓縮
(3) Export 輸出 GeoJSON 或 TopoJSON

STEP3: D3讀入JSON再綁定<path>

第一種: GeoJSON

市面上流通較常見的格式

自己創造GeoJSON的網站: http://geojson.io/#map=2/20.0/0.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//1.地理資料檔: GeoJSON
d3.json("tw-map-geo.json”, function(mapDataSet) {
bind(mapDataSet);
render();
});

function bind(geoRoot){
// 2.地理投影器: 設定投影方式:麥卡托、定位點([經度,緯度])、縮放(scale)
var projection = d3.geo.mercator().center([121,24]).scale(8000);

// 3.路徑產生器: d3.geo.path()
var path = d3.geo.path().projection(projection);

// 綁定path與載入的地理資料(features:每一地理區劃)
var selection = d3.select("svg").selectAll("path").data(geoRoot.features);
selection.enter().append("path").classed("map-boundary", true)
.attr("d", path);
selection.exit().remove();
}

第二種: TopoJSON

D3.js的作者自己開發的格式,因為多了共享邊(arcs),每個邊界只會畫一次,所以檔案較小,較適合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// -----------------------HTML---------------------
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/3.0.2/topojson.min.js"></script>

// -----------------------javascript---------------------
//1.地理資料檔: TopoJSON
d3.json("tw-map-topo.json", function(mapDataSet) {
bind(mapDataSet);
render();
});
function bind(topoRoot){
// 2.地理投影器: 設定投影方式:麥卡托、定位點([經度,緯度])、縮放(scale)
var projection = d3.geo.mercator().center([121,24]).scale(8000);

// 3.路徑產生器: d3.geo.path()
var path = d3.geo.path().projection(projection);

// 4.Topo轉Geo: 使⽤topojson.js做檔案格式轉換
var geoRoot = topojson.feature(topoRoot, topoRoot.objects["COUNTY_MOI_1090820"]); //先console.log觀察

// 綁定path與載入的地理資料(features:每一地理區劃)
var selection = d3.select("svg").selectAll("path").data(geoRoot.features);
selection.enter().append("path").classed("map-boundary", true)
.attr("d", path);
selection.exit().remove();
}