d3js selections深入理解

世有因果知因求果發表於2017-07-16

D3 selections選擇DOM元素以便可以對這些dom元素做相應的操作,比如:更改其style,修改其屬性,執行data-join操作,或者插入、刪除相應elements

比如,如果給定5個circles:

我們可以使用d3.selectAll來選中所有的circles,並且通過.style和.attr來修改其樣式或者屬性

d3.selectAll('circle')
  .style('fill', 'orange')
  .attr('r', function() {
    return 10 + Math.random() * 40;
  });

 

Making selections

D3有兩個函式來執行選擇操作:d3.select和d3.selectAll

d3.select進選中第一個匹配的元素。而d3.selectAll則選中所有匹配的元素。這兩個函式都接收一個引數來指定選擇器字串:和css選擇器相同語法 selector string.

比如我們使用css class類名來選擇

d3.selectAll('.item').

Modifying elements

一旦我們完成了選擇得到了selection,則我們可以使用selection的以下函式:

NameBehaviourExample
.style Update the style d3.selectAll('circle').style('fill', 'red')
.attr Update an attribute d3.selectAll('rect').attr('width', 10)
.classed Add/remove a class attribute d3.select('.item').classed('selected', true)
.property Update an element's property d3.selectAll('.checkbox').property('checked', false)
.text Update the text content d3.select('div.title').text('My new book')
.html Change the html content d3.select('.legend').html('<div class="block"></div><div>0 - 10</div>')

需要注意的是,無論是select還是selectAll返回的selection,執行上面的操作都將對所有選中的元素產生影響。

Updating selections with functions

除了傳入常量值到對應attr,style函式外,我們也可以傳入一個函式,,該函式輸出值作為attr, style最終設定的值:

d3.selectAll('circle')
  .attr('cx', function(d, i) {
    return i * 100;
  });

該函式將自動接收(d,i).  d 是joined在該元素上的資料 (參考 data joins section) 而 i 則是該元素在selection中對應的index

如果我們希望根據其在selection中的index來更改selection中的元素對應的x軸位置,比如我們希望水平佈置rect元素在不同的位置(而不是堆砌在一起):

d3.selectAll('rect')
  .attr('x', function(d, i) {
    return i * 40;
  });

大多數情況下,傳入一個匿名函式就好,但是我們也可以使用命名的

function positionRects(d, i) {
  return i * 40;
}

d3.selectAll('rect')
  .attr('x', 

Handling events

我們可以通過selection.on()來為selection中的elements新增事件處理函式,而這個事件處理函式,同樣地會傳入d和i兩個引數以方便處理。同樣地,d是本元素joined data,i則是本元素在selection中的index值,下面是列出的主要event時間

 

Event nameDescription
click Element has been clicked
mouseenter Mouse pointer has moved onto the element
mouseover Mouse pointer has moved onto the element or its children
mouseleave Mouse pointer has moved off the element
mouseout Mouse pointer has moved off the element or its children
mousemove Mouse pointer has moved over the element

比如下面我們寫一個小的程式碼,實現當點選後就修改其text指明是第幾個元素被點選

d3.selectAll('circle')
  .on('click', function(d, i) {
    d3.select('.status')
      .text('You clicked on circle ' + i);
  });

需要注意的是,在事件回撥函式中,this的值是DOM 元素本身,和selection無關!如果要使用d3對該dom操作,必須先做select操作:

d3.selectAll('circle')
  .on('click', function(d, i) {
    d3.select(this)  // this指被點選的dom元素,而不是d3 selection!
      .style('fill', 'orange');
  });

Inserting and removing elements

我們看一個例子:3個g元素,每個g都包含了一個circle:

<g class="item" transform="translate(0, 0)">
  <circle r="40" />
</g>
<g class="item" transform="translate(120, 0)">
  <circle r="40" />
</g>
<g class="item" transform="translate(240, 0)">
  <circle r="40" />
</g>

我們可以插入一個 text element到每個g元素中:

d3.selectAll('g.item')
  .append('text')
  .text(function(d, i) {
    return i + 1;
  });

最終的結果是在每個g.item元素中追加了一個text元素,其text值為對應i的值:

<g class="item" transform="translate(0, 0)">
  <circle r="40" />
  <text>1</text>
</g>
<g class="item" transform="translate(120, 0)">
  <circle r="40" />
  <text>2</text>
</g>
<g class="item" transform="translate(240, 0)">
  <circle r="40" />
  <text>3</text>
</g>

.remove 刪除selection中的元素:

d3.selectAll('circle')
  .remove();

Chaining

絕大部分selection的方法都仍然返回selection本身,因此這意味著我們可以鏈式方法繼續呼叫其他的方法:

d3.selectAll('circle')
  .style('fill', 'orange')
  .attr('r', 20)
  .on('click', function(d, i) {
    d3.select('.status')
      .text('You clicked on circle ' + i);
  });

Each and call

.each 允許對每個selection中的每個元素執行一段函式功能(同樣傳入d,i, this引數可以使用), .call 則允許對 selection itself. 執行相應的函式功能

看下面的例子:

function addNumberedCircle(d, i) {
  d3.select(this)
    .append('circle')
    .attr('r', 40);

  d3.select(this)
    .append('text')
    .text(i + 1)
    .attr('y', 50)
    .attr('x', 30);
}

d3.selectAll('g.item')
  .each(addNumberedCircle);

再看一個.each 呼叫的例子

   d3.selectAll('circle')

  .each(function(d, i) {
    var odd = i % 2 === 1;

    d3.select(this)
      .style('fill', odd ? 'orange' : '#ddd')  // 對奇偶元素使用不用的顏色和半徑大小
      .attr('r', odd ? 40 : 20);
  });

而.call則傳入selection本身,這實際上是javascript本身的特性!

function addNumberedCircle(selection) {
  selection
    .append('circle')
    .attr('r', 40);

  selection
    .append('text')
    .text(function(d, i) {
      return i + 1;
    })
    .attr('y', 50)
    .attr('x', 30);
}

d3.selectAll('g.item')
  .call(addNumberedCircle);

 

Filtering and sorting selections

We can filter a selection using .filter. A function is usually passed into .filter which returns true if the element should be included. .filter returns the filtered selection.

我們也可以使用selection的.filter方法來過濾對應selection中的元素。通常我們通過傳入一個函式(該函式同樣具有d,i,this可以訪問)給到.filter方法來告訴d3,如果該函式返回true則該i元素將被包含住,如果返回false,則不被包含在filter的返回selection中,而.filter則返回filtered selection.

比如下面的例子中我們對偶數的元素來著色為orange色,奇數元素作色為藍色:

d3.selectAll('circle')
  .filter(function(d, i) { // filter返回偶數的元素
    return i % 2 === 0;
  })
  .style('fill', 'orange');// 偶數元素作色為橘色
d3.selectAll('circle')
  .filter(function(d, i) { // filter返回奇數的元素
    return i % 2 === 1;
  })
  .style('fill', 'blue'); // 奇數元素作色為藍色

.sort()排序只有在有joined資料時才有意義。我們可以傳入一個比較函式來呼叫.sort對selection中的元素執行排序操作。比較函式本身有兩個引數,通常為a,b分別代表了被比較的兩個元素上繫結的datam.如果比較函式返回負值,那麼a將在b之前,如果是正,則a放在b之後。

下面假定selection上有以下繫結的資料:

[
{"name":"Andy","score":37},
{"name":"Beth","score":39},
{"name":"Craig","score":31},
{"name":"Diane","score":35},
{"name":"Evelyn","score":38}
]

 

  d3.selectAll('.person')
    .sort(function(a, b) {
      return b.score - a.score;
    });
myData = [
  {
    "name": "Andy",
    "score": 37
  },
  {
    "name": "Beth",
    "score": 39
  },
  {
    "name": "Craig",
    "score": 31
  },
  {
    "name": "Diane",
    "score": 35
  },
  {
    "name": "Evelyn",
    "score": 38
  }
];

var barWidth = 400;
var barScale = d3.scaleLinear().domain([0, 100]).range([0, barWidth]);

var u = d3.select('#wrapper')
  .selectAll('.person')
  .data(myData);

var entering = u.enter()
  .append('div')
  .classed('person', true);

entering.append('div')
  .classed('label', true)
  .text(function(d) {
    return d.name;
  });

entering.append('div')
  .classed('bar', true)
  .style('width', function(d) {
    return barScale(d.score) + 'px';
  });

function sort() {
  d3.selectAll('.person')
    .sort(function(a, b) {
      return b.score - a.score;
    });
}