Reactのチャート描画ライブラリRechartsで様々なチャートを出力する

Ateam Lifestyle x cyma Advent Calendar 2018の12日目は、株式会社エイチームライフスタイルのエンジニア@kazenomachiが担当します。

本稿では、Reactのチャート描画ライブラリ[Recharts](http://recharts.org/en-US/)で様々なチャートを出力する方法を紹介していきます。

## やりたいこと

* Reactで、簡単なグラフ（折れ線グラフや棒グラフ）を描画したい
* できるだけ簡単に分かりやすいコードで書きたい

## Rechartsとは？

Reactで簡単にSVGチャートを描画できるライブラリです。

他にもチャートを描画することができるライブラリは多くありますが、今回は以下の点を理由にRechartsを選びました。

* Reactコンポーネントを使ってReactらしくコードを書ける（読みやすい）
* ある程度カスタマイズも可能で、レスポンシブ対応も簡単そう

## 基本編

まずは早速、基本のグラフを書いてみます。
[公式ドキュメント](http://recharts.org/en-US/guide/getting-started)に従って実装すれば簡単にグラフを作ることができます。

### 折れ線グラフ

<p data-height="600" data-theme-id="dark" data-slug-hash="BGEEmm" data-default-tab="result" data-user="kazenomachi" data-pen-title="折れ線グラフ" class="codepen">See the Pen <a href="https://codepen.io/kazenomachi/pen/BGEEmm/">折れ線グラフ</a> by Nao Kubota (<a href="https://codepen.io/kazenomachi">@kazenomachi</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

今回グラフで表示したデータは以下の通りです。
X軸に日付、Y軸に気温を表示しています。

```react
const data = [
  { date: moment('2018-12-01').unix() * 1000, 最高気温: 10, 最低気温: 1 },
  { date: moment('2018-12-02').unix() * 1000, 最高気温: 12, 最低気温: 4 },
  { date: moment('2018-12-03').unix() * 1000, 最高気温: 18, 最低気温: 8 },
  { date: moment('2018-12-04').unix() * 1000, 最高気温: 10, 最低気温: 0 },
  { date: moment('2018-12-05').unix() * 1000, 最高気温: 9, 最低気温: 1 },
  { date: moment('2018-12-06').unix() * 1000, 最高気温: 13, 最低気温: 2 },
  { date: moment('2018-12-07').unix() * 1000, 最高気温: 16, 最低気温: 5 },
]
```

グラフは以下の通りです。
折れ線グラフでは `LineChart` を使います。
`ResponsiveContainer` で囲めば、レスポンシブになります。便利！

```react
<ResponsiveContainer width="95%">
  <LineChart
      data={this.props.data} // 表示するデータ  
      margin={{top: 5, right: 50, left: 50, bottom: 25}}>
    <XAxis // X軸
      dataKey="date" // X軸の基準となるデータ項目名
      tickFormatter={(props) => moment(props).format('YYYY/MM/DD')} // X軸を YYYY/MM/DD 形式で表示します
    />
    <YAxis // Y軸
      domain={['dataMin', 'dataMax']}
      ticks={[-10,-5,0,5,10,15,20,25,30]} // Y軸に表示する温度
      unit="℃" // Y軸の単位
    />
    <CartesianGrid // ガイド線の表示
      stroke="#ccc"
      strokeDasharray="3 3"
    />
    <Tooltip // ツールチップの表示
      labelFormatter={(props) => moment(props).format('YYYY/MM/DD(ddd)')} // ラベルの表示フォーマット（日付）
    />
    <Line // 最高気温のデータを表示
      name="最高気温"
      dataKey="最高気温" // this.props.data のキー
      stroke="salmon" // 線の色
      unit="℃" //単位
    />
    <Line // 最低気温のデータを表示
      name="最低気温"
      dataKey="最低気温" // this.props.data のキー
      stroke="skyblue" // 線の色
      unit="℃" //単位
    />
  </LineChart>
</ResponsiveContainer>
```

### 棒グラフ

渡すデータは同じですが、`LineChart` を `BarChart` にすれば棒グラフになります。

<p data-height="600" data-theme-id="dark" data-slug-hash="OaKqpB" data-default-tab="result" data-user="kazenomachi" data-pen-title="棒グラフ" class="codepen">See the Pen <a href="https://codepen.io/kazenomachi/pen/OaKqpB/">棒グラフ</a> by Nao Kubota (<a href="https://codepen.io/kazenomachi">@kazenomachi</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

### 棒グラフ（データを配列で渡す）

`BarChart`ではデータを配列で渡すこともできます。表示が以下のように変わります。

<p data-height="600" data-theme-id="dark" data-slug-hash="oQKVaq" data-default-tab="result" data-user="kazenomachi" data-pen-title="棒グラフ2" class="codepen">See the Pen <a href="https://codepen.io/kazenomachi/pen/oQKVaq/">棒グラフ2</a> by Nao Kubota (<a href="https://codepen.io/kazenomachi">@kazenomachi</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>


## 応用編

`React`の`state`を使って操作できるグラフを作ってみます。
凡例をクリックし、表示項目を指定できるようにします。

<p data-height="600" data-theme-id="dark" data-slug-hash="EGxgGE" data-default-tab="result" data-user="kazenomachi" data-pen-title="stateでグラフの表示を切り替える" class="codepen">See the Pen <a href="https://codepen.io/kazenomachi/pen/EGxgGE/">stateでグラフの表示を切り替える</a> by Nao Kubota (<a href="https://codepen.io/kazenomachi">@kazenomachi</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

`state`は以下の通り初期化します。

```react
constructor(props) {
  super()
  this.state = {
    data: props.data, // グラフデータ
    chartColors: { // グラフ表示色
      総売上: '#ffd700',
      商品A: '#3cb371',
      商品B: '#87ceeb',
      来客数: '#9370db'
    },
    leftYAxisUnit: '円', // Y軸左側に表示する単位
    rightYAxisUnit: '人', // Y軸右側に表示する単位
    leftYAxis: ['総売上', '商品A', '商品B'], // Y軸左側に合わせる項目
    rightYAxis: ['来客数'], // Y軸右側に合わせる項目
    disabled: ['来客数'] // 非表示項目
  }
}
```

項目名がクリックされた時に行う処理を実装します。
`setState`で`state`の`disabled`項目を変更します。

```react
toggleChart = toggleKey => {
  if (_.includes(this.state.disabled, toggleKey)) {
    // 表示されている項目を非表示にします
    this.setState({
      // disabledから指定されたキーを削除
      disabled: this.state.disabled.filter(value => value !== toggleKey)
    })
  } else {
    // 非表示の項目を表示します
    this.setState({
      // disabledに指定されたキーを追加
      disabled: this.state.disabled.concat(toggleKey)
    })
  }
}
```

グラフの上に表示する凡例をカスタマイズします。
`onClick`で`toggleChart`を呼び出すようにします。

```react
customizedLegend = () => {
  return(
    <div className="customized-legend">
      {
        _.toPairs(this.state.chartColors).map(pair => {  // 項目名と表示色のペア
          const disable = _.includes(this.state.disabled, pair[0])
          const style = {
            marginRight: 10,
            color: disable ? "#ccc" : "#000", // 非表示項目のフォントの色を変えます
            cursor: 'pointer'
          }
          return (
            <span
              className="legend-item"
              onClick={() => this.toggleChart(pair[0])} // onClickでtoggleChartを実行します
              style={style}
            >
              { // 凡例の ● を表示 }
              <Surface width={20} height={20} viewBox="0 0 20 20">
                <Symbols cx={13} cy={13} type="circle" size={100} fill={pair[1]} />
                  {
                    // 非表示項目の場合 ● を ○ にするため、円の中に白丸を描きます
                    disable && (
                      <Symbols
                        cx={13}
                        cy={13}
                        type="circle"
                        size={50}
                        fill="#fff"
                      />
                    )
                  }
                </Surface>
              { // 項目名を表示 }
              <span>{pair[0]}</span>
            </span>
          )
        })
      }
    </div>
  )
}
```

グラフ部分は以下の通りとなります。
`Legend`の`content`で`customizedLegend`を表示するようにしています。

`state`が変わると、`this.state`を使用している部分が再描画されるので、グラフを動かしながら読んでみるとおもしろいと思います。

今回は`disabled`項目しか変更していませんが、例えば`chartColors`を変えるとグラフの色が変わったりします。

```react
<ResponsiveContainer width="95%">
  <ComposedChart
      data={this.state.data}
      margin={{top: 0, right: 50, left: 50, bottom: 25}}>
    <XAxis
      dataKey="date"
      tickFormatter={(props) => moment(props).format('YYYY/MM/DD')}
    />
    <YAxis
      orientation="left"
      yAxisId="leftYAxis"
      domain={['dataMin', 'dataMax']}
      ticks={[600000,700000,800000,900000,1000000,1100000,1200000,1300000]}
      unit="円"
    />
    <YAxis
      orientation="right"
      yAxisId="rightYAxis"
      domain={['dataMin', 'dataMax']}
      ticks={[700,800,900,1000,1100,1200,1300,1400]}
      unit="人"
    />
    <CartesianGrid 
      stroke="#ccc"
      strokeDasharray="3 3"
    />
    <Tooltip
      labelFormatter={(props) => moment(props).format('YYYY/MM/DD(ddd)')}
    />
    <Legend
      verticalAlign="top"
      height={50}
      align="left"
      content={this.customizedLegend()}
    />
    {
      _.toPairs(this.state.chartColors)
        .filter(pair => !_.includes(this.state.disabled, pair[0]))
        .map(pair => (
        <Bar
          key={pair[0]}
          name={pair[0]}
          dataKey={pair[0]}
          stackId={_.includes(['商品A', '商品B'], pair[0]) ? 'sales' : ''}
          yAxisId={_.includes(this.state.leftYAxis, pair[0]) ? 'leftYAxis' : 'rightYAxis'}
          unit={_.includes(this.state.leftYAxis, pair[0]) ? this.state.leftYAxisUnit : this.state.rightYAxisUnit}
          fill={pair[1]}
        />
      ))
    }
  </ComposedChart>
</ResponsiveContainer>
```


## まとめ

データを用意してコンポーネントに渡し、いくつかオプションを指定するだけで簡単にグラフを表示することができました。
stateを使って再描画するような処理も、Reactらしく簡単に書けて良かったです。

今回紹介したグラフ以外にも、[公式サイトのExamples](http://recharts.org/en-US/examples)や[APIドキュメント](http://recharts.org/en-US/api)にあるように、様々なグラフを簡単に実装できます。
ドキュメントに無いような特殊なグラフを実装するのには向いていませんが、手早く簡単に一般的なグラフを実装したい時には役に立つと思います！

-----

Ateam Lifestyle x cyma Advent Calendar 2018、明日は @kiitan さんに書いてもらう予定です。ぜひお楽しみに！

エイチームグループでは、一緒に働けるチャレンジ精神旺盛な仲間を募集しています。興味を持たれた方はぜひエイチームグループ採用サイトを御覧ください。
https://www.a-tm.co.jp/recruit/