Posted at

D3.jsで子要素のDOMから親要素のデータを取得

More than 1 year has passed since last update.

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>要素にバインドされたデータvdata[i].values[idx]、すなわち2010のような数値データのみなので、名前のプロパティを持ちません。

  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>xScaleyScaleで座標を与えることで同じようなチャートが作成できます。


階層構造のDOMを作成する方法は慣れるまでは理解しづらいかもしれませんが、慣れるとDOMの扱いがわかりやすくなります。


Demos on Bl.ocks

この記事のtipsで作成した図をBl.ocksで表示したものです。