データの結合
はじめに、Thinking With Joins と selection.join ノートブック を参照してください。
selection.data(data, key)
ソース · 指定された *data* の配列を選択された要素にバインドし、 *更新* セレクションを表す新しいセレクションを返します。更新セレクションとは、データに正常にバインドされた要素のことです。また、返されたセレクションに *enter* セレクションと *exit* セレクションを定義します。これらは、新しいデータに対応するように要素を追加または削除するために使用できます。指定された *data* は、任意の値 ( *例:* 数値またはオブジェクト) の配列、または各グループの値の配列を返す関数です。データが要素に割り当てられると、プロパティ `__data__` に格納されるため、データは「スティッキー」になり、再選択時に使用できます。
*data* は、セレクション内の **各グループ** に対して指定されます。セレクションに複数のグループがある場合 ( d3.selectAll の後に selection.selectAll が続くなど)、 *data* は通常、関数として指定する必要があります。この関数は、各グループに対して順番に評価され、グループの親データ ( *d* 、未定義の場合あり)、グループインデックス ( *i* )、およびセレクションの親ノード ( *nodes* ) が渡され、 *this* はグループの親要素となります。
selection.join (または、より明示的には selection.enter 、 selection.exit 、 selection.append 、 selection.remove ) と組み合わせて、 *selection.data を使用して、データに一致するように要素を入力、更新、および終了できます。たとえば、数値のマトリックスからHTMLテーブルを作成するには、次のようにします。
const matrix = [
[11975, 5871, 8916, 2868],
[ 1951, 10048, 2060, 6171],
[ 8010, 16145, 8090, 8045],
[ 1013, 990, 940, 6907]
];
d3.select("body")
.append("table")
.selectAll("tr")
.data(matrix)
.join("tr")
.selectAll("td")
.data(d => d)
.join("td")
.text(d => d);
この例では、 *data* 関数は恒等関数です。各テーブル行に対して、データマトリックスから対応する行を返します。
*key* 関数が指定されていない場合、 *data* の最初のデータは最初の選択された要素に、2番目のデータは2番目の選択された要素に、というように割り当てられます。 *key* 関数は、デフォルトのインデックスによる結合を置き換え、各データと要素の文字列識別子を計算することにより、どのデータがどの要素に割り当てられるかを制御するために指定できます。このキー関数は、選択された各要素に対して順番に評価され、現在のデータ ( *d* )、現在のインデックス ( *i* )、および現在のグループ ( *nodes* ) が渡され、 *this* は現在のDOM要素 ( *nodes* [*i*] ) となります。返される文字列は、要素のキーです。次に、キー関数は、 *data* の各新しいデータに対しても評価され、現在のデータ ( *d* )、現在のインデックス ( *i* )、およびグループの新しい *data* が渡され、 *this* はグループの親DOM要素となります。返される文字列は、データのキーです。指定されたキーのデータは、一致するキーを持つ要素に割り当てられます。同じキーを持つ要素が複数ある場合、重複する要素はexitセレクションに配置されます。同じキーを持つデータが複数ある場合、重複するデータはenterセレクションに配置されます。
たとえば、次のドキュメントがあるとします。
<div id="Ford"></div>
<div id="Jarrah"></div>
<div id="Kwon"></div>
<div id="Locke"></div>
<div id="Reyes"></div>
<div id="Shephard"></div>
キーでデータを結合するには、次のようにします。
const data = [
{name: "Locke", number: 4},
{name: "Reyes", number: 8},
{name: "Ford", number: 15},
{name: "Jarrah", number: 16},
{name: "Shephard", number: 23},
{name: "Kwon", number: 42}
];
d3.selectAll("div")
.data(data, function(d) { return d ? d.name : this.id; })
.text(d => d.number);
この例のキー関数は、データ *d* が存在する場合はそれを使用し、そうでない場合は要素のidプロパティにフォールバックします。これらの要素は以前にデータにバインドされていなかったため、キー関数が選択された要素で評価されるとデータ *d* はnullになり、キー関数が新しいデータで評価されるとnull以外になります。
*update* セレクションと *enter* セレクションはデータの順序で返されますが、 *exit* セレクションは結合前のセレクションの順序を保持します。キー関数が指定されている場合、セレクション内の要素の順序はドキュメント内の順序と一致しない場合があります。必要に応じて、 selection.order または selection.sort を使用してください。キー関数が結合にどのように影響するかについては、 棒グラフ、パート2 と オブジェクトの恒常性 を参照してください。
*data* が指定されていない場合、このメソッドは選択された要素のデータの配列を返します。
このメソッドを使用してバインドされたデータをクリアすることはできません。代わりに selection.datum を使用してください。
selection.join(enter, update, exit)
ソース · selection.data によって以前にバインドされたデータに一致するように、必要に応じて要素を追加、削除、および並べ替え、 *マージされた* enterセレクションとupdateセレクションを返します。このメソッドは、明示的な 一般的な更新パターン に代わる便利な方法であり、 selection.enter 、 selection.exit 、 selection.append 、 selection.remove 、および selection.order を置き換えます。例えば
svg.selectAll("circle")
.data(data)
.join("circle")
.attr("fill", "none")
.attr("stroke", "black");
*enter* 関数は、上記のように文字列の省略形として指定できます。これは、指定された要素名で selection.append を呼び出すことと同じです。同様に、オプションの *update* 関数と *exit* 関数を指定できます。デフォルトでは、それぞれ恒等関数と selection.remove の呼び出しです。したがって、上記の省略形は次と同等です。
svg.selectAll("circle")
.data(data)
.join(
enter => enter.append("circle"),
update => update,
exit => exit.remove()
)
.attr("fill", "none")
.attr("stroke", "black");
enter、update、exitに別々の関数を渡すことで、何が起こるかをより詳細に制御できます。また、 selection.data にキー関数を指定することで、DOMへの変更を最小限に抑えてパフォーマンスを最適化できます。たとえば、enterとupdateに異なる塗りつぶしの色を設定するには、次のようにします。
svg.selectAll("circle")
.data(data)
.join(
enter => enter.append("circle").attr("fill", "green"),
update => update.attr("fill", "blue")
)
.attr("stroke", "black");
*enter* 関数と *update* 関数によって返されるセレクションはマージされ、 *selection.join によって返されます。
*enter* 関数、 *update* 関数、 *exit* 関数内にトランジションを作成することで、enter、update、exitをアニメーション化できます。 *enter* 関数と *update* 関数がトランジションを返す場合、それらの基になるセレクションはマージされ、 *selection.join によって返されます。 *exit* 関数の戻り値は使用されません。
詳細については、 selection.join ノートブック を参照してください。
selection.enter()
ソース · enterセレクションを返します。enterセレクションとは、セレクションに対応するDOM要素がなかった各データのプレースホルダーノードのことです。( selection.data によって返されないセレクションの場合、enterセレクションは空です。)
enterセレクションは通常、新しいデータに対応する「不足している」要素を作成するために使用されます。たとえば、数値の配列からDIV要素を作成するには、次のようにします。
const div = d3.select("body")
.selectAll("div")
.data([4, 8, 15, 16, 23, 42])
.enter().append("div")
.text(d => d);
bodyが最初に空の場合、上記のコードは6つの新しいDIV要素を作成し、それらをbodyに順番に追加し、関連付けられた (文字列に変換された) 数値をテキストコンテンツとして割り当てます。
<div>4</div>
<div>8</div>
<div>15</div>
<div>16</div>
<div>23</div>
<div>42</div>
概念的には、enterセレクションのプレースホルダーは親要素 (この例では、ドキュメントbody) へのポインタです。enterセレクションは通常、要素を追加するための一時的なものに過ぎず、追加後にupdateセレクションと マージ されることがよくあります。そのため、enterする要素とupdateする要素の両方に変更を適用できます。
selection.exit()
ソース · exitセレクションを返します。exitセレクションとは、新しいデータが見つからなかったセレクション内の既存のDOM要素のことです。( selection.data によって返されないセレクションの場合、exitセレクションは空です。)
exitセレクションは通常、古いデータに対応する「余分な」要素を削除するために使用されます。たとえば、以前に作成したDIV要素を新しい数値の配列で更新するには、次のようにします。
div = div.data([1, 2, 4, 8, 16, 32], d => d);
キー関数が指定され (恒等関数として)、新しいデータにドキュメント内の既存の要素と一致する数値 [4、8、16] が含まれているため、updateセレクションには3つのDIV要素が含まれています。これらの要素をそのままにして、enterセレクションを使用して [1、2、32] の新しい要素を追加できます。
div.enter().append("div").text(d => d);
同様に、exitする要素 [15、23、42] を削除するには、次のようにします。
div.exit().remove();
これで、ドキュメントbodyは次のようになります。
<div>1</div>
<div>2</div>
<div>4</div>
<div>8</div>
<div>16</div>
<div>32</div>
古いデータの順序と新しいデータの順序が一貫していたため、DOM要素の順序はデータの順序と一致します。新しいデータの順序が異なる場合は、 selection.order を使用してDOM内の要素を並べ替えます。データ結合の詳細については、 一般的な更新パターン ノートブックを参照してください。
selection.datum(value)
ソース · 選択された各要素にバインドされたデータを取得または設定します。selection.dataとは異なり、このメソッドは結合を計算せず、インデックスやenterおよびexitの選択には影響しません。
valueが指定されている場合、選択されたすべての要素で、要素のバインドされたデータを指定された値に設定します。valueが定数の場合、すべての要素に同じデータが与えられます。そうでない場合、valueが関数の場合、選択された各要素に対して順番に評価され、現在のデータ(d)、現在のインデックス(i)、現在のグループ(nodes)が渡され、thisは現在のDOM要素(nodes[i])となります。そして、この関数は各要素の新しいデータを設定するために使用されます。null値はバインドされたデータを削除します。
valueが指定されていない場合、選択内の最初の(null以外の)要素のバインドされたデータを返します。これは、選択に要素が1つだけ含まれていることがわかっている場合にのみ一般的に役立ちます。
このメソッドは、HTML5のカスタムデータ属性にアクセスするのに役立ちます。たとえば、次の要素が与えられた場合
<ul id="list">
<li data-username="shawnbot">Shawn Allen</li>
<li data-username="mbostock">Mike Bostock</li>
</ul>
各要素のデータを組み込みのdatasetプロパティとして設定することにより、カスタムデータ属性を公開できます。
selection.datum(function() { return this.dataset; })
selection.merge(other)
ソース · この選択を指定されたother選択またはトランジションとマージした新しい選択を返します。返された選択は、この選択と同じ数のグループと親を持ちます。この選択に存在しない(null)要素は、指定されたselectionに存在する場合(nullでない場合)、対応する要素で埋められます。(other選択に追加のグループまたは親がある場合、それらは無視されます。)
このメソッドは、selection.joinによって内部的に使用され、データのバインド後にenter選択とupdate選択をマージします。明示的にマージすることもできますが、マージは要素のインデックスに基づいているため、selection.filterではなく、selection.selectなど、インデックスを保持する操作を使用する必要があります。例えば
const odd = selection.select(function(d, i) { return i & 1 ? this : null; ));
const even = selection.select(function(d, i) { return i & 1 ? null : this; ));
const merged = odd.merge(even);
詳細はselection.dataを参照してください。
ただし、このメソッドは任意の選択を連結することを意図したものではありません。この選択と指定されたother選択の両方に同じインデックスに(null以外の)要素がある場合、この選択の要素がマージで返され、other選択の要素は無視されます。