2017年12月14日
D3.jsで子要素のDOMから親要素のデータを取得
D3.jsで階層構造のDOMの子要素から親要素のデータを取得する方法です。
例
以下のような配列から、
const data = [
{ name: 'name-00', values: [20, 10, /* ... */] },
{ name: 'name-01', values: [12, 45, /* ... */] },
// ...,
{ name: 'name-17', values: [48, 90, /* ... */] },
];
下図のようなチャートを作成します。

まず初めに配列dataから要素のグループ化を行なう<g>要素を生成します。
ここで生成した<g>要素にバインドされるデータはdata[i]、すなわち{name: 'name-00', values: [20, 10, ...]}という内容のオブジェクトです。
const rows = svg.selectAll('.rows')
.data(data)
.enter()
.append('g')
.attr('class', 'rows')
.attr('transform', (d) => `translate(0, ${yScale(d.name)})`)
.on('click', (d) => {
console.log(d);
// => { name: 'name-00', values: [20, 10, ...] }
});
<g>要素にバインドしたデータdata[i]のプロパティdata[i].valuesの配列を使用して、<g>要素の子要素にdata[i].values.lengthの数だけ<circle>を生成します。
rows.selectAll(".circle")
.data((d) => d.values)
.enter()
.append('circle')
.attr('class', 'circle')
.attr('cx', (v) => xScale(v))
.attr('cy', 0)
.attr('r', 4);
子要素のselection.attr(name, value)で使用する関数の引数をvとしています。
vの部分を従来のdと置いても問題なく動作しますが、親要素の関数の引数dとの混同を避けるためにvとしました。
上記の2つのコードで生成されるDOMは以下のようになります。
<g class="rows" transform="translate(0, 15)">
<circle class="circle" cx="24" cy="0" r="4"></circle>
<circle class="circle" cx="12" cy="0" r="4"></circle>
...
<circle class="circle" cx="168" cy="0" r="4"></circle>
</g>
<g class="rows" transform="translate(0, 35)">
<circle class="circle" cx="35" cy="0" r="4"></circle>
...
</g>
...
次に、各<circle>要素にイベントを設定します。
「マウスオーバー、クリックで画面右上に名前nameと数値valueを表示する」という簡潔なイベントを考えます。
selection.on(type, function(d, i, nodes) {...})を使いますが、ここで一つ問題があります。
<circle>要素にバインドされたデータvはdata[i].values[idx]、すなわち20や10のような数値データのみなので、名前のプロパティを持ちません。
rows.selectAll('.circle')
.on('click', (v, i, nodes) => {
console.log(v); // => 14
});
子要素から親要素のデータにあるプロパティdata[i].nameにアクセスするには工夫が必要です。
解決法
d3.select(nodes[i].parentNode).datum()で親要素のデータを取得できます。
rows.selectAll('.circle')
.on('click', (v, i, nodes) => {
// <circle>要素を選択している
d3.select(nodes[i]);
// nodes[i] = <circle class="circle">
// <circle>要素の親要素である<g>要素を選択している
d3.select(nodes[i].parentNode);
// nodes[i].parentNode = <g class="rows">
// <circle>要素にバインドされたデータを表示
console.log(v);
// => 14
console.log(d3.select(nodes[i]).datum());
// => 14
// <circle>要素の親要素である<g>要素にバインドされたデータを表示
console.log(d3.select(nodes[i].parentNode).datum());
// => Object { name: 'name-00', values: [14, 11, ...] }
const text = `${d3.select(nodes[i].parentNode).datum().name}: ${v}min.`;
svg.append('text')
.attr('class', 'guide')
.text(text);
});
<g>と<circle>の階層構造のDOM生成がわかりづらかったら、dataを単一の配列に整形し、それぞれの<circle>にxScaleとyScaleで座標を与えることで同じようなチャートが作成できます。
階層構造のDOMを作成する方法は慣れるまでは理解しづらいかもしれませんが、慣れるとDOMの扱いがわかりやすくなります。
作成例
上図のコードは gist や Bl.ocks でも公開しています。
つつじの蜜 / ギリシャラブ (2017)
D3
D3はデータビジュアライゼーションのためのJavaScriptライブラリです。
https://d3js.org/