LoginSignup
1
1

More than 5 years have passed since last update.

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

Posted at

D3.jsで階層構造のDOMの子要素から親要素のデータを取得する方法です。

以下のような配列から、

  const data = [
    {name: 'name-00', values: [20, 10, ...]},
    {name: 'name-01', values: [12, 45, ...]},
    ...,
    {name: 'name-17', values: [48, 90, ...]}
  ];

下図のようなチャートを作成します。

playground03.png

まず初めに配列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で表示したものです。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1