D3jsのselectionとdata joinを実験する

# 概要
SVGを表示するために [D3js](https://d3js.org/) を利用したいのだが、JSコード上で動的に操作するデータに対応するSVGツリーの構築方法が特徴的だったので実験してみた。

JS 上でデータ配列を操作したときに、データ配列の各要素に対応する DOM element を追加、更新、削除する。（最終的には SVG の要素の操作が目的だが、ここではシンプルに HTML DOM 要素を用いて実験した）

実験内容は、[Codepen](https://codepen.io/ken_miyashita/pen/xxdBBbb?editors=1001) で実行することができる。

# 構成
- D3js (ver4)

# 既存 DOM element 集合を見つける： selectAll()

参照： [D3.js - selections](https://www.tutorialspoint.com/d3js/d3js_selections.htm)

これは jQuery と基本的には同じ考え方。`selectAll()` で指定した条件を満たす DOM element 集合が手に入る。

```html
<ul id="list">
  <li>initial element 0</li>
  <li>initial element 1</li>
</ul>
```
に対して

```javascript
d3.select("#list").selectAll("li");
```
を実行すると、以下のような selection が構築される。

<img width=400 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861827/a418ca1a-88e7-31a9-cdcf-002a78274c8b.png">

画面上の様子：
<img width=300 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861827/d942e423-b168-e62b-1e0f-f2197e5f21b9.png">

DOM ツリーの様子：
<img width=700 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861827/135ba5b2-b2f4-31c6-1880-bdec7bd781fc.png">


# 既存 DOM element と data を対応させる： data()

selection が指す DOM element 配列にデータ配列を対応させるためには、`data()` を利用する。まずシンプルに以下のように記述すると、DOM element 配列とデータ配列の要素が配列要素順に従って対応する。この場合、データ配列の要素数の方が多いので、対応する DOM element が存在しないデータ配列要素が残る。

```javascript
d3.select("#list").selectAll("li")
  .data([10, 20, 30, 40, 50]);
```

<img width=500 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861827/815ddabf-35db-88e3-2f47-d73a2c3697c5.png">

この selection に含まれる各 DOM element の内容を更新するには、`text()` などの DOM element を操作するメソッドを利用する。
以下の例では、selection に含まれる各 DOM element (&lt;li&gt; element) のテキストを、対応するデータを利用して更新している。

```javascript
d3.select("#list").selectAll("li")
   .data([10, 20, 30, 40, 50])
   .text(function(d) {
       return "This is pre-existing element and the value is " + d;
     });
```

画面上の様子：
<img width=500 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861827/b612ff41-2e4a-d301-1f1e-e568552b5e81.png">

DOM ツリーの様子：
<img width=600 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861827/856e4e99-7969-62e0-cede-39d3925ec7d3.png">

# 新規 DOM element を data に対応して作成する：enter(), append()

先の例では、データ配列の方が要素数が多く、後半3つのデータに対しては DOM element が存在しなかった。ここでは、それらのデータに対して新規の DOM element を挿入する。

`enter()` は、selection が指すデータ配列の要素のうち対応する DOM element がないものに対して、仮の DOM element (placeholder) を作り、それらを指す新しい selection を返す。

```javascript
d3.select("#list").selectAll("li")
   .data([10, 20, 30, 40, 50])
   .text(function(d) {
       return "This is pre-existing element and the value is " + d;
     })
   .enter();
```

<img width=700 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861827/88bbd140-dc04-752a-d672-67111d0cca09.png">

そして、これらの placeholder を指す selection に対して `append()` を適用すると新規 DOM element を挿入することができる。

```javascript
d3.select("#list").selectAll("li")
   .data([10, 20, 30, 40, 50])
   .text(function(d) {
       return "This is pre-existing element and the value is " + d;
     })
   .enter()
   .append("li")
   .text(function(d) {
       return "This is dynamically created element and the value is " + d;
     });
```

<img width=700 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861827/37479aa0-bb67-eb6b-93c2-8803628822c0.png">

画面上の様子：
<img width=600 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861827/237cda59-ea86-aa31-1934-97e208682b52.png">


DOM ツリーの様子：
<img width=600 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861827/0192fba6-5591-7971-5281-a3158f31483f.png">

## 配列末尾の DOM element を削除する：exit(), remove()

ここで、もう一度 `selectAll()` を実行すると、5つの &lt;li&gt; が見つかる。さらに要素3つのデータ配列を `data()` で対応させてみる。

```javascript
d3.select("#list").selectAll("li")
   .data([10, 20, 40]);
```

<img width=700 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861827/af6cb840-d10b-6bea-b93e-dcacb78307a4.png">


`exit()` は、`enter()` の逆にあたり、対応するデータが存在しない DOM element 群をさす selection を作成する。
そして `remove()` は、selection が指す DOM element を削除する。

ここで注意が必要なのは、DOM element 配列とデータ配列の対応は配列要素の順番で決まっていること。データ配列の中から3つ目の要素（"30") を削除したからと言って、対応する（と人間が思っている）&lt;li&gt; 要素 ("This is dynamically created element and the value is 30"）が削除されるわけではない。

```javascript
d3.select("#list").selectAll("li")
   .data([10, 20, 40])
   .exit()
   .remove();
```

<img width=600 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861827/89317185-797d-2483-2ee7-68dac2d32256.png">

画面上の様子：
<img width=600 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861827/64a2d289-04c3-756a-cd18-b998d2d944d8.png">

DOM ツリーの様子：
<img width=600 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861827/7ce23b87-e337-c0e9-c35b-9035573c39a0.png">

# データに対応する DOM element を削除する：attr(), data()

参照：[selection.data()](https://github.com/d3/d3-selection/blob/v3.0.0/README.md#selection_data)

データに対応する DOM element を削除するためには、そもそも「対応」を記憶しておく必要がある。そのため、`attr()` を利用する。

```javascript
d3.select("#list").selectAll("li")
   .data([10, 20, 30, 40, 50])
   .text(function(d) {
       return "This is pre-existing element and the value is " + d;
     })
   .attr('id', function(d) {
     return d;
   })
   .enter()
   .append("li")
   .text(function(d) {
       return "This is dynamically created element and the value is " + d;
     })
   .attr('id', function(d) {
     return d;
   });
```

画面上の見た目は変わらないが、DOM ツリーをみると各 &lt;li&gt; 要素に `id` 属性がついていることがわかる。

画面上の様子：
<img width=600 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861827/237cda59-ea86-aa31-1934-97e208682b52.png">

DOM ツリーの様子：
<img width=600 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861827/79d19249-8135-9a0b-c8de-4a947ba946a4.png">

この状態で対応関係を定義する関数を第2引数に指定して`data()`を呼び出すと、配列の要素順序とは関係なく意図したとおりのデータと DOM element の対応関係をもった selection が構築できる。

この第2引数の関数は以下のように利用され、DOM element およびデータ配列要素それぞれに対してキー値が算出される。このキー値が同じ DOM element とデータ配列要素が対応するものと考えられる。

- DOM element に対して：
  - `function(d)`の引数 `d` は null
  - `this` はこの DOM element
- データ配列要素に対して：
  - `function(d)`の引数 `d` はこのデータ配列要素
  - `this` は親となる DOM element。今の例では &lt;ul&gt;
  

```javascript
d3.selectAll("li")
  .data([10, 20, 40], 
        function(d) {
            return d ? d : this.id;
        })
```

<img width=700 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861827/715fc48c-1908-8102-f2bd-6ee6ae209ab1.png">

この selection に対して `exit()`, `remove()` を適用すれば、意図したとおりの削除（"30" に対応する DOM element の削除）が行える。

```javascript
d3.selectAll("li")
  .data([10, 20, 40], 
        function(d) {
            return d ? d : this.id;
        })
  .exit()
  .remove()
```

<img width=600 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861827/e4b3bde6-5391-d229-5477-3908a711fb83.png">


画面上の様子：
<img width=600 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861827/3fa7adc7-054f-d255-a247-84be459f00a7.png">

DOM ツリーの様子：
<img width=600 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1861827/77f0b40d-19b8-d507-0d9f-76353fc92ed8.png">