6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

D3jsのselectionとdata joinを実験する

Last updated at Posted at 2021-08-13

概要

SVGを表示するために D3js を利用したいのだが、JSコード上で動的に操作するデータに対応するSVGツリーの構築方法が特徴的だったので実験してみた。

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

実験内容は、Codepen で実行することができる。

構成

  • D3js (ver4)

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

参照: D3.js - selections

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

<ul id="list">
  <li>initial element 0</li>
  <li>initial element 1</li>
</ul>

に対して

d3.select("#list").selectAll("li");

を実行すると、以下のような selection が構築される。

画面上の様子:

DOM ツリーの様子:

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

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

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

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

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;
     });

画面上の様子:

DOM ツリーの様子:

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

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

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

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();

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

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;
     });

画面上の様子:

DOM ツリーの様子:

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

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

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

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

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

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

画面上の様子:

DOM ツリーの様子:

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

参照:selection.data()

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

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 ツリーをみると各 <li> 要素に id 属性がついていることがわかる。

画面上の様子:

DOM ツリーの様子:

この状態で対応関係を定義する関数を第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。今の例では <ul>
d3.selectAll("li")
  .data([10, 20, 40], 
        function(d) {
            return d ? d : this.id;
        })

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

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

画面上の様子:

DOM ツリーの様子:

6
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?