📈

React + Highcharts で都道府県の人口をグラフで表示するアプリ

2021/08/29に公開

はじめに

初めて記事のようなものを書きます.よろしくお願いします.
今回は2020年のインターンシップ選考で私が開発した,都道府県ごとの人口を表示するアプリについて開発した手順を書いていきたいと思います.
至らない点があれば、遠慮なくご指摘いただければと思います。

Demo

県名にチェックを入れると,その県の人口推移が表示されます.
複数選択すると、まとめて表示されます。
react-highcharts-resas
https://react-highcharts-resas.web.app/

環境

  • macOS Catalina (v10.15.6)
  • Node.js (v14.8.0)

使用技術

API(都道府県人口データ)

API Key取得

今回、都道府県人口データを使用したいので、RESAS APIにアクセスして、利用登録をします。
取得したAPI Keyは後の開発手順で使用します。

開発手順

プロジェクト作成

まずは,作業ディレクトリに移り,プロジェクトを作成しましょう.
--template typescript オプションをつけるとTypeScript用のプロジェクトが作成できます.

npx create-react-app --template typescript react-highcharts

必要なライブラリをインストール

これからプロジェクトに移動して、スクリプトを書き換えていきますが、必要なライブラリをインストールします。
外部リソースを取得するためのライブラリであるaxiosをインストールします。
fetchを使う場合は、この必要はありません。axiosを使う方がいくらか書く量が減りますが、それほど違いはないでしょう。

yarn add axios

highchartsとReactで使うためのライブラリがあったのでそれも。

yarn add highcharts      
yarn add highcharts-react-official

API Key

次にプロジェクト直下に.env.localファイルを作成し、前の手順で取得したAPI Keyを書き込んでおきます。
環境変数は一般的に開発環境であれば.env.localを作成して書きます。
ただ、環境変数の命名にはルールがあり、頭に必ずREACT_APPと付ける必要があります。

.env.local
REACT_APP_API_KEY=<前の手順で取得したAPI_KEY>

Styleについて

ReactでCSS Styleを設定するにはさまざまな手段がありますが、今回は、React.CSSPropertiesを使おうと思います。CSSファイルに書かないインラインスタイルで、スタイル定義をコンポーネント内で完結できるのが特徴だと思います。(個人的にはMaterial-UIのmakeStyleみたいな感じという曖昧な認識です)

Button(サンプル).tsx
import React from "react";

const Styles: { [key: string]: React.CSSProperties } = {
  button: {
    margin: "1rem",
  },
};

// サンプルButtonのコンポーネント
const Button: React.FC = () => {
  return <button style={Styles.button}>hoge</button>;
};

export default Button;

コーディング

App.tsxと同じ階層にcomponentsフォルダを作成し,その中にMain.tsxCheckField.tsxGraph.tsxの3つのファイルを作成します.
Main.tsxが親でCheckField.tsxGraph.tsxを呼び出します。
呼び出されるコンポーネントから書いていきます。

都道府県の名前が並び、チェックを入れられるコンポーネント

CheckField.tsx
import React from "react";

type Props = {
  prefectures:
    | {
        prefCode: number;
        prefName: string;
      }[];

  onChange: (name: string, prefName: number, check: boolean) => void;
};

const Styles: { [key: string]: React.CSSProperties } = {
  checkcardList: {
    display: "flex",
    flexWrap: "wrap",
    padding: "10px",
    justifyContent: "flex-start",
    justifySelf: "auto",
  },
  text: { display: "contents", marginLeft: "1em", cursor: "pointer" },
  checkcard: {
    borderRadius: "24px",
    border: "solid 2px",
    textAlign: "center",
    padding: "4px",
    margin: "0.5rem",
  },
};

// 都道府県一覧のチェックボックスを表示するコンポーネント
const CheckField: React.FC<Props> = ({ prefectures, onChange }) => {
  return (
    <>
      <div style={Styles.checkcardList}>
        {prefectures.map((prefecture) => (
          <div style={Styles.checkcard} key={prefecture.prefName}>
            <input
              type="checkbox"
              name="Prefecture name"
              onChange={(event) =>
                onChange(
                  prefecture.prefName,
                  prefecture.prefCode,
                  event.target.checked
                )
              }
              id={"checkbox" + prefecture.prefCode}
            />
            <label
              style={Styles.text}
              htmlFor={"checkbox" + prefecture.prefCode}
            >
              {prefecture.prefName.length === 3
                ? " " + prefecture.prefName
                : prefecture.prefName}
            </label>
          </div>
        ))}
      </div>
    </>
  );
};

export default CheckField;

チェックを入れた都道府県のグラフを表示するコンポーネント

Graph.tsx
import React from "react";
import Highcharts from "highcharts";
import HighchartsReact from "highcharts-react-official";

const Styles: { [key: string]: React.CSSProperties } = {
  graph: {
    padding: "12px",
  },
};

type Props = {
  populationdata: {
    prefName: string;
    data: { year: number; value: number }[];
  }[];
};

// 選んだ都道府県の人口推移グラフを表示するコンポーネント
const Graph: React.FC<Props> = ({ populationdata }) => {
  let series: Highcharts.SeriesOptionsType[] = [];
  let categories = [];

  for (let p of populationdata) {
    let data = [];

    for (let pd of p.data) {
      data.push(pd.value);
      categories.push(String(pd.year));
    }

    series.push({
      type: "line",
      name: p.prefName,
      data: data,
    });
  }

  const options: Highcharts.Options = {
    title: {
      text: "総人口推移",
    },
    xAxis: {
      title: {
        text: "年度",
      },
      categories: categories,
    },
    yAxis: {
      title: {
        text: "人口数",
      },
    },
    // 都道府県を一つも選んでいない場合との分岐条件
    series:
      series.length === 0
        ? [{ type: "line", name: "都道府県名", data: [] }]
        : series,
  };

  return (
    <div style={Styles.graph}>
      <HighchartsReact highcharts={Highcharts} options={options} />
    </div>
  );
};

export default Graph;

APIからデータを取得し、CheckFieldコンポーネントとGraphコンポーネントを呼び出す

Main.tsx
import React, { useEffect, useState } from "react";
import CheckField from "./CheckField";
import Graph from "./Graph";

import axios from "axios";

const Styles: { [key: string]: React.CSSProperties } = {
  graph: {
    padding: "10px",
  },
  label: {
    fontSize: "20px",
    padding: "0.5rem 2rem",
    borderLeft: "4px solid #000",
    marginLeft: "10pt",
  },
};

const Main: React.FC = () => {
  const [prefectures, setPreFectures] = useState<{
    message: null;
    result: {
      prefCode: number;
      prefName: string;
    }[];
  } | null>(null);
  const [prefPopulation, setPrefPopulation] = useState<
    { prefName: string; data: { year: number; value: number }[] }[]
  >([]);

  useEffect(() => {
    // 都道府県一覧を取得する
    axios
      .get("https://opendata.resas-portal.go.jp/api/v1/prefectures", {
        headers: { "X-API-KEY": process.env.REACT_APP_API_KEY },
      })
      .then((results) => {
        setPreFectures(results.data);
      })
      .catch((error) => {});
  }, []);

  // チェックボックスをクリックした際の処理
  const handleClickCheck = (
    prefName: string,
    prefCode: number,
    check: boolean
  ) => {
    let c_prefPopulation = prefPopulation.slice();

    // チェックをつけた処理
    if (check) {
      if (
        c_prefPopulation.findIndex((value) => value.prefName === prefName) !==
        -1
      )
        return;

      axios
        .get(
          "https://opendata.resas-portal.go.jp/api/v1/population/composition/perYear?prefCode=" +
            String(prefCode),
          {
            headers: { "X-API-KEY": process.env.REACT_APP_API_KEY },
          }
        )
        .then((results) => {
          c_prefPopulation.push({
            prefName: prefName,
            data: results.data.result.data[0].data,
          });

          setPrefPopulation(c_prefPopulation);
        })
        .catch((error) => {
          return;
        });
    }
    // チェックを外した処理
    else {
      const deleteIndex = c_prefPopulation.findIndex(
        (value) => value.prefName === prefName
      );
      if (deleteIndex === -1) return;
      c_prefPopulation.splice(deleteIndex, 1);
      setPrefPopulation(c_prefPopulation);
    }
  };

  return (
    <main>
      <h2 style={Styles.label}>都道府県</h2>
      {prefectures && (
        <CheckField
          prefectures={prefectures.result}
          onChange={handleClickCheck}
        />
      )}
      <h2 style={Styles.label}>人口推移グラフ</h2>
      <Graph populationdata={prefPopulation} />
    </main>
  );
};

export default Main;

App.tsx

最後に大元のApp.tsxをこのように書き換えます。
処理は単純にMain.tsxを呼び出しているだけです.

App.tsx
import React from "react";
import Main from "./components/Main";

const App: React.FC = () => {
  return (
    <div className="App">
      <header style={{ textAlign: "center" }}>
        <h1>都道府県別人口推移</h1>
      </header>
      <Main />
    </div>
  );
};

export default App;

終わりに

最近はNext.jsでスタイル設定はTailwindCSSを使っているので、すっかり忘れてしまっていました。
自分にとって、初めて外部APIを使ったReactの開発だったので、経験すると実現できる範囲が格段に広がるような思いがありました。
ここまでお読みいただきありがとうございました。何か力になれれば幸いです。
では。

リポジトリ

https://github.com/shimapon/react-highcharts-resas-demo

Discussion