選擇元素
d3中選擇元素的API有兩個:
select()
方法和
selectAll()
方法。
-
:傳回比對選擇器的第一個元素,用于選擇單個元素時使用;select
-
:傳回比對選擇器的所有元素,用于選擇多個元素時使用;selectAll
這兩個選擇元素的API方法的參數是選擇器,即指定應當選擇文檔中的哪些元素。這個選擇器參數可以是CSS選擇器,也可以是已經被DOM API選擇的元素(如
document.getElementById("id1")
)。不過為了簡單易懂好維護,推薦使用CSS選擇器。
此外如果要對選擇集中的元素再進行一番選擇,例如選擇body中的所有p元素,除了使用CSS的派生選擇器作為參數外,還可以采用這個方法
d3.select( "body" ).selectAll( "p" )
。這是類似于jQuery的鍊式調用寫法。
選擇集
選擇集(
selection
)就是
d3.select()
和
d3.selectAll()
方法傳回的對象。添加、删除、設定頁面中的元素都需要用到這個選擇集。
①.檢視選擇集元素的狀态
檢視選擇集的狀态,有三個函數可用:
-
:如果選擇集為空,則傳回selection.empty()
,非空傳回true
;false
-
:傳回第一個非空元素,如果選擇集為空,傳回selection.node()
;null
-
:傳回選擇集中的元素個數;selection.empty()
<body>
<p>1</p>
<p>2</p>
<p>3</p>
</body>
<script>
let selection = d3.selectAll( "p" );
console.log( selection.empty() ); // false
console.log( selection.node() ); // <p>1</p>
console.log( selection.size() ); // 3
</script>
複制
②.擷取和設定選擇集元素的屬性
d3中設定和擷取選擇集屬性的API函數共有六個:
-
:設定或擷取選擇集元素的屬性,name是屬性名,value是屬性值,如果省略value,則傳回目前name的屬性值;如果不省略則将屬性name的值設定為value。selection.attr( name[, value] )
-
:設定或擷取選擇集元素的CSS類,name是類名,boolean是一個布爾值。布爾值表示該類是否開啟。selection.classed( name[, boolean] )
-
:設定或擷取選擇集元素的樣式,name是樣式名,value是樣式值。如果隻寫第一個參數name,則傳回該樣式的值。selection.style( name[, value[, priority]] )
-
:設定或擷取選擇集元素的屬性,name是屬性名,value是屬性值,如果省略value,則傳回目前name的屬性值;如果不省略則将屬性name的值設定為value。有部分屬性是不能用selection.property( name[, value] )
來設定和擷取的,最典型的是文本輸入框的value屬性,此屬性不會在标簽中顯示。當使用第二個參數時,可以給文本框指派。另外還有複選框等。attr()
-
:設定或擷取選擇集元素的文本内容。如果省略value參數,則傳回目前的文本内容。文本内容相當于DOM的innerText,不包括元素内部的标簽。selection.text( [value] )
-
:設定或擷取選擇集的内部HTML内容,相當于DOM的innerHTML,包括元素内部的标簽。selection.html( [value] )
操作選擇集:添加、插入和删除
操作選擇集的方法有添加、插入和删除。
-
:在選擇集的末尾添加一個元素,name為元素名稱。selection.append( name )
-
:在選擇集中的指定元素之前插入一個元素,name是被插入的元素名稱,before是CSS選擇器名稱。selection.insert( name[, before] )
-
:删除選擇集中的元素。該方法沒有參數,就是單純删除選擇集對象對應的元素。selection.remove()
資料綁定
d3.select()
和
d3.selectAll()
傳回的元素選擇集上是沒有任何資料的。想要在選擇集上綁定資料,就需要這樣兩個API方法:
-
:選擇集中的每一個元素都綁定相同的資料value,即selection.datum( [value] )
-
:選擇集中的每一個元素分别綁定數組values中的每一項。key是一個鍵函數,用于指定綁定數組時的對應規則。selection.data( [values[, key]] )
①.datum()的工作過程
對于選擇集中的每一個元素,
datum()
方法都為其增加一個
__data__
屬性,屬性值是
datum(value)
的參數value。value值類型任意,但如果值為
null
或
undefined
,則不會建立
__data__
屬性。
外鍊圖檔轉存失敗(img-VAxTsZnU-1568253451779)(https://github.com/nitxs/public_docs/blob/master/image_hosting/19/190520_0.png?raw=true)
外鍊圖檔轉存失敗(img-NpRUT7wy-1568253451781)(https://github.com/nitxs/public_docs/blob/master/image_hosting/19/190520_1.png?raw=true)
資料被綁定到選擇集元素上後,該如何使用呢?
以被綁定的資料替換元素中原本的文本為例:
<body>
<p>1</p>
<p>2</p>
<p>3</p>
</body>
<script>
let selection = d3.select( "body" ).selectAll( "p" );
selection.datum( 11 ) // 綁定數值11到所有選擇集元素上
.text( function( d, i ){ // 替換内容
return d + " " + i;
} )
</script>
複制
上例的
text()
方法參數是一個匿名函數
function( d, i ){}
,該匿名函數的參數分别是
d
和
i
,表示資料和索引。
d3還能将被綁定的資料傳遞給子元素。例如
selection.datum( 11 )
.append( "span" )
.text( function( d, i ){
return d + " " + i;
} ) )
複制
此時在控制台列印的結果顯示p的子元素span裡也含有屬性
__data__
,并且屬性值也繼承自p的
__data__
。
②.data()的工作過程
data()
能将數組各項分别綁定到選擇集的各元素上,并且能指定綁定的規則。
當數組長度與選擇集元素個數不一緻時也可以處理:當數組長度大于元素數量時,為多餘資料預留元素位置以便将來插入新元素;當數組長度小于元素數量時,能擷取多餘元素的位置,以便将來删除。
使用
data()
綁定資料的示例如下:
<body>
<p>1</p>
<p>2</p>
<p>3</p>
</body>
<script>
let dataset = [ 3, 6, 9 ];
let selection = d3.select( "body" ).selectAll( "p" );
let update = selection.data( dataset ); // 将數組綁定到選擇集
console.log( update ); // 輸出綁定結果
</script>
複制
列印結果截圖如下:
外鍊圖檔轉存失敗(img-NwgOT8JG-1568253451783)(https://github.com/nitxs/public_docs/blob/master/image_hosting/19/190520_2.png?raw=true)
上例中資料長度與選擇集元素個數正好相等。當然也會有兩者不等的情況。
根據數組長度與元素數量的關系,有以下三種情況:
-
:數組長度 === 元素數量update
-
:數組長度 > 元素數量enter
-
:數組長度 < 元素數量exit
以上三種情況可以這樣了解:
- 如果數組長度等于元素數量,則綁定資料的元素為
;即将被更新 update
- 如果數組長度大于元素數量,則部分還不存在的元素
;即将進入可視化 enter
- 如果數組長度小于元素數量,則多餘的元素為
;即将退出可視化 exit
以數組長度為5,元素數量為3為例:
<body>
<p>1</p>
<p>2</p>
<p>3</p>
</body>
<script>
let dataset = [ 3, 6, 9, 10, 2 ];
let selection = d3.select( "body" ).selectAll( "p" );
let update = selection.data( dataset ); // 将數組綁定到選擇集
console.log( update ); // 輸出綁定結果
</script>
複制
結果截圖:
外鍊圖檔轉存失敗(img-ciYLc78T-1568253451784)(https://github.com/nitxs/public_docs/blob/master/image_hosting/19/190520_3.png?raw=true)
以數組長度為1,元素數量為3為例:
<body>
<p>1</p>
<p>2</p>
<p>3</p>
</body>
<script>
let dataset = [ 3 ];
let selection = d3.select( "body" ).selectAll( "p" );
let update = selection.data( dataset ); // 将數組綁定到選擇集
console.log( update ); // 輸出綁定結果
</script>
複制
代碼執行結果截圖:
外鍊圖檔轉存失敗(img-SN6vEcGS-1568253451785)(https://github.com/nitxs/public_docs/blob/master/image_hosting/19/190520_4.png?raw=true)
從上面的截圖可以看到,除了被綁定資料的三個p元素外,還有
enter()
和
exit()
兩個函數,它們分别傳回
enter
和
exit
部分。當數組長度大于元素數量時,
enter
函數有值,d3已為多餘數組項10和2預留了位置以備将來添加元素;當數組長度小于元素數組時,
exit
函數有值。
③.資料綁定的順序
預設狀态下,
data()
是按索引号順序綁定的。如果需求要不按索引号綁定,可以使用
data()
方法的第二個參數,即鍵函數。注意,隻有在選擇集原來已有綁定資料的前提下,使用鍵函數才生效。
選擇集的處理
之前講過d3對資料綁定的操作。當數組長度與元素數量不一緻時,有enter部分和exit部分,前者表示存在多餘的資料待插入,後者表示存在多餘的元素待删除。下面就來看下怎麼d3是怎麼處理這些多餘的東西的,并給出一個處理模闆,包含處理update、enter、exit。
①.enter的處理方法
如果存在多餘的資料待插入,即沒有足夠的元素,那麼處理方法就是添加元素。
以下示例中p元素僅有一個,但資料有3個,是以enter部分就有多餘的兩個資料,解決辦法是手動添加元素使其與多餘資料對應,處理後就有3個p元素:
<body>
<p></p>
</body>
<script>
let dataset = [ 3, 6, 9 ];
let p = d3.select( "body" ).selectAll( "p" );
let update = p.data( dataset ),
enter = update.enter();
// update部分的處理方法是直接修改内容
update.text( function( d, i ){
return d;
} )
// enter部分的處理方法是添加元素後再修改内容
enter.append( "p" )
.text( function( d, i ){
return d;
} )
</script>
複制
通常情況下,從伺服器讀取資料時,頁面中是不會有與之相對應的元素的。此時最常用的方法是:選擇一個空集,然後使用enter().append()的方法來添加足夠多的元素。上例可以改為:
<body>
</body>
<script>
let dataset = [ 3, 6, 9 ];
let body = d3.select( "body" );
body.selectAll( "p" ) // 選擇body中的所有p元素,但由于沒有p元素,是以傳回的選擇集對象是個空集
.data( dataset ) // 綁定dataset數組
.enter() // 傳回enter()部分
.append( "p" ) // 添加p元素
.text( function( d, i ){ // 修改p元素中的内容
return d;
} )
</script>
複制
②.exit的處理方法
如果存在多餘的元素,但沒有與之相對應的資料,即數組長度小于元素個數,那麼d3就會使用
remove()
删除多餘的元素。示例代碼如下:
<body>
<p></p>
<p></p>
<p></p>
<p></p>
<p></p>
</body>
<script>
let dataset = [ 3, 6, 9 ];
let p = d3.select( "body" ).selectAll( "p" );
// 綁定資料後,分别擷取update部分和exit部分
let update = p.data( dataset ),
exit = update.exit();
// update部分的處理方法是修改内容
update.text( function( d, i ){
return d;
} )
// exit部分的處理方法是删除元素
exit.remove();
</script>
複制
删除後頁面上就不會有多餘的空p元素。
③.通用處理模闆
在通常情況下,是不知道數組長度與元素個數關系的,是以需要給出一個通用的解決方案:
<body>
</body>
<script>
let dataset = [ 3, 6, 9 ];
let p = d3.select( "body" ).selectAll( "p" );
// 綁定資料後分别傳回update、enter、exit部分
let update = p.data( dataset );
let enter = update.enter();
let exit = update.exit();
// update部分的處理方法
update.text( function( d, i ){ return d; } );
// enter部分的處理方法
enter.append( "p" )
.text( function( d, i ){ return d } );
// exit部分的處理方法
exit.remove();
</script>
複制
如此,則不需要關心頁面中的元素夠不夠,無論何種情況,頁面中的元素和數組中每個資料都會一一對應顯示,沒有多餘。這種通常模闆在實際應用中是非常實用的。
④.過濾器
有時需求要根據被綁定資料對某些選擇集的元素進行篩選,進而擷取選擇集的子集,就要用到過濾器方法filter()。
<body>
</body>
<script>
let dataset = [ 3, 6, 9 ];
let p = d3.select( "body" ).selectAll( "p" );
let update = p.data( dataset ),
enter = update.enter(),
exit = update.exit();
update.text( function( d, i ){
return d;
} )
enter.append( "p" )
.filter( function( d, i ){ // 篩選 資料大于6 的項顯示
if( d > 6 ){
return true;
}else {
return false;
}
} )
.text( function( d, i ){
return d;
} )
exit.remove();
</script>
複制
⑤.選擇集的順序
使用
sort()
可以将被綁定資料重新排列選擇集中的元素。d3預設使用d3.ascending(遞增)順序排列。可以向
sort()
中傳入一個匿名函數參數,來對選擇集重新選擇。
<body>
</body>
<script>
let dataset = [ 3, 6, 9 ];
let p = d3.select( "body" ).selectAll( "p" );
let update = p.data( dataset ),
enter = update.enter(),
exit = update.exit();
update.text( function( d, i ){
return d;
} )
enter.append( "p" )
.sort( function( a, b ){ // 更改預設的遞增排序為遞減排序
return b-a;
} )
.text( function( d, i ){
return d;
} )
exit.remove();
</script>
複制
⑥.each()
each()
方法可以對選擇集的各元素分别處理:
<body>
</body>
<script>
let dataset = [
{id: 3, name: 'nitx'},
{id: 9, name: 'nz'},
{id: 6, name: 'hx'}
];
let p = d3.select( "body" ).selectAll( "p" );
let update = p.data( dataset ),
enter = update.enter(),
exit = update.exit();
update.text( function( d, i ){
return d.id + " " + d.age + ' ' + d.name;
} )
enter.append( "p" )
.each( function( d, i ){
d.age = i*20;
return d;
} )
.text( function( d, i ){
return d.id + " " + d.age + ' ' + d.name;
} )
exit.remove();
</script>
複制
⑦.call()的應用
call()
方法可以将選擇集自身作為參數傳入業務函數中,應用場景如拖拽、縮放元素等。
<body>
</body>
<script>
let dataset = [
{id: 3, name: 'nitx'},
{id: 9, name: 'nz'},
{id: 6, name: 'hx'}
];
let p = d3.select( "body" ).selectAll( "p" );
let update = p.data( dataset ),
enter = update.enter(),
exit = update.exit();
update.text( function( d, i ){
return d.id + " " + d.age + ' ' + d.name;
} )
enter.append( "p" )
.each( function( d, i ){
d.age = i*20;
return d;
} )
.call( fn ) // 将選擇集傳入call參數 fn 函數中進行操作
.text( function( d, i ){
return d.id + " " + d.age + ' ' + d.name;
} )
exit.remove();
function fn( selection ){
// 函數體内部定義對選擇集的操作邏輯
console.log( selection );
console.log( selection.node() );
selection.style( "color", "red" );
}
</script>
複制
d3中處理數組的API
盡管原生js中已有很多處理數組的API,甚至在ES6中又新增了好多方法,但并不能完全滿足資料可視化的需求,d3為此封裝了不少數組處理函數。
①.排序
d3中對數組排序可以使用
sort()
方法,如果不傳入比較函數,則預設是按鈕
d3.ascending
(遞增)排序,此外也可以定義成
d3.descending
(遞減)排序。排序後會改變原數組。
let arr = [ 10, 3, 5, 6, 7 ];
arr.sort( d3.ascending ); // [3, 5, 6, 7, 10]
arr.sort( d3.dscending ); // [10, 3, 5, 6, 7]
複制
②.求值
對數組求值的常用操作有最大值、最小值、中間值、平均值等。d3提供了相應的操作函數,它們類似于這樣:
d3.fn( array[, accessor] )
。參數array就是目标操作數組,可選參數accessor是一個存取器函數,定義後數組各項會先執行accessor函數,然後再使用fn函數處理。注意以下方法中參數array裡無效值(如null、undefined、NAN等在計算時會被忽略,不影響方法執行)
-
:傳回數組最小值。d3.min( array[, accessor] )
-
:傳回數組最大值。d3.max( array[, accessor] )
-
:傳回數組最小值和最大值,注意傳回值是一個數組,第一項是最小值,第二項是最大值。d3.extent( array[, accessor] )
-
:傳回數組的總和,如果數組為空,則傳回0d3.sum( array[, accessor] )
-
:傳回數組的平均值,如果數組為空,則傳回undefind。注意由于數組中可能存在無效值,是以本方法求平均值并非按照d3.mean( array[, accessor] )
來算,而是按照和/數組長度
來算的。和/去除無效值後的有效長度
-
:求數組的中間值,如果數組為空,則傳回undefined。如果數組的有效長度為奇數,則中間值為數組經遞增排序後位于正中間的值;如果數組的有效長度為偶數,則中間值為經遞增排序後位于正中間的兩個數的平均值。d3.median( array[, accessor] )
-
:求取p分位點的值,p的範圍是0, 1。數組需要先遞增排序。參數numbers是經遞增排序後的數組。舉例:d3.quantile( numbers, p )
d3.quantile( numbers.sort(d3.ascending), 0.5 )
-
:求方差d3.variance( array[, accessor] )
-
:求标準差。方差和标準差用于度量随機變量和均值之間的偏離程度,多用于機率統計。其中标準差是方差的二次方根。這兩個值越大,表示此随機變量偏離均值的程度越大。d3.deviation( array[, accessor] )
-
:擷取某數組項左邊的位置d3.bisectLeft()
-
:擷取某數組項右邊的位置d3.bisect()
-
:擷取某數組項右邊的位置,以上這三方法用于需要對數組中指定位置插入項時首先要擷取指定位置的需求。這幾個方法可以配合d3.bisectRight()
方法來插入項。splice()
以上方法的代碼示例:
// 求最大最小值
let array1 = [ 30, 20, 10, 50, 40 ];
let min = d3.min( array1 ); // 10
let max = d3.max( array1 ); // 50
let extent = d3.extent( array1 ); // [10, 50]
// 在求值之前,先用accessor函數處理資料
let minAcc = d3.min( array1, function(d){ return d*3 } ); // 30
let maxAcc = d3.max( array1, function(d){ return d-5 } ); // 45
let extentAcc = d3.extent( array1, function(d){ return d%7 } ); // [1, 6]
// 求總和、平均值
let array2 = [ 69, 11, undefined, 53, 27, 82, 65, 34, NaN, null ];
let sum = d3.sum( array2, function( d, i ){ return d*2 } ); // 682
let mean = d3.mean( array2 ); // 48.714285714285715
// 求中間值
let array3 = [ 3, 1, 7, undefined, 9, NaN ]; // 數組有效長度為偶數
let median1 = d3.median( array3 ); // 5
let array4 = [ 3, 1, 7, undefined, 9, 10, NaN ]; // 數組有效長度為奇數
let median2 = d3.median( array4 ); // 7
// 求分位點的值
let array5 = [ 3, 1, 10 ];
array5.sort( d3.ascending ); // 遞增
console.log( d3.quantile( array5, 0 ) ); // 1
console.log( d3.quantile( array5, 0.25 ) ); // 2
console.log( d3.quantile( array5, 0.5 ) ); // 3
console.log( d3.quantile( array5, 0.75 ) ); // 6.5
console.log( d3.quantile( array5, 0.9 ) ); // 8.600000000000001
console.log( d3.quantile( array5, 1 ) ); // 10
// 求方差和标準差,比較值偏離平均值的程度,結果顯示雖然兩個數組的平均值都是7.25,但是前者的方差和标準差大于後者,表明數組array6中的值偏離平均值程度更大
let array6 = [ 1, 9, 7, 9, 5, 8, 9, 10 ];
console.log( d3.mean( array6 ) ); // 平均值 7.25
console.log( d3.variance( array6 ) ); // 方內插補點 8.785714285714286
console.log( d3.deviation( array6 ) ); // 标準內插補點 2.9640705601780613
let array7 = [ 7, 8, 6, 7, 7, 8, 8, 7 ];
console.log( d3.mean( array7 ) ); // 平均值 7.25
console.log( d3.variance( array7 ) ); // 方內插補點 0.4999999999999999
console.log( d3.deviation( array7 ) ); // 标準內插補點 0.7071067811865475
// 在數組指定位置插入指定項
// 先回顧js的splice()方法是怎樣删除和插入數組指定項的
let array8 = [ 'nitx', 'nz', 'sxm' ];
// 在數組索引為2的位置,删除0項,插入字元串hx
array8.splice( 2, 0, 'hx' );
console.log( array8 ); // ["nitx", "nz", "hx", "sxm"]
// 在數組索引為3的位置,删除1項,并插入字元串zn和xm
array8.splice( 3, 1, "zn", 'xm' );
console.log( array8 ); // ["nitx", "nz", "hx", "zn", "xm"]
// 在d3中,擷取指定位置可以使用如 bisectLeft() 這樣的方法:
let array9 = [ 20, 9, 16, 7 ];
// bisectLeft()所用的數組必須經過遞增排序,第二個參數用于指定某項的位置,如果此項在數組中存在,則傳回此位置的左邊。如果此項在數組中不存在,則傳回第一個大于此項的值的左邊。
let iLeft = d3.bisectLeft( array9.sort( d3.ascending ), 16 );
console.log( iLeft ); // 2
// 在iLeft位置處删除0項,插入項 11
array9.splice( iLeft, 0, 11 );
console.log( array9 ); // [7, 9, 11, 16, 20]
複制
⑤.操作數組
d3還提供了對數組進行洗牌、合并等操作:
-
:随機排列數組,将數組作為參數使用後,能将數組随機排列。d3.shuffle( array[, lo[, hi]] )
-
:合并兩個數組d3.merge( arrays )
-
:傳回鄰接的數組對,以第i項和第i-1項為對傳回。使用本方法後,原數組 array 不變。d3.pairs( array )
-
:傳回等差數列,如果stop為正,則最後的值小于stop;如果stop為負,則最後的值大于stop。start和step如果省略,則預設值是0和1.range在生成數組時經常使用。d3.range( [start ,] stop[, step] )
-
:根據指定的索引号數組傳回排列後的數組,原數組不變,結果儲存在傳回值中。要注意數組索引号從0開始,如有超出範圍的索引号,該位置會以undefined代替。d3.permute( array, indexes )
-
:用多個數組來生成元素為數組的數組。參數是任意多個數組,輸出的是一個數組。d3.zip( arrays... )
-
:求轉置矩陣,即将矩陣的行轉為列,得到的矩陣即為轉置矩陣。d3.transpose( matrix )
以上方法的代碼示例:
// 重新随機排列數組
let array1 = [10, 23, 'nitx', 99, 0];
console.log( d3.shuffle( array1 ) ); // 結果随機,本次執行結果為 [0, 10, 23, 99, "nitx"]
// 合并兩個數組
let mergeArr = d3.merge( [ [10, 2], [22, 0, 7] ] );
console.log( mergeArr ); // [10, 2, 22, 0, 7]
// 傳回鄰接的數組對
let array3 = [ 'nitx', 'sxm', 'hx', 'nz', 'zn' ];
let pairs = d3.pairs( array3 );
console.log( pairs );
/** 原數組 array3 值不變
* [
* ["nitx", "sxm"],
* ["sxm", "hx"],
* ["hx", "nz"],
* ["nz", "zn"]
* ]
*/
// 傳回等差數列
// start為0,stop為10,step為1
console.log( d3.range( 10 ) ); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
// start為2,stop為10,step為1
console.log( d3.range( 2, 10 ) ); // [2, 3, 4, 5, 6, 7, 8, 9]
// start為2,stop為10,step為2
console.log( d3.range( 2, 10, 2 ) ); // [2, 4, 6, 8]
// 根據指定的索引号數組傳回排列後的數組
let array4 = [ 'nitx', 'hx', 'sxm' ];
// 索引數組是 [2, 0, 1],将數組array4根據索引數組指定的順序重排,不影響原數組array4,結果儲存在變量newArr中。
let newArr = d3.permute( array4, [2, 0, 1] );
console.log( newArr ); // ["sxm", "nitx", "hx"]
// zip()
let zip = d3.zip(
[ 10, 99, 76 ],
[ 'nitx', 'sxm', 'hx' ],
[ true, false, true ]
)
console.log( zip );
/**
* [
* [10, "nitx", true],
* [99, "sxm", false],
* [76, "hx", true]
* ]
*/
// zip()方法最典型的用法是可以用來求向量的積
let a = [ 10, 20, 5 ];
let b = [ -5, 10, 3 ];
console.log( d3.zip( a, b ) );
let ab = d3.sum( d3.zip( a, b ), function( d ){
return d[0] * d[1];
} )
console.log( ab ); // 165
/**
* 上例向量積代碼解釋:
* d3.zip( a, b ) => 得到結果: [ [10, -5], [20, 10], [5, 3] ]
* 然後結果首先被accessor函數處理,得到結果 => [ -50, 200, 15 ]
* 最後被d3.sum()求和,結果即為向量a和b的内積。
*/
// 轉置矩陣
let c = [ [1, 2, 3], [4, 5, 6] ];
// 轉置後,原數組不變,結果儲存在傳回值指派給變量t
let t = d3.transpose( c );
console.log( t ); // [ [1, 4], [2, 5], [3, 6] ]
複制
映射 map
映射(Map)由一系列鍵(Key)和值(value)構成的常見資料結構。每個key對應一個value,根據key可以擷取和設定對應value。在js中,map類似于對象,但相對對象的鍵隻接受字元串作為鍵名,map的鍵名則可以使用任何類型的值,是一種更完善的hash結構。
-
用于建構map映射。可選參數object是源數組,可選參數key是用于指定映射的key。d3.map( [object][, key] )
-
:指定key存在時傳回true,否則傳回false。map.has( key )
-
:指定key存在則傳回該key對應的value,否則傳回falsemap.get( key )
-
: 對指定key設定value,如該key存在,則新value覆寫舊value。map.set( key, value )
-
:如果指定的key存在,則将此key和對應value删除,如不存在則傳回falsemap.remove( key )
-
:以數組形式傳回map的所有keymap.keys()
-
:以數組形式傳回map的所有valuemap.values()
-
:以數組形式傳回map所有的key和valuemap.entries()
-
:如果映射為空,傳回true;否則傳回falsemap.empty()
-
:傳回映射的大小map.size()
let dataset = [
{ id: 1000, name: 'nitx' },
{ id: 1001, name: 'hx' },
{ id: 1002, name: 'sxm' }
]
// 以數組dataset建構映射,并以其中各項的id為鍵,當然也可以指定别的不相關的值,不一定要使用id
let map = d3.map( dataset, function( d ){
return d.id;
} )
map.has( 1001 ); //true
map.has( 1003 ); //false
console.log( map.get( 1001 ) ); // {id: 1001, name: "hx"}
console.log( map.get( 1003 ) ); // undefined
// 将鍵key的值新設
map.set( 1001, { id: 1001, name: 'nz' } );
console.log( map );
// 将鍵key的值新設
map.set( 1003, { id: 1003, name: 'hx' } );
console.log( map );
// 删除key為1001的鍵和值
map.remove( 1001 );
console.log( map );
// 傳回所有鍵組成的數組
console.log( map.keys() ); // ["1000", "1002", "1003"]
// 傳回所有值組成的數組
console.log( map.values() ); // [ {id: 1000, name: "nitx"}, {id: 1002, name: "sxm"}, {id: 1003, name: "hx"} ]
// 傳回所有的鍵和值
console.log( map.entries() );
/**
* [
* {key: "1000", value: {id: 1000, name: "nitx"}},
* {key: "1002", value: {id: 1002, name: "sxm"}},
* {key: "1003", value: {id: 1003, name: "hx"}}
* ]
*/
console.log( map.empty() ); // false
console.log( map.size() ); // 3
複制
集合 set
集合類似于數組,但是成員的值都是唯一的,沒有重複的值。
-
:使用數組來建構集合,如果集合中有重複的值,則隻添加其中一項。d3.set( [array] )
-
:如果集合中有指定元素,傳回true,否則傳回false。set.has( value )
-
:如果集合中沒有指定元素,則将其添加到集合中,否則就不添加set.add( value )
-
:如果集合中有指定元素,則将其删除并傳回true,否則傳回falseset.remove( value )
-
:以數組形式傳回集合中的所有元素set.values()
-
:如果該集合為空,傳回true;否則傳回falseset.empty()
-
:傳回該集合的大小set.size()
嵌套結構 nest
嵌套結構可以使用鍵對數組中的大量對象進行分類,多個鍵一層套一層,使得分類越來越具體,索引越來越友善。
例如要在幾千個職員資料中查找其中一個職員的資訊,但隻知道其出生地和年齡分别是北京和22歲,一般這樣查比較簡單:先查找在北京的職員,再在其中查找22歲的職員。如此可一步步縮小查找範圍。那麼出生地和年齡就能作為嵌套結構的鍵。
代碼示例:
let persons = [
{ id: 1000, name: '倪某某', year: '1988', hometown: '北京' },
{ id: 1001, name: '黃某某', year: '1988', hometown: '無錫' },
{ id: 1002, name: '沈某某', year: '1987', hometown: '上海' },
{ id: 1003, name: '趙某某', year: '1984', hometown: '廣州' },
{ id: 1004, name: '胡某某', year: '1987', hometown: '上海' }
]
let nest = d3.nest()
// 将year作為第一個鍵
.key( function( d ){ return d.year } )
// 将hometown作為第二個鍵
.key( function( d ){ return d.hometown } )
// 指定将應用嵌套結構的數組是persons
.entries( persons );
console.log( nest );
let persons = [
{ id: 1000, name: '倪某某', year: '1988', hometown: '北京' },
{ id: 1001, name: '黃某某', year: '1988', hometown: '無錫' },
{ id: 1002, name: '沈某某', year: '1987', hometown: '上海' },
{ id: 1003, name: '趙某某', year: '1984', hometown: '廣州' },
{ id: 1004, name: '胡某某', year: '1987', hometown: '上海' }
]
let nest = d3.nest()
// 将year作為第一個鍵
.key( function( d ){ return d.year } )
.sortKeys( d3.ascending ) // 将鍵名year按照遞增排序
// 将hometown作為第二個鍵
.key( function( d ){ return d.hometown } )
// 指定将應用嵌套結構的數組是persons
.entries( persons );
console.log( nest );
/**
* 嵌套資料結構
* [
* {
* key: "1984",
* values: [
* {
* key: "廣州",
* values: [ { id: 1003, name: '趙某某', year: '1984', hometown: '廣州' } ]
* }
* ]
* },
* {
* key: "1987",
* values: [
* {
* key: "上海",
* values: [
* { id:1002, name: "沈某某", year: "1987", hometown: "上海" },
* { id:1004, name: "胡某某", year: "1987", hometown: "上海" }
* ]
* }
* ]
* },
* {
* key: "1988",
* values: [
* {
* key: "北京",
* values: [ { id:1000, name: "倪某某", year: "1988", hometown: "北京" } ]
* },
* {
* key: "無錫",
* values: [ { id:1001, name: "黃某某", year: "1988", hometown: "無錫" } ]
* }
* ]
* },
* ]
*/
複制
以上代碼使用了如下方法:
-
:該函數沒有任何參數,表示接下來會建構一個嵌套結構,其他函數需要跟在此函數之後使用d3.nest()
-
:指定嵌套結構的鍵nest.key( fn )
-
:指定數組array将被用于建構嵌套結構nest.entries( array )
-
:按照鍵對嵌套結構進行排序,接在nest.sortKeys( comparator )
後使用nest.key()
-
:按照值對嵌套結構進行排序nest.sortValues( comparator )
-
:對每組葉子節點調用指定函數fn,該函數有一個參數values,是目前葉子節點的數組nest.rollup( fn )
-
:以映射形式輸出數組nest.map( array[, mapType] )
柱狀圖
①.制作柱狀圖
制作一個柱狀圖,并配上相應的文字。代碼示例如下:
<body></body>
<script>
import * as d3 from "d3";
// 定義表示每個柱狀矩形長短的數組
// 數組長度表示柱狀矩形的個數,數組項值表示柱狀矩形的高度,機關為px
let dataset = [ 50, 43, 120, 87, 99, 167, 142 ];
// 定義寬高變量
let width = 400, height= 400;
// 1.定義SVG畫布
let svg = d3.select( "body" ) // 選擇body元素
.append( "svg" ) // 添加svg元素
.attr( "width", width ) // 定義svg畫布的寬度
.attr( "height", height ) // 定義svg畫布的高度
.style( "background-color", "#e5e5e5" )
// 2.定義柱狀矩形
// 定義svg内邊距
let padding = { top: 20, right: 20, bottom: 20, left: 20 };
// 定義矩形所占寬度(包括空白處),表示前一柱狀矩形開始位置到後一個柱狀矩形開始位置的矩形,此部分包含一段空白,它是為和後一個柱狀矩形做區分。
let rectStep = 35;
// 定義矩形所占寬度(不包括空白),表示柱狀矩形實際所占的寬度,此部分是要填充顔色的
let rectWidth = 30;
let rect = svg.selectAll( "rect" ) // 擷取空選擇集
.data( dataset ) // 綁定資料
.enter() // 擷取enter部分,因為此時頁面上其實是沒有rect元素的,擷取的是空選擇集,此時就要在enter部分上進行操作
.append( "rect" ) // 根據資料個數插入相應數量的rect元素
.attr( "fill", "#377ade" ) // 設定每個柱狀矩形的填充顔色為 steelblue
.attr( "x", function( d, i ){ // 設定每個柱狀矩形的x坐标
return padding.left + i*rectStep;
} )
.attr( "y", function( d, i ){ // 設定每個柱狀矩形的y坐标
return height - padding.bottom - d;
} )
.attr( "width", rectWidth ) // 設定每個柱狀矩形的寬度
.attr( "height", function( d, i ){ // 設定每個柱狀矩形的高度
return d;
} )
// 3.為柱狀矩形添加标簽文字
let text = svg.selectAll( "text" ) // 擷取空選擇集
.data( dataset ) // 綁定資料
.enter() // 擷取enter部分
.append( "text" ) // 為每個資料添加對應的text元素
.attr( "fill", "#fff" )
.attr( "font-size", "14px" )
.attr( "text-anchor", "middle" ) // 文本錨點屬性,中間對齊
.attr( "x", function( d, i ){
return padding.left + i*rectStep;
} )
.attr( "y", function( d, i ){
return height - padding.bottom - d;
} )
.attr( "dx", rectWidth/2 )
.attr( "dy", "1em" )
.text( function( d ){
return d;
} )
// 為svg添加标題
svg.append( "text" )
.attr( "fill", "#000" )
.attr( "textLength", 60 )
.style( "font-size", "16px" )
.attr( "x", function(){
return 160;
} )
.attr( "y", function(){
return 30;
} )
.text( function(){
return "柱狀圖"
} )
</script>
複制
結果截圖:
外鍊圖檔轉存失敗(img-xA5IJ6nu-1568253451788)(https://github.com/nitxs/public_docs/blob/master/image_hosting/19/190522_1.png?raw=true)
其中文字
<text>
的
text-anchor
屬性需要特地講下,它有三個值:
start
、
middle
、
end
。由于文本設定的
x、y、dx、dy
這幾個屬性,是以按坐标軸原點來了解,
(x+dx, y+dy)
就是文字的起始位置,
start
值表示文字的第一個字元位于起始位置的右方;
middle
值表示文字的中心位于起始位置;
end
值表示文字的最後一個字元靠近起始位置。
②.更新資料
常見需求:當執行資料排序、增加删除等更新資料操作時柱狀圖也會發生改變。代碼示例如下:
<body>
<button type="button" id="sortBtn">重排</button>
<button type="button" id="addBtn">新增</button>
<button type="button" id="removeBtn">删除</button>
</body>
<script>
import * as d3 from "d3";
// 定義表示每個柱狀矩形長短的數組
// 數組長度表示柱狀矩形的個數,數組項值表示柱狀矩形的高度,機關為px
let dataset = [ 50, 43, 120, 87, 99, 167, 142 ];
// 定義寬高變量
let width = 400, height= 400;
// 定義svg内邊距
let padding = { top: 20, right: 20, bottom: 20, left: 20 };
// 定義矩形所占寬度(包括空白處),表示前一柱狀矩形開始位置到後一個柱狀矩形開始位置的矩形,此部分包含一段空白,它是為和後一個柱狀矩形做區分。
let rectStep = 35;
// 定義矩形所占寬度(不包括空白),表示柱狀矩形實際所占的寬度,此部分是要填充顔色的
let rectWidth = 30;
let svg = d3.select( "body" ) // 選擇body元素
.append( "svg" ) // 添加svg元素
.attr( "width", width ) // 定義svg畫布的寬度
.attr( "height", height ) // 定義svg畫布的高度
.style( "background-color", "#e5e5e5" )
// 預設繪制柱狀圖
draw();
/**
* 繪制柱狀圖方法
* 分别繪制矩形和文字,并定義各自的屬性
* 更新使用到updata部分
* 新增使用到enter部分
* 删除使用到exit部分
*/
function draw(){
// 擷取矩形的updata部分
let updataRect = svg.selectAll( "rect" ) // 擷取空選擇集
.data( dataset ) // 綁定資料
// 擷取矩形的enter部分
let enterRect = updataRect.enter(); // 擷取enter部分,因為此時頁面上其實是沒有rect元素的,擷取的是空選擇集,此時就要在enter部分上進行操作
// 擷取矩形的exit部分
let exitRect = updataRect.exit();
// 矩形的updata部分的處理方法
updataRect.attr( "fill", "#377ade" )
.attr( "x", function( d, i ){ // 設定每個柱狀矩形的x坐标
return padding.left + i*rectStep;
} )
.attr( "y", function( d, i ){ // 設定每個柱狀矩形的y坐标
return height - padding.bottom - d;
} )
.attr( "width", rectWidth ) // 設定每個柱狀矩形的寬度
.attr( "height", function( d ){ // 設定每個柱狀矩形的高度
return d;
} )
// 矩形的enter部分的處理方法
enterRect.append( "rect" )
.attr( "fill", "#377ade" )
.attr( "x", function( d, i ){
return padding.left + i*rectStep;
} )
.attr( "y", function( d, i ){
return height - padding.bottom - d;
} )
.attr( "width", rectWidth )
.attr( "height", function( d ){
return d;
} )
// 矩形的exit部分的處理方法
exitRect.remove();
// 擷取文字的updata部分
let updataText = svg.selectAll( "text" )
.data( dataset )
// 擷取文字的enter部分
let enterText = updataText.enter();
// 擷取文字的exit部分
let exitText = updataText.exit();
// 文字的updata部分的處理方法
updataText.attr( "fill", "#fff" )
.attr( "font-size", "14px" )
.attr( "text-anchor", "middle" ) // 文本錨點屬性,中間對齊
.attr( "x", function( d, i ){
return padding.left + i*rectStep;
} )
.attr( "y", function( d, i ){
return height - padding.bottom - d;
} )
.attr( "dx", rectWidth/2 )
.attr( "dy", "1em" )
.text( function( d ){
return d;
} )
// 文字的enter部分的處理方法
enterText.append( "text" )
.attr( "fill", "#fff" )
.attr( "font-size", "14px" )
.attr( "text-anchor", "middle" ) // 文本錨點屬性,中間對齊
.attr( "x", function( d, i ){
return padding.left + i*rectStep;
} )
.attr( "y", function( d, i ){
return height - padding.bottom - d;
} )
.attr( "dx", rectWidth/2 )
.attr( "dy", "1em" )
.text( function( d ){
return d;
} )
// 文字的exit部分的處理方法
exitText.remove();
}
let sortBtn = document.getElementById( "sortBtn" );
let addBtn = document.getElementById( "addBtn" );
let removeBtn = document.getElementById( "removeBtn" );
// 遞增重排
sortBtn.onclick = function( e ){
// 将資料按遞增規則排序
dataset.sort( d3.ascending );
draw();
}
// 增加柱狀資料
addBtn.onclick = function( e ){
dataset.push( Math.floor( Math.random() * 100 ) );
draw();
}
// 删除最後一個柱狀資料
removeBtn.onclick = function( e ){
dataset.pop();
draw();
}
</script>
複制
效果截圖:
外鍊圖檔轉存失敗(img-W0kTwbNy-1568253451789)(https://github.com/nitxs/public_docs/blob/master/image_hosting/19/190522_2.png?raw=true)