D3.js dataの使い方 - Enter, Update, Exit

D3の基本とdataの使い方を紹介する。

基本的な使い方

基本的な使い方

datasetを次のようにすると、<div>要素にデータが割り当てられる。

var dataset = [1, 2, 3, 4, 5, 6];
var div = d3.selectAll("div")
  .data(dataset);

たとえば、<div>要素がない状態でstyletextを指定すると、

d3.selectAll("div")
  .data(dataset)
  .enter()
  .append("div")
  .style("color", "red")
  .text(
    function (d) {
      return "this data is " + d;
    }
  );

次のようにデータ数分の<div>要素が生成される。

コードを実行

既存要素に追加する

次のように<div>要素がすでに2つあるとする。

コードを実行

ここに次のように新しい要素を追加すると、新しく生成された要素が更新される。

d3.select("#view")
  .selectAll("div")
  .data(dataset)
  .enter()
  .append("div")
  .style("color", "red")
  .text(
    function (d) {
      return "this data is " + d
    }
  );

コードを実行

新しく生成された部分をenter領域、すでにある部分をupdate領域という。

update領域のデータを更新する

このupdate領域にstyleを適用するには、次のようにする。

d3.selectAll("div")
  .data(dataset)
  .style("color", "blue")
  .text(
    function (d) {
      return "this data is " + d;
    }
  );

コードを実行

update領域とenter領域のスタイルを同時に変更する

update領域とenter領域のスタイルを同時に変更したい場合は多い。その場合は、次のようにまとめて変更できる。

var div = d3.selectAll("div")
  .data(dataset);

var divEnter = div.enter()
  .append("div");

var divUpdate = divEnter.merge(div);
divUpdate.style("color", "blue")
  .text(
    function (d) {
      return "this data is " + d
    }
  );

コードを実行

exitを使用して余分な要素領域を指定する

一方、すでに上のように6つの<div>要素があり、割り当てられるデータが少ない場合は、exitを使用して余分な要素領域を指定できる。

dataset = ['A', 'B'];
var div = d3.selectAll("div")
  .data(dataset)
  .style("color", "red")
  .text(function(d){return "this data is " + d;});

div.exit()
  .style("color", "green");

コードを実行

exit領域のデータを削除する

exit領域のデータは削除することが多いため、次のように削除すればよい。

div.exit().remove();

コードを実行

Enter selection, Exit selection

D3の最も重要な概念であるUpdate、Enter、Exitについて見ていく。

Enter selection

まず、次のHTMLでenter selectionについて説明する。

<div class="fruitlist">
    <div class="fruit">Apple</div>
    <div class="fruit">Banana</div>
    <div class="fruit">Cherry</div>
</div>

data()dataを割り当てた後、DOM要素よりdataの数が多い場合、dataの数が多い部分をenter selectionと呼ぶ。

enter()でenter selectionに要素を追加できる。append()でデータを追加するには、selection.selectAll()メソッドを使用して親要素から選択する必要がある。

d3.select(".fruitlist")
  .selectAll(".fruit")
  .data([1, 2, 3, 4, 5])
  .enter()
  .append("div")
  .text(d => `Extra fruit number ${d}`)

コードを実行

Exit selection

次に、次のHTMLでExit selectionについて見ていく。

<div>
    <div class="lunch">Burger</div>
    <div class="lunch">Hotdog</div>
    <div class="lunch">Curry</div>
    <div class="lunch">Pasta</div>
    <div class="lunch">Pizza</div>
</div>

DOM要素よりdataの数が少ない場合、dataの数が少ない部分をexit selectionと呼ぶ。exit()でexit selectionを削除できる。

d3.selectAll(".lunch")
  .data([1, 2, 3])
  .exit()
  .remove()

コードを実行

Update, Enter, Exitを使用して必要な場合だけDOMを更新する

D3チュートリアルを見ていると、存在しない要素をselectAll()した後にenter()を実行し、append()するパターンが突然出てきて戸惑うことがある。

存在しない要素に対してselectAll()を行う理由は、データ更新時に前回のDOM要素を再利用するためである。つまり、最初は存在しない要素をselectAll()するが、2回目には存在していることになる。このパターンを使ってみる。

この例では、div要素は最初は空である。

<style>
 .chart div {
     background-color: powderblue;
     text-align: right;
     margin: 1px;
     transition: width 0.5s ease-in-out;
     animation: fade-in 0.5s ease 0s 1 normal;
 }
 @keyframes fade-in {
     0% { opacity: 0 }
     100% {opacity: 1 }
 }
</style>
<div class="chart"/>

データの更新がわかりやすいように、簡単なアニメーションを付けている。次のコードでランダムにデータを更新する。

function barchart(data) {
    const chart = d3.select(".chart")
                    .selectAll("div")
                    .data(data)
                    .style("width", function(d) { return d + "px"; })
                    .text( d => d);

    chart.enter()
                  .append("div")
                  .style("width", function(d) { return d + "px"; })
                  .text( d => d)

    chart.exit().remove();
}

function updatebar() {
    ndata = Math.floor(Math.random() * 5) + 5; // [5, 10)
    data = d3.range(0, ndata).map(() => Math.floor(Math.random() * 300));
    barchart(data);
}

setInterval(updatebar, 1000);