BETA

D3をReact+TypeScriptと一緒に使ってみる

投稿日:2019-09-22
最終更新:2019-09-22

はじめに

データビジュアライゼーションのライブラリであるD3.jsをReact+TypeScriptと一緒に使ってみたのでそれの紹介です。

バージョン

TypeScript+Reactの作業環境に加えて、d3@types/d3をインストールします。

  • TypeScript - 3.6.3
  • React - 16.9.0
  • d3 - 5.12.0
  • @types/d3 - 5.7.2

D3でDOM操作をしてみる

ディレクトリ構成

d3test  
|--node_modules  
|--src  
|  |--components  
|  |  |--test.tsx  
|  |--index.html  
|  |--index.tsx  
|--package.json  
|--tsconfig.json  

./src/index.html

<!DOCTYPE html>  
<html>  
<head>  
  <meta charset="UTF-8">  
  <title>TypeScript+React+D3</title>  
</head>  
<body>  
  <div id="test"></div>  
  <script src="index.tsx"></script>  
</body>  

</html>  

./src/index.tsx

import React from "react"  
import ReactDom from "react-dom"  

import { Test } from './components/test'  

ReactDom.render(<Test />, document.getElementById("test"))  

./src/components/test.tsx

import React, { useEffect } from "react"  
import * as d3 from 'd3'  

export const Test: React.FC = () => {  

  useEffect(() => {  
    const test = d3.select('.test');  
    test.append("div").html("Hello D3");  
  })  

  return (  
    <>  
      Graph will be Here  
      <div className="test"></div>  
    </>  
  )  
}  

主役のd3を使っているのはtest.tsxですが、よくみるとd3を使っているのはuseEffect()内です。これは、コンポーネントをレンダリングしたに動かないといけない為です。

useEffect()内に書くことで、<div class="test"></div>のレンダリング後に実行することが可能です。

動作確認

Hello D3が表示されていればOKです。

D3でSVGを描写してみる

若干難しくしてみます。下をまとめたブロックを複数個SVGの中に描くd3を試していきます。

  • rect - 大きさ、場所、色を指定できる
  • text - 表示する文字を指定できる

ディレクトリ構成

d3test  
|--node_modules  
|--src  
|  |--components  
|  |  |--d3  
|  |  |  |--BoxTexts.tsx  
|  |  |  |--Svg.tsx  
|  |  |  
|  |  |--Canvas.tsx  
|  |  
|  |--index.html  
|  |--index.tsx  
|  
|--package.json  
|--tsconfig.json  

変更部分と追加部分のファイルを見てみます

./src/components/index.tsx

import React from "react"  
import ReactDom from "react-dom"  

// 変更部分  
import { Canvas } from './components/Canvas'  

// 変更部分  
ReactDom.render(<Canvas />, document.getElementById("test"))  

./src/components/d3/Svg.tsx

import React from 'react'  

// 縦、横を選択できるSVG。わざわざd3を使う必要ないと思って使わなかった。  
export const Svg: React.FC<{ width: number, height: number }> = ({ children, width, height }) => (  
  <svg style={{ width: width, height: height }}>  
    {children}  
  </svg>  
)  

./src/components/d3/BoxTexts.tsx

import React, { useEffect } from "react"  
import * as d3 from 'd3'  

//-----------------------------------------------------------  
// 型設定。  
type BoxText = {  
  text: string  
  width: number  
  height: number  
  color: 'red' | 'green' | 'blue'  
  coordinate: [number, number]  
}  

export type BoxTexts = BoxText[];  

//-----------------------------------------------------------  
//  
export const BoxTexts: React.FC<{ boxtexts: BoxTexts }> = ({ boxtexts }) => {  

  //d3-----------------------------  
  useEffect(() => {  
    const g = d3.select('.rects')  

    // 一個一個の箱(■+TEXT)の配列みたいなもの。transformで全体を移動している  
    const boxes = g.selectAll('rect')  
      .data(boxtexts).enter()  
      .append('g')  
      .attr("transform", (d) => `translate(${d.coordinate[0]}, ${d.coordinate[1]})`)  

    // 箱(■)のみの設定。  
    boxes.append("rect").attr("width", (d) => d.width)  
      .attr("height", (d) => d.height)  
      .attr("fill", (d) => d.color)  

    // TEXTの設定。  
    boxes.append("text")  
      .attr("transform", (d) => `translate(0, ${d.height})`)  
      .attr("fill", "white")  
      .style("font-size", (d) => d.width * 0.9)  
      .text((d) => d.text)  
  })  
  //-------------------------------  

  return (  
    <g className="rects"></g>  
  )  
}  

./src/components/Canvas.tsx

import React from 'react'  

import { BoxTexts } from './d3/BoxTexts'  
import { Svg } from './d3/Svg'  

export const Canvas: React.FC = () => {  
  let boxTexts: BoxTexts = [  
    {  
      text: "E",  
      width: 100,  
      height: 100,  
      coordinate: [0, 0],  
      color: "blue"  
    },  
    {  
      text: "N",  
      width: 100,  
      height: 100,  
      coordinate: [80, 30],  
      color: "red"  
    },  
    {  
      text: "D",  
      width: 100,  
      height: 100,  
      coordinate: [160, 60],  
      color: "green"  
    }  
  ]  

  return (  
    <Svg width={260} height={160}>  
      <BoxTexts boxtexts={boxTexts} />  
    </Svg>  
  )  
}  

ちょっと解説

d3を使用しているのはBoxTexts.tsx、ここのBoxTextsというコンポーネントは表示したい箱の情報を入れるだけで描いてくれるものです。

注目してほしいところは、ここ

// ・・・略  

// 一個一個の箱(■+TEXT)の配列みたいなもの。transformで全体を移動している  
    const boxes = g.selectAll('rect')  
      .data(boxtexts).enter()  
      .append('g')  
      .attr("transform", (d) => `translate(${d.coordinate[0]}, ${d.coordinate[1]})`)  // <= ココ !!!!  

    // 箱(■)のみの設定。  
    boxes.append("rect").attr("width", (d) => d.width)  // <= ココ !!!!  
      .attr("height", (d) => d.height)  // <= ココ !!!!  
      .attr("fill", (d) => d.color)  // <= ココ !!!!  

    // TEXTの設定。  
    boxes.append("text")  
      .attr("transform", (d) => `translate(0, ${d.height})`)  // <= ココ !!!!  
      .attr("fill", "white")  
      .style("font-size", (d) => d.width * 0.9)  // <= ココ !!!!  
      .text((d) => d.text)  // <= ココ !!!!  
  })  

「いや注目するとこ多過ぎ」と思ったかもしれませんが、d3の厄介なところの一つであるデータのバインド。ぱっと見でこの(d) => {...}dに入っているのが何かわかるかは慣れていないと難しいと思います。

しかし、TypeScriptのおかげで

boxes.append("rect").attr("width", (d) => d.width)  // (parameter) d: BoxText  

と教えてくれます(tooltipで)!!さらに値の候補も表示してくれるので開発にミスが少なくなります。d3を触らない人でもすぐ理解できそうですね。

最後に、Canvas.tsxから箱の情報を渡す際にも型が決まっているので安心して渡せます。

動作確認

何が表示されているか見てみましょう。

これで終わりです。

技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

@syakooのブログ

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう
目次をみる
技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
or 外部アカウントではじめる
10秒で技術ブログが作れます!