ReactでRechartsを使って折れ線グラフと棒グラフを描く方法

​​React​Playwright,Recharts,SVG,TypeScript

Recharts は、React のコンポーネントを組み合わせてグラフを描くライブラリです。<LineChart> の中に軸・ツールチップ・凡例・線を並べるだけで、SVG の折れ線グラフになります。LineChart を BarChart に、Line を Bar に替えると、同じデータで棒グラフになります。

組み立て方は短く書けますが、大きさを指定しないと何も表示されないなど、つまずきやすい点もありました。グラフの大きさ、縦軸の目盛り、描くときのアニメーションの扱い方も、Chromium で測った値とともに示します。

補足

検証環境は Ubuntu 24.04、Node.js 22.23.4 です。recharts 3.10.1、react / react-dom 19.3.1、@types/react 19.3.0、typescript 7.0.2、esbuild 0.28.2 を使い、.tsx を tsc で型チェックしてから esbuild でビルドしました。ブラウザは Playwright 1.63.0 で動かした Chromium 153(ヘッドレス)だけです。Firefox、Safari、実機のスマートフォン、Next.js などでのサーバーサイドレンダリング、古い Recharts で使う ResponsiveContainer は試していません。

Recharts と react-is を入れる

Recharts の README では、react-is もいっしょに入れるように書かれています。react-is は、使っている react と同じバージョンにそろえる必要があります。

sh
npm install recharts react-is

検証環境の npm は、react-is を指定せずに recharts を入れたときにも、peerDependencies として react-is を自動で入れていました。入ったバージョンは、react と同じ 19.3.1 でした。

データは横軸の1目盛りごとに1つのオブジェクトにする

Recharts に渡すデータは、オブジェクトの配列です。1つのオブジェクトが、横軸の1目盛り(ここでは1か月)にあたります。線や棒にしたい値は、オブジェクトのキーで指定します。

例として、Web とアプリの月ごとの利用者数(架空の数字)を使います。

ts
// data.ts
export type MonthlyVisits = {
  month: string;
  web: number;
  app: number;
};

export const visits: MonthlyVisits[] = [
  { month: '4月', web: 1200, app: 800 },
  { month: '5月', web: 1500, app: 950 },
  { month: '6月', web: 1100, app: 1300 },
  { month: '7月', web: 1800, app: 1250 },
  { month: '8月', web: 2100, app: 1600 },
  { month: '9月', web: 1900, app: 1900 },
];

折れ線グラフを描く

LineChart に data を渡し、中に部品を並べます。

tsx
// VisitsLineChart.tsx
import {
  CartesianGrid, Legend, Line, LineChart, Tooltip, XAxis, YAxis,
} from 'recharts';
import { visits } from './data';

export function VisitsLineChart() {
  return (
    <LineChart
      data={visits}
      responsive
      style={{ width: '100%', maxWidth: 640, aspectRatio: 1.6 }}
      margin={{ top: 16, right: 24, bottom: 0, left: 0 }}
    >
      <CartesianGrid strokeDasharray="3 3" />
      <XAxis dataKey="month" />
      <YAxis width={56} />
      <Tooltip formatter={(value) => `${Number(value).toLocaleString()}人`} />
      <Legend />
      <Line type="monotone" dataKey="web" name="Web" stroke="#00c8e8" strokeWidth={2} />
      <Line type="monotone" dataKey="app" name="アプリ" stroke="#9aa5ab" strokeWidth={2} />
    </LineChart>
  );
}

部品の役割は次のとおりです。

  • CartesianGrid:背景の格子線
  • XAxis:横軸。dataKey="month" で、各オブジェクトの month を目盛りの文字にする
  • YAxis:縦軸。目盛りは値の範囲から自動で決まる
  • Tooltip:グラフにマウスを乗せたときに、その月の値を出す
  • Legend:凡例
  • Line:線。dataKey で、どのキーの値を線にするかを決める

Line の name は、ツールチップと凡例に出す名前です。Line の API リファレンスのとおり、name を省くと dataKey の値(web)がそのまま出ます。type の既定値は linear です。type="monotone" を付けると、下の画像のように点と点が曲線でつながりました。

Tooltip の formatter は、ツールチップに出す値を書き換える関数です。ここでは3桁ごとにカンマを入れ、「人」を付けました。グラフの中の7月の位置にマウスを乗せると、ツールチップは「7月」「Web : 1,800人」「アプリ : 1,250人」になりました。

2本の線の折れ線グラフ。7月にマウスを乗せ、ツールチップにWeb 1,800人、アプリ 1,250人と出ている
折れ線グラフ。7月にマウスを乗せたところ

同じデータで棒グラフにする

棒グラフにするには、LineChart を BarChart に、Line を Bar に替えます。棒の色は、Line の stroke の代わりに fill で指定します。fill="#00c8e8" を渡した棒は、その色で塗られました。軸やツールチップなどの部品は、そのまま使えます。

tsx
// VisitsBarChart.tsx
import {
  Bar, BarChart, CartesianGrid, Legend, Tooltip, XAxis, YAxis,
} from 'recharts';
import { visits } from './data';

type Props = { stacked?: boolean };

export function VisitsBarChart({ stacked = false }: Props) {
  const stackId = stacked ? 'total' : undefined;
  return (
    <BarChart
      data={visits}
      responsive
      style={{ width: '100%', maxWidth: 640, aspectRatio: 1.6 }}
      margin={{ top: 16, right: 24, bottom: 0, left: 0 }}
    >
      <CartesianGrid strokeDasharray="3 3" vertical={false} />
      <XAxis dataKey="month" />
      <YAxis width={56} />
      <Tooltip formatter={(value) => `${Number(value).toLocaleString()}人`} />
      <Legend />
      <Bar dataKey="web" name="Web" fill="#00c8e8" stackId={stackId} />
      <Bar dataKey="app" name="アプリ" fill="#9aa5ab" stackId={stackId} />
    </BarChart>
  );
}

Bar を2つ並べると、1か月の中に Web とアプリの棒が横に並びます。横幅640pxのグラフで測ると、どちらの棒も幅35pxで、4pxのすきまを空けて並びました。マウスを乗せると、その月の後ろに灰色(#ccc)の帯が出ました。これは Tooltip が描くカーソルで、Tooltip の API リファレンスによると、cursor={false} を付けると描かれなくなります。

月ごとにWebとアプリの棒が横に並んだ棒グラフ。7月の後ろに灰色の帯が出ている
棒グラフ。7月にマウスを乗せたところ

2つの棒を積み上げる

Bar に同じ stackId を渡すと、棒が積み上がります。Bar の API リファレンスでは、軸と stackId が同じ Bar どうしが積み上がると説明されています。上のコードでは、stacked が true のときだけ、2つの Bar に 'total' を渡しています。

積み上げると、縦軸の目盛りは合計の値に合わせて変わりました。並べたときは0〜2200でしたが、積み上げると0〜3800になりました。9月の合計(1,900+1,900)が3,800だからです。

月ごとにWebの上にアプリを積み上げた棒グラフ。縦軸は0から3800まで
棒を積み上げたグラフ

グラフの大きさを決める

Recharts のグラフは、大きさを指定しないと何も描きません。公式ガイドのグラフの大きさの説明にも、そう書かれています。<LineChart data={visits}> だけで描いたところ、出力されたのは中身が空の <div class="recharts-wrapper"> だけでした。エラーや警告は出ませんでした。

大きさの決め方は、主に2つあります。

1つは、width={640} height={400} のように数値で指定する方法です。この場合、グラフはいつも 640×400px で描かれます。ブラウザの幅を800pxから375pxに縮めても640pxのままで、ページの横幅が656pxになり、横にスクロールが出ました。

もう1つは、この記事のコードのように responsive を付け、style で CSS の大きさを指定する方法です。width: '100%' で親の幅いっぱいに広げ、maxWidth で大きな画面での上限を決め、aspectRatio で縦横の比を保ちます。ブラウザの幅を375pxに縮めると、グラフは 640×400px から 343×214px に縮みました。ページの横幅は375pxのままでした。

ポイント

スマートフォンでも見せるグラフには、responsive と style の組み合わせが向いています。

公式ガイドによると、responsive は Recharts 3.3 で入りました。それより前のバージョンでは、ResponsiveContainer で囲むと、似たように大きさを合わせられます(公式ガイドでは、responsive ほど柔軟ではないとされています)。その場合は、ResponsiveContainer の親要素に大きさを決めておく必要があります。

縦軸の目盛りをきりのよい数にする

何も指定しないと、このデータの縦軸の目盛りは 0、550、1100、1650、2200 になりました。区切りが550では、値を読み取りにくくなります。

YAxis に domain で範囲を、ticks で目盛りの値を渡すと、目盛りを決められます。

tsx
<YAxis width={56} domain={[0, 2500]} ticks={[0, 500, 1000, 1500, 2000, 2500]} />

この1行だけを替えたところ、目盛りは 0、500、1000、1500、2000、2500 になりました。なお、domain={[0, 2500]} だけを付けたときは 650 刻み、tickCount={6} だけを付けたときは 450 刻みで、どちらもきりのよい数にはなりませんでした(この2つは、横軸のないグラフで確かめた値です)。

描くときのアニメーションを止める

Recharts は、最初に描くときに線や棒を少しずつ伸ばします。API リファレンスによると、かかる時間(animationDuration)の既定値は、Line が1,500ミリ秒、Bar が400ミリ秒です。

軸を付けない、線1本だけの 640×400px の折れ線グラフで、線の長さ(726px)のうち表示されている部分を測りました。表示を始めてから100ミリ秒後には約20px、500ミリ秒後には約388px、1,000ミリ秒後には約663pxで、1,700ミリ秒後には線全体が出ていました。

画面の画像を撮るテストなどで、描き終わるのを待ちたくないときは、isAnimationActive={false} を付けます。付けると、100ミリ秒後にはもう線全体が出ていました。

tsx
<Line dataKey="web" isAnimationActive={false} />

isAnimationActive の既定値は 'auto' です。API リファレンスでは、'auto' のときはサーバーサイドレンダリングでアニメーションをしないことと、OS などで動きを減らす設定(prefers-reduced-motion)をしているかどうかに従うことが説明されています。

Chromium で prefers-reduced-motion: reduce にして試したところ、isAnimationActive を付けていない折れ線グラフも棒グラフも、150ミリ秒後には描き終わっていました。この設定をしていないときは、150ミリ秒の時点で、棒の高さは描き終わったときの180pxに対して89pxでした。

​Posted by 86