Reactとd3で健康オタクのためのスタバサイトを作ってみた!

2024/01/02に公開

はじめに

初記事です!よろしくお願いします。

今回、スタバで作業したいけど健康も守りたいという全ての人に向けて、スタバの商品の栄養成分量を比較できるWebサイトを作りました。Pythonでスクレイピングしたデータをd3.jsで可視化してReactに組み込むという流れで作ってます!

健康オタク向けのスタバサイト
👆このリンクからアクセスできます。

スマホで開くと一番綺麗に見えます。
(React書き疲れて発狂したので全画面サイズ対応は断念しました😇)

こんな感じのUIになってます👇

今回はReactとd3の勉強の一環でこの2つを合体させてみたかったので、あえてReactを使ってサイトを作成しましたが、効率を優先するなら普通にHTMLでJavaScriptファイルを読み込む形式で十分だと思います。

この記事では開発の流れと工夫したことをまとめます。
Reactとd3を使ってデータ可視化サイトをつくるときの大まかな流れが伝わればいいなと思います。
「Reactでデータを可視化してみたい!」というフロント開発の初心者向けです!

1. スクレイピング

データを可視化したいならまずそのデータを取ってこなきゃいけないですね。データ取得の方法は(1)CSVファイルのダウンロード、(2)手動入力、(3)APIを叩く、(4)HTMLを取得して解析する、(5)ブラウザの自動操作、などがあるかと思います。

優先順位としては(1)から順に辿って可能なものを選ぶ感じが効率的だと思います。
(手動入力はデータ数が少ない時や補完の時ですね。)
今回は動的なサイトからデータを取得したかったので仕方なく自動操作しました。

PythonのSelenium、BeautifulSoupというライブラリを使っています。
今の時代、ChatGPTに頼れば簡単に動かせますね😅

2023-12-29 18:28:29,499 - INFO - Scraping the list page.
2023-12-29 18:28:29,852 - INFO - Successfully retrieved the list of data.
2023-12-29 18:29:40,010 - INFO - Scraping the data of "https://menu.starbucks.co.jp/4524785556560"
2023-12-29 18:30:00,108 - WARNING - Failed to choose Tall size during processing "ほうじ茶 もちっと ミルク フラペチーノ®" of "https://menu.starbucks.co.jp/4524785556560"
2023-12-29 18:30:00,225 - INFO - Successfully retrieved the data of https://menu.starbucks.co.jp/4524785556560
2023-12-29 18:30:45,860 - INFO - Scraping the data of "https://menu.starbucks.co.jp/4524785344143"
2023-12-29 18:30:56,420 - INFO - Successfully retrieved the data of https://menu.starbucks.co.jp/4524785344143
2023-12-29 18:31:17,437 - INFO - Scraping the data of "https://menu.starbucks.co.jp/4524785165731"
2023-12-29 18:31:23,834 - INFO - Successfully retrieved the data of https://menu.starbucks.co.jp/4524785165731
2023-12-29 18:31:37,299 - INFO - Scraping the data of "https://menu.starbucks.co.jp/4524785166059"
2023-12-29 18:31:43,475 - INFO - Successfully retrieved the data of https://menu.starbucks.co.jp/4524785166059

こんな感じでログを残して不具合をトレースできるようにしました。一応リトライ処理も入れてます。あとでデータの確認したい時に時短になるのでログは大事だなって思いました。ただブラウザ操作に関してはコード書くのも実際に動かすのも結構時間かかるので、誰かAPI作ってください...。

2. データの加工

データの加工も全部Pythonでやりました。CSVファイルのデータについてはpandasを使って加工しています。商品の画像については、元データが正方形なのでPILというライブラリを使って円形に切り抜いてます。これが結構綺麗で個人的に好きです笑。この辺も生成AIに頼れば誰でも簡単にできますね。

3. d3のアニメーションの構築

データが整ったらいよいよ可視化です。今回は学科実験で学んだd3.jsを使いました。
ソート可能な棒グラフは以下のページを参考に作ってます。

https://observablehq.com/@d3/bar-chart-transitions/2?intent=fork

d3.jsはアニメーションの自由度が高いので、つぎはぎでコーディングすると何が何だかわからなくなります。これはReactも同じですが、、。

そこで、今回は作りたいアニメーション機能を紙に過不足なく書き出してトリガーとの関係を整理しました。次に所望のアニメーションをできる限り小さい単位ごとに実装&テストして、最後に実装したアニメーションを組み合わせました。まずはHTMLファイルと一つのJavaScriptファイルで完成させて、最後にReactに組み込むのが一番作りやすいと思います!

itemCategorySelector.addEventListener("change", updateFilteredData);
categorySelector.addEventListener("change", onCategoryChange);
sortSelector.addEventListener("change", () => onSelectorChange(0.3));

プログラムについて細かくは触れませんが、HTMLベースのプログラムの段階では、上記のようなJavaScriptのリスナーメソッドでセレクタごとにその値が変わった時のアニメーション関数が実行されるようにしました。

4. Reactのコンポーネント設計

d3のアニメーションが完成したら、後はそれをReactに組み込むだけです。
Reactはd3以上に強敵です。設計図なしに作り始めると地獄を見ます(経験談)。
そこでUIのパーツごとにコンポーネントを分割してツリー構造を紙に書き出しました。
今回のコンポーネントの階層構造はこんな感じです。

これに加えて、Global Stateを管理するためのカスタムフックを定義するItemHooks.jsというファイルも用意しました。これについては次節で紹介します。

5. Reactの状態管理

Reactで沼らないためには、状態管理の方法も設計の段階で厳格に決めた方が良いです。
今回、あるコンポーネント内だけで使用する変数はStateとして管理し、ツリー構造の異なる枝からアクセスしたい変数にはuseContextを使ってGloabl Stateとして管理しました。具体的には、データの項目やソート順は前者、商品カテゴリや現在表示したいデータ配列は後者の方法です。

Global Stateを設定するカスタムフックのjsファイルは次の通りです。

import { useState } from 'react'

const useItemCategory = () => {
  const [itemCategory, setItemCategory] = useState("フラペチーノ®")
  const [contextAllData, setContextAllData] = useState()
  const [contextData, setContextData] = useState()
  const [contextChart, setContextChart] = useState()

  return {
    itemCategory,
    setItemCategory,
    contextAllData,
    setContextAllData,
    contextData,
    setContextData,
    contextChart,
    setContextChart
  }
}

export default useItemCategory

工夫したこととしては、初回に読み込む全データ群と、現在表示したいデータ群、描画するグラフのコンポーネントについても、無駄なレンダリングを防ぐために、Global Stateとして一括管理してます。これをd3のコンポーネント内でuseStateを使って管理すると、表示データの更新の度にuseEffectが起動して無限ループに入ってしまうのでやめました...。

6. d3のReactへの組み込み

HTMLベースで作ったd3のアニメーションと、Reactの状態管理を踏まえて、最後にd3をReactに組み込みます!

各状態の変化に対して、3章で述べたように所望のアニメーションだけを発火させたいので、これをReactで実現するために、トリガーごとにuseEffectを分割して複数用意しました。これで、トリガーごとにアニメーションを綺麗に分岐させられます!!

あとは、各useEffectに適切な関数を割り当てればちゃんとd3のアニメーションがReactの中で再現されます。(感動!)

おわりに

今回はデータの取得、加工、d3グラフの作成、Reactへの組み込みの4ステップで合計50時間くらいかかりました...。😭😭😭
最初の設計をちゃんとして、Reactのコンポーネント設計や状態管理に熟達すればもっと短時間で作れると思います。
あと、今回のサイトは中身のデータだけ入れ替えればスタバ以外の店舗にも対応できると思うので気が向いたらやりたいと思います(伏線)。

最後まで読んでいただきありがとうございました!

参考サイト

Discussion