個人開発したアプリでRechartsを使用して、悩んだポイントがあったのでその内容についてのアウトプットです。
やりたいこと
睡眠系のアプリだと必須の睡眠時間だけ色がついているようなログ表示、コンサルタントさんがまず注目する場所なので、必須の機能だったのですが、棒グラフをつかって表現することにしました。
完成したのは画面中央の左にある棒です!
ReactでグラフはRechartsだというのも聞いていたので、棒グラフの積み上げ形式でいこうと決めました!!
今回は1日単位の日付の0時~24時までを1本のグラフで描画しました。
ここで重要なのはdataです(当然ですがw)。
[{ name: 'a', value: 12 }]
[{ name: 'a', value: [5, 12] }]
わかっていなくて、一日分を1本の棒で描画したいのに、一回の睡眠データを配列の1要素としていて積み上がらず悩みました。
下記の型でデータ作っていました。
{
date: string;
time: string;
sleep: number;
awake: number;
}[]
日付のデータをlabelにして同じなら積み上がるかなと試したり。
フロント側であれこれしたけど上手くいきませんでした。
わかったこと
1本の棒はdataの配列の1要素!!
同じ日付でも配列が各睡眠(寝た→起きた)の集まりになっていた場合、仮に3回寝ていたら3本の棒グラフが描画されてしまいます。
積み上げているグラフの記事を見て、データ構造と私の内容の記事の違いを探して間違っていることに気づきました。
1日分を描画したいのだから、長さ1の配列を渡す必要がありました!!
試行錯誤する過程で用意したデータが下記のデータ。
違ったときに手戻りすごいので、データが怪しいことに気付いた時点でバックエンド書き変えずにまずはフロントでデータをベタ打ちしてうまくいく型を探しました。
const data = {
date: "06/29",
sleep0: 428,
awake0: 111,
sleep1: 90,
awake1: 249,
sleep2: 60,
awake2: 230,
sleep3: 150,
awake3: 122,
},
・・・(中略)・・・
<Bar dataKey="awake0" stackId="a" fill="#c2c2f8" />
<Bar dataKey="sleep1" stackId="a" fill="#FF8042" />
<Bar dataKey="awake1" stackId="a" fill="#c2c2f8" />
<Bar dataKey="sleep1" stackId="a" fill="#FF8042" />
<Bar dataKey="awake2" stackId="a" fill="#c2c2f8" />
<Bar dataKey="sleep2" stackId="a" fill="#FF8042" />
<Bar dataKey="awake3" stackId="a" fill="#c2c2f8" />
<Bar dataKey="sleep3" stackId="a" fill="#FF8042" />
これで成功しました!
この型で用意したらしたい形に描画できることがわかったのでバックエンド書き変えて、最終的に完成したコードが下記です。
import React from "react";
import { BarChart, Bar, YAxis, XAxis, Tooltip } from "recharts";
import { ChartData } from "@/app/_types/apiRequests/dashboard/sleep";
interface Props {
chartData: ChartData | undefined;
keyName: string[] | undefined;
}
export const Chart: React.FC<Props> = ({ chartData, keyName }) => {
if (!chartData || !keyName)
return <div className="text-center">データなし</div>;
const tooltipFormatter = (value: number, name: any) => {
const hour = Math.floor(value / 60);
const min = value % 60;
return [`${hour}時間${min}分`, name];
};
const ticks = Array.from({ length: 9 }, (_, i) => i * 3 * 60);
const tickFormatter = (value: number) => {
const hour = value / 60;
return `${hour}時`;
};
return (
<BarChart
width={100}
height={300}
data={[chartData]}
layout="horizontal"
barSize={20}
margin={{ top: 10, right: 5, left: 0, bottom: 10 }}
>
<XAxis type="category" dataKey="date" hide />
<YAxis
type="number"
ticks={ticks}
tickFormatter={tickFormatter}
domain={[0, 24 * 60]}
interval={0}
reversed={true}
/>
<Tooltip formatter={tooltipFormatter} />
{keyName.map((item, index) => {
if (item.includes("活動時間")) {
return <Bar dataKey={item} stackId="a" fill="#c2c2f8" key={index} />;
} else {
return <Bar dataKey={item} stackId="a" fill="#3B82F6" key={index} />;
}
})}
</BarChart>
);
};
PropsのchartDataがどんなデータ型なのかですが下記のように定義しています。
export interface ChartData {
date: string;
[key: string]: number | string;
}
dateは
<XAxis type="category" dataKey="date" hide />
で使い日付をカテゴリーとしています。
日付重複しないようにデータを用意する必要がありますし、
そもそも今回は長さ1の配列で用意する必要があります。
データ(data)の実際のデータ例は下記のような感じです。
{1:睡眠時間:405,
2:活動時間: 152,
3:睡眠時間: 105,
4:活動時間: 176,
5:睡眠時間: 100,
6:活動時間:334,
7:睡眠時間: 165,
date: "7/31"}
ただのオブジェクトなので、BarChartのコンポーネントに渡すタイミングでオブジェクトごと配列に入れて、長さ1のオブジェクトの配列にしています。
フロントの問題と思ってコンポーネントに渡す値をああじゃないこうじゃないってやっていましたが、問題はデータで気づいたら簡単に解決しました!
まとめ
dataの配列の長さが棒グラフの棒の数になるところが大事なところでした。
しかしこれは、XAxisのtypeなどにより異なると思います。
今回のような日付をカテゴリーとして扱い、一日あたりの睡眠時間を積み上げたいパターンの場合の話だと思います。