はじめに
React アプリケーションにおいてグラフの UI を作成したいというのはよくあると思うが、3rd パーティ製ライブラリの選択肢が非常に多く、どれが良いのか迷ってしまいがちである。
React のグラフライブラリ紹介記事などでいくつかみてみたところ、Recharts がバランスが良さそうに感じたので使ってみることにした。
やりたかったこと
あるグラフの上に別のタイプのグラフを重ねる
例えば、折れ線グラフで示した平均気温のデータを、棒グラフで示した降水量のデータに重ねて表示するようなシチュエーションである。
Area Chart の上下反転
面グラフ (Area Chart)1 は通常、グラフ縦軸の下から積み上がっていくが、これを「上から積み下げる」ような形で表示したい。
環境・前提
- react: 18.3.1
- next: 14.2.3
- recharts: 2.13.0-alpha.1
storybook で概要を確認
ウェブ
下記 URL にて、ウェブ上で稼働している storybook があるため、いち早くグラフの見た目や使い方をサクッと確認するにはベスト。
ローカル
既存の storybook に少し手を加えながら動作確認してみたい場合は、ローカルで storybook を起動し、コードをいじりながらやるのが良い。
$ git clone https://github.com/recharts/recharts.git
$ cd recharts
$ npm install
$ npm run storybook
作業
ここからは Next.js アプリへの Recharts の導入と使用方法について記述する。
導入
npm install recharts
ユースケース 1: あるグラフの上に別のタイプのグラフを重ねる
import {
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
LineChart,
Line,
BarChart,
Bar,
} from 'recharts';
// データ
const data = [
{
month: 'Jan',
avgTemp: 3.1,
sumPcpn: 50.6,
},
{
month: 'Feb',
avgTemp: 4.2,
sumPcpn: 47.1,
},
{
month: 'Mar',
avgTemp: 7.7,
sumPcpn: 95.5,
},
{
month: 'Apr',
avgTemp: 12.8,
sumPcpn: 109.8,
},
{
month: 'May',
avgTemp: 17.4,
sumPcpn: 129.8,
},
{
month: 'Jun',
avgTemp: 20.8,
sumPcpn: 131.8,
},
{
month: 'Jul',
avgTemp: 24.6,
sumPcpn: 134.6,
},
{
month: 'Aug',
avgTemp: 25.9,
sumPcpn: 118.2,
},
{
month: 'Sep',
avgTemp: 22.3,
sumPcpn: 187.6,
},
{
month: 'Oct',
avgTemp: 16.6,
sumPcpn: 193.5,
},
{
month: 'Nov',
avgTemp: 10.5,
sumPcpn: 79.1,
},
{
month: 'Dec',
avgTemp: 5.3,
sumPcpn: 48.5,
},
];
export const ErrorBudget = () => {
const w = 600;
const h = 400;
return (
<ResponsiveContainer width="100%" height="100%">
<div style={{ position: 'relative' }}> // ①
<BarChart
data={data}
width={w}
height={h}
style={{
position: 'absolute', // ①
left: '34px', // ②
width: '100%',
}}
margin={{
top: 0,
right: 0,
left: -48,
bottom: 0,
}}
barSize={20}
>
<Bar dataKey="sumPcpn" fill="royalblue" />
<XAxis tick={false} />
<YAxis
fontSize="0.5rem"
domain={[0, 250]} // ③
orientation="right" // ②
tickFormatter={(value) => `${value}mm`}
/>
</BarChart>
<LineChart
width={w}
height={h}
data={data}
style={{
position: 'absolute', // ①
left: '-60px', // ②
width: '100%',
}}
margin={{
top: 0,
right: 0,
bottom: 0,
left: 0,
}}
>
<Tooltip />
<XAxis dataKey="month" fontSize="0.5rem" interval={0} />
<YAxis
fontSize="0.5rem"
domain={[0, 35]} // ③
tickFormatter={(value) => `${value}℃`}
/>
<Line
type="monotone"
dataKey="avgTemp"
stroke="tomato"
strokeWidth={2}
/>
</LineChart>
</div>
</ResponsiveContainer>
);
};
このコードでは、data
で定義した 12 ヶ月分の{月平均気温 (avgTemp
)、月間降水量 (sumPcpn
)}の情報をグラフに表示している。LineChart
が月平均気温を、BarChart
が月間降水量に対応している。
詳細の説明は API ドキュメントや storybook に譲るが、実装上のポイントとなると思った点を以下に挙げる:
- 二つのグラフを位置的にざっくり重ね合わせる (①)
-
position='relative'
のプロパティを持った div の下に二つのグラフ (BarChart
,LineChart
) を配置し、それらをposition='absolute'
指定することで、親要素からの相対距離で位置を指定する。
-
- 一つのグラフの Y 軸は右側に持ってくる (②)
- デフォルトでは Y 軸はグラフ左側に表示される。メモリが重なるとみづらいため、降水量データの Y 軸を右側に移動させる。
<BarChart>
><YAxis>
>orientation="right"
を指定することで実現できる。なおこの際、グラフのデータ部分が左側に寄ってしまうため、BarChart, LineChart それぞれのstyle.left
を調整することで表示崩れを整えている。
- デフォルトでは Y 軸はグラフ左側に表示される。メモリが重なるとみづらいため、降水量データの Y 軸を右側に移動させる。
- Y 軸範囲を設定する (③)
-
<BarChart>
><YAxis>
>domain
および<LineChart>
><YAxis>
>domain
に上下限値を指定する。
-
これによって作成されたグラフの一例が以下である
ユースケース 2: Area Chart の上下反転
import { ResponsiveContainer, XAxis, YAxis, AreaChart, Area } from 'recharts';
// データ
const data = [
{
month: 'Jan',
sumPcpn: 50.6,
sumSnow: 60,
dummy: 0,
},
{
month: 'Feb',
sumPcpn: 47.1,
sumSnow: 50,
dummy: 0,
},
{
month: 'Mar',
sumPcpn: 95.5,
sumSnow: 10,
dummy: 0,
},
{
month: 'Apr',
sumPcpn: 109.8,
sumSnow: 0,
dummy: 0,
},
{
month: 'May',
sumPcpn: 129.8,
sumSnow: 0,
dummy: 0,
},
{
month: 'Jun',
sumPcpn: 131.8,
sumSnow: 0,
dummy: 0,
},
{
month: 'Jul',
sumPcpn: 134.6,
sumSnow: 0,
dummy: 0,
},
{
month: 'Aug',
sumPcpn: 118.2,
sumSnow: 0,
dummy: 0,
},
{
month: 'Sep',
sumPcpn: 187.6,
sumSnow: 0,
dummy: 0,
},
{
month: 'Oct',
sumPcpn: 193.5,
sumSnow: 0,
dummy: 0,
},
{
month: 'Nov',
sumPcpn: 79.1,
sumSnow: 0,
dummy: 0,
},
{
month: 'Dec',
sumPcpn: 48.5,
sumSnow: 10,
dummy: 0,
},
];
export const ErrorBudget = () => {
const w = 600;
const h = 400;
// ①
const maxSum = data.reduce(
(acc, cur) => Math.max(acc, cur.sumPcpn + cur.sumSnow),
0,
);
// ①
const data2 = data.map((x) =>
Object.assign(x, { dummy: maxSum - (x.sumPcpn + x.sumSnow) }),
);
return (
<ResponsiveContainer>
<div>
<AreaChart data={data2} width={w} height={h}>
<XAxis dataKey="month" fontSize="0.5rem" interval={0} />
<YAxis
fontSize="0.5rem"
domain={[0, maxSum]} // ①
tickFormatter={(value) => `${value}mm`}
/>
// ②
<Area
type="monotone"
dataKey="dummy"
stackId="1"
stroke="seashell"
fill="seashell"
/>
<Area
type="monotone"
dataKey="sumSnow"
stackId="1"
stroke="powderblue"
fill="powderblue"
/>
<Area
type="monotone"
dataKey="sumPcpn"
stackId="1"
stroke="royalblue"
fill="royalblue"
/>
</AreaChart>
</div>
</ResponsiveContainer>
);
};
このコードでは、data
で定義した 12 ヶ月分の{月間降水量 (sumPcpn
)、月間降雪量 (sumSnow
)}の情報を二つの AreaChart グラフに表示している。
実装上のポイントは以下:
- AreaChart を上下反転(上から積み下げていく)ためには、一番下の層となる部分も何らかのデータが必要となるため、それを
dummy
として定義する。dummy の値は、「各月の降水量と降雪量の値の合計値に足すと全てが等しくなるような最小の数」ということになる。つまり、降水量と降雪量の最大値から、各月に対する降水量と降雪量の合計を引いたものを、各月に足せば良い ので、その辺りの計算を行っている (①)。 -
<AreaChart>
内の<Area>
は、記述順に下から上に積み重なっていく。そのため、前述の dummy 層を最初に記述し、次は降雪量、最後が降水量という順番にすることで、その逆順に上から下に向けて積み下がっていくような表現になる (②)。
これによって作成されたグラフの一例が以下である
終わりに
Recharts を使えば、基本的なグラフ描画には困らないと感じた。また、本ライブラリは React18 にも対応しているが、他のいくつかのグラフライブラリでは対応していないものもあるようで、重要なファクターである。さらに、最近のライブラリでは storybook の整備が充実していることが多く、開発体験をより加速させてくれて大変ありがたい。