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/