水戸地図(β)

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>要素にバインドされたデータ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の扱いがわかりやすくなります。

作成例

Observable
observablehq.com

上図のコードは gist Bl.ocks でも公開しています。

つつじの蜜 / ギリシャラブ (2017)

広告

2017年12月14日

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

技術記事

Top

水戸地図(β)