12
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

第5回 2020年版 ReactのRechartsで新型コロナウイルス感染症対策サイトのデータを可視化する

Last updated at Posted at 2020-03-11

1. 概要

2020年3月4日に公開された新型コロナウイルス感染症対策サイトは、Vue+Nuxt.jsで構築されており、プログラムはGithubで公開され、データもオープンデータカタログとして公開される予定となっています。

今回は、この新型コロナウイルス感染症対策サイトのデータをReactのRechartsを利用して、可視化を行いたいと思います。

2. 前提条件

作業日時

  • 2020/3/12

環境

  • MacBook Pro
  • macOS Catalina

ソフトウェアのバージョン

ソフトウェア バージョン
React 16.13.0
React-dom 16.13.0
Typescript 3.7.2
Recharts 2.0.0-beta.1
Material UI v4.9.5
@material-ui/core 4.9.5
@material-ui/icons 4.9.1

3. Reactアプリの作成

3.1. Rechartsのインストール

create-react-appでサンプル用のReactのアプリを作成します。
Rechartsの他にMaterial UIをインストールします。

$ npx create-react-app recharts-sample --template typescript
$ cd recharts-sample/
$ yarn add recharts @material-ui/core @material-ui/icons
$ yarn add -D @types/recharts

3.2. プログラムの作成

まず、App.tsxを修正します。
グラフを表示するChartsのコンポーネントを追加します。

src/App.tsx
import React from 'react';
import './App.css';
import Charts from './Charts';

function App() {
  return (
    <div className="App">
      <Charts />
    </div>
  );
}

export default App;

次に、Chartsのコンポーネントを作成します。

fetchで非同期に外部サイトからデータを取得し、取得完了後にグラフを表示しています。
東京都のオープンデータカタログにデータがすべて揃っていないので、今回は新型コロナウイルス感染症対策サイトのデータを拝借させて貰っています。

取得したデータをRechartsで描画可能なデータ形式に変換し、 <BarChart data={patients} ... という形でデータを渡します。また、<XAxis dataKey="date" ... で対象のデータを指定します。

src/Charts.tsx
//import * as React from 'react';
import React, { useState, useEffect } from "react";

import { Theme, createStyles, makeStyles } from '@material-ui/core';

import {
  Paper,
  Typography,
  Grid,
  CircularProgress
} from '@material-ui/core';

import {
  ResponsiveContainer,
  BarChart,
  Bar,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip as ChartTooltip,
} from 'recharts';

import { format, getUnixTime, parse } from 'date-fns'

const useStyles = makeStyles((theme: Theme) =>
  createStyles({
    root: {
      margin: theme.spacing(1),
    },
    charts: {
      width: '100%',
      height: '300px',
    },
    card: {
      width: '100%',
      height: '300px',
    }
  }),
);

export type Contacts = {
  date: string,
  data: Array<any>
};
export type Querents = {
  date: string,
  data: Array<any>
};
export type Patients = {
  date: string,
  data: Array<any>
};
export type PatientsSummary = {
  date: string,
  data: Array<any>
};
export type DischargesSummary = {
  date: string,
  data: Array<any>
};
export type Inspections = {
  date: string,
  data: Array<any>
};
export type InspectionsSmmary = {
  date: string,
  data: {
    都内: Array<any>,
    その他: Array<any>
  },
  labels: Array<string>
};
export type MainSummary = {
  attr: string,
  value: number,
  children: Array<any>
};

export type ICovid19Data = {
  contacts: Contacts
  querents: Querents,
  patients: Patients,
  patients_summary: PatientsSummary,
  discharges_summary: DischargesSummary,
  inspections: Inspections,
  inspections_summary: InspectionsSmmary,
  lastUpdate: string,
  main_summary: MainSummary
};

type Props = {};

// FetchAPIでのデータ取得先
const url = "https://raw.githubusercontent.com/tokyo-metropolitan-gov/covid19/development/data/data.json"

const chart_margin = { top: 5, right: 50, left: 50, bottom: 25 };

/*
* Rechartsで可視化
*/
export const Charts = (props: Props) => {

  const [codiva_data, setData] = useState<ICovid19Data | undefined>();
  const [loading, setLoading] = useState(true);

  const classes = useStyles(props);

  // useEffect
  useEffect(() => {
    console.log('useEffect');

    let cleanedUp = false;

    const fetchData = async () => {
      const response = await fetch(url);
      const result: ICovid19Data = await response.json();

      if (!cleanedUp) { // unmount後にデータの読み込みが完了した場合はデータを設定しない
        // Objectをコピーする。 Object.assign でも良い。
        setData(prevState => {
          return { ...prevState, ...result };
        });

        setLoading(false);
      }
    };

    fetchData();

    return () => { cleanedUp = true; }; // 戻り値の関数がunmount時点で実行される

  }, [setData]);

  if (loading || codiva_data === undefined) {
    console.log("Loadingの表示");
    return (<div><CircularProgress>Loading...</CircularProgress></div>)

  } else {
    console.log("グラフの表示");

    const patients = codiva_data.patients_summary.data;
    for (var key in patients) {
      patients[key].date = getUnixTime(parse(patients[key].日付, "yyyy-MM-dd'T'HH:mm:ss.SSSxxx", new Date())) * 1000;
    }

    const contacts = codiva_data.contacts.data;
    for (key in contacts) {
      contacts[key].date = getUnixTime(parse(contacts[key].date, 'yyyy-MM-dd', new Date())) * 1000;
    }

    const querents = codiva_data.querents.data;
    for (key in querents) {
      querents[key].date = getUnixTime(parse(querents[key].date, 'yyyy-MM-dd', new Date())) * 1000;
    }

    const inspections = Array();
    for (key in codiva_data.inspections_summary.labels) {
      inspections.push({
        date: getUnixTime(parse(codiva_data.inspections_summary.labels[key], 'M/d', new Date())) * 1000,
        tokyo: codiva_data.inspections_summary.data.都内[key],
        other: codiva_data.inspections_summary.data.その他[key],
      })
    }

    return (
      <Grid container className={classes.root} spacing={3} justify="center" >
        <Grid item xs={6} >
          <Paper>
            <Typography variant="h5">陽性患者数</Typography>
            <div className={classes.charts}>
              {/* // 親要素でwidth,heightの指定が必要。 */}
              <ResponsiveContainer width="95%">
                <BarChart
                  // ResponsiveContainerを利用しない場合はwidth,heightを指定する
                  // width={600}  //グラフ全体の幅を指定
                  // height={280}  //グラフ全体の高さを指定          
                  data={patients} // 表示するデータ  
                  margin={chart_margin}>
                  <XAxis // X軸
                    dataKey="date" // X軸の基準となるデータ項目名
                    tickFormatter={(x) => format(x, 'M/d')} // X軸を YYYY/MM/DD 形式で表示します
                  />
                  <YAxis
                    domain={[0, 10]}
                    ticks={[0, 2, 4, 6, 8, 10]} // Y軸に表示する温度
                    unit="人" // Y軸の単位
                  />
                  <CartesianGrid // ガイド線の表示
                    stroke="#ccc"
                    strokeDasharray="3 3"
                  />
                  <ChartTooltip // ツールチップの表示
                    labelFormatter={(x) => format(x as number, 'M/d')} // ラベルの表示フォーマット(日付)
                  />
                  <Bar
                    name="陽性患者数"
                    dataKey="小計" // charts_data のキー
                    stroke="salmon" // 線の色
                    fill="salmon"
                    unit="人" //単位
                  />
                </BarChart>
              </ResponsiveContainer>
            </div>
          </Paper>
        </Grid>
        <Grid item xs={6} >
          <Paper>
            <Typography variant="h5">検査実施数</Typography>
            <div className={classes.charts}>
              <ResponsiveContainer width="95%">
                <BarChart data={inspections} margin={chart_margin}>
                  <XAxis dataKey="date" tickFormatter={(x) => format(x, 'M/d')} />
                  <YAxis domain={[0, 250]} ticks={[0, 50, 100, 150, 200, 250]} unit="人" />
                  <CartesianGrid stroke="#ccc" strokeDasharray="3 3" />
                  <ChartTooltip labelFormatter={(x) => format(x as number, 'M/d')} />
                  <Bar name="都内発生" dataKey="tokyo" stackId="a" stroke="salmon" fill="salmon" unit="人" />
                  <Bar name="その他" dataKey="other" stackId="a" stroke="#8884d8" fill="#8884d8" unit="人" />
                </BarChart>
              </ResponsiveContainer>
            </div>
          </Paper>
        </Grid>
        <Grid item xs={6} >
          <Paper>
            <Typography variant="h5">新型コロナコールセンター相談件数</Typography>
            <div className={classes.charts}>
              <ResponsiveContainer width="95%">
                <BarChart data={contacts} margin={chart_margin}>
                  <XAxis dataKey="date" tickFormatter={(x) => format(x, 'M/d')} />
                  <YAxis domain={[0, 700]} ticks={[0, 100, 200, 300, 400, 500, 600, 700]} unit="人" />
                  <CartesianGrid stroke="#ccc" strokeDasharray="3 3" />
                  <ChartTooltip labelFormatter={(x) => format(x as number, 'M/d')} />
                  <Bar name="相談件数" dataKey="小計" stroke="salmon" fill="salmon" unit="人" />
                </BarChart>
              </ResponsiveContainer>
            </div>
          </Paper>
        </Grid>
        <Grid item xs={6} >
          <Paper>
            <Typography variant="h5">新型コロナ受診相談窓口相談件数</Typography>
            <div className={classes.charts}>
              <ResponsiveContainer width="95%">
                <BarChart data={querents} margin={chart_margin}>
                  <XAxis dataKey="date" tickFormatter={(x) => format(x, 'M/d')} />
                  <YAxis domain={[0, 3000]} ticks={[0, 500, 1000, 1500, 2000, 2500, 3000]} unit="人" />
                  <CartesianGrid stroke="#ccc" strokeDasharray="3 3" />
                  <ChartTooltip labelFormatter={(x) => format(x as number, 'M/d')} />
                  <Bar name="相談件数" dataKey="小計" stroke="salmon" fill="salmon" unit="人" />
                </BarChart>
              </ResponsiveContainer>
            </div>
          </Paper>
        </Grid>
      </Grid>
    );
  }

}

export default Charts;

4. 実行結果

yarn startでサーバーを起動し、画面を表示してみます。

Rechartsサンプル.gif

5. 最後に

今回はRechartsを利用して可視化を行う方法について説明しました。
データを設定するだけで綺麗なグラフを表示してくれます。レポート機能などはRechartsである程度対応できると思います。

次回はFirebaseのCloud storageに画像をアップロードする方法について説明をします。

6. 過去の記事

過去のReactに関する記事です。

12
9
1

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
12
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?