はじめに

Reactアプリケーションでデータ可視化を実装する際、グラフライブラリの選択は重要な決定事項です。Rechartsは、ReactとD3.jsをベースに構築された宣言的なチャートライブラリで、Reactコンポーネントとしてグラフを直感的に作成できます。

本記事では、ReactとTypeScriptを使用してRechartsでインタラクティブなグラフを作成する方法を解説します。折れ線グラフ、棒グラフ、円グラフといった基本的なグラフから、ツールチップや凡例のカスタマイズ、レスポンシブ対応、さらにはリアルタイムデータ更新まで、実務で必要となるデータ可視化のテクニックを網羅します。

本記事を読むことで、以下のことができるようになります。

  • Rechartsの導入とセットアップ
  • 折れ線グラフ、棒グラフ、円グラフの実装
  • ツールチップ・凡例のカスタマイズ
  • レスポンシブ対応のグラフ作成
  • リアルタイムデータ更新の実装
  • 実務でのグラフ活用パターン

実行環境・前提条件

必要な環境

  • Node.js 20.x以上
  • React 18.x以上
  • TypeScript 5.x以上
  • VS Code(推奨)

前提知識

  • Reactコンポーネントの基本
  • TypeScriptの型定義
  • useStateとuseEffectの使い方
  • npm/yarnによるパッケージ管理

Rechartsの概要と特徴

Rechartsとは

Rechartsは、ReactとD3.jsをベースに構築されたチャートライブラリです。2024年現在、週間700万以上のダウンロード数を誇り、Reactエコシステムで最も人気のあるグラフライブラリの一つです。

Rechartsの主な特徴

Rechartsには以下のような特徴があります。

宣言的なコンポーネント設計

Rechartsは完全にReactコンポーネントとして設計されています。JSXの中でグラフを構築でき、propsを通じてデータやスタイルを渡すことができます。

SVGベースの軽量設計

グラフはSVGで描画されるため、解像度に依存せず美しい表示が可能です。また、最小限の依存関係で軽量に動作します。

豊富なグラフタイプ

折れ線グラフ、棒グラフ、円グラフ、エリアグラフ、散布図、レーダーチャートなど、多様なグラフタイプをサポートしています。

インタラクティブ機能

ツールチップ、凡例、ズーム、ブラシなど、ユーザーとのインタラクションを実現する機能が豊富に用意されています。

TypeScriptサポート

型定義が同梱されており、TypeScriptプロジェクトでも安心して使用できます。

セットアップ手順

プロジェクトの作成

まず、Viteを使用してReact + TypeScriptプロジェクトを作成します。

1
2
npm create vite@latest recharts-demo -- --template react-ts
cd recharts-demo

Rechartsのインストール

Rechartsと必要な依存関係をインストールします。

1
npm install recharts react-is

Recharts v3.x以降では、react-isパッケージも必要となります。react-isのバージョンは、プロジェクトで使用しているReactのバージョンと一致させてください。

インストールの確認

package.jsonでインストールが正常に完了したことを確認します。

1
2
3
4
5
6
7
8
{
  "dependencies": {
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-is": "^18.3.1",
    "recharts": "^3.6.0"
  }
}

基本的なグラフ実装例

サンプルデータの準備

まず、グラフで使用するサンプルデータを定義します。TypeScriptの型定義も含めて作成します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// src/data/sampleData.ts
export interface SalesData {
  month: string;
  sales: number;
  profit: number;
  customers: number;
}

export const monthlySalesData: SalesData[] = [
  { month: '1月', sales: 4000, profit: 2400, customers: 240 },
  { month: '2月', sales: 3000, profit: 1398, customers: 210 },
  { month: '3月', sales: 2000, profit: 9800, customers: 290 },
  { month: '4月', sales: 2780, profit: 3908, customers: 200 },
  { month: '5月', sales: 1890, profit: 4800, customers: 181 },
  { month: '6月', sales: 2390, profit: 3800, customers: 250 },
  { month: '7月', sales: 3490, profit: 4300, customers: 210 },
];

export interface CategoryData {
  name: string;
  value: number;
}

export const categoryData: CategoryData[] = [
  { name: '電子機器', value: 400 },
  { name: '衣料品', value: 300 },
  { name: '食品', value: 300 },
  { name: '書籍', value: 200 },
  { name: 'その他', value: 100 },
];

折れ線グラフ(LineChart)の実装

折れ線グラフは、時系列データや連続的な変化を表現するのに適しています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// src/components/SalesLineChart.tsx
import {
  LineChart,
  Line,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  Legend,
  ResponsiveContainer,
} from 'recharts';
import { monthlySalesData } from '../data/sampleData';

export const SalesLineChart = () => {
  return (
    <div style={{ width: '100%', height: 400 }}>
      <h3>月別売上推移(折れ線グラフ)</h3>
      <ResponsiveContainer width="100%" height="100%">
        <LineChart
          data={monthlySalesData}
          margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
        >
          <CartesianGrid strokeDasharray="3 3" />
          <XAxis dataKey="month" />
          <YAxis />
          <Tooltip />
          <Legend />
          <Line
            type="monotone"
            dataKey="sales"
            stroke="#8884d8"
            name="売上"
            strokeWidth={2}
          />
          <Line
            type="monotone"
            dataKey="profit"
            stroke="#82ca9d"
            name="利益"
            strokeWidth={2}
          />
        </LineChart>
      </ResponsiveContainer>
    </div>
  );
};

このコンポーネントでは、以下の要素を使用しています。

  • LineChart: 折れ線グラフのコンテナコンポーネント
  • Line: データ系列を表す線
  • XAxis / YAxis: X軸とY軸
  • CartesianGrid: グリッド線
  • Tooltip: マウスホバー時の情報表示
  • Legend: 凡例
  • ResponsiveContainer: レスポンシブ対応のラッパー

棒グラフ(BarChart)の実装

棒グラフは、カテゴリ間の比較やデータの大小を視覚的に表現するのに適しています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// src/components/SalesBarChart.tsx
import {
  BarChart,
  Bar,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  Legend,
  ResponsiveContainer,
} from 'recharts';
import { monthlySalesData } from '../data/sampleData';

export const SalesBarChart = () => {
  return (
    <div style={{ width: '100%', height: 400 }}>
      <h3>月別売上比較(棒グラフ)</h3>
      <ResponsiveContainer width="100%" height="100%">
        <BarChart
          data={monthlySalesData}
          margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
        >
          <CartesianGrid strokeDasharray="3 3" />
          <XAxis dataKey="month" />
          <YAxis />
          <Tooltip />
          <Legend />
          <Bar dataKey="sales" fill="#8884d8" name="売上" />
          <Bar dataKey="profit" fill="#82ca9d" name="利益" />
        </BarChart>
      </ResponsiveContainer>
    </div>
  );
};

積み上げ棒グラフの実装

複数のデータ系列を積み上げて表示する場合は、stackIdプロパティを使用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// src/components/StackedBarChart.tsx
import {
  BarChart,
  Bar,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  Legend,
  ResponsiveContainer,
} from 'recharts';
import { monthlySalesData } from '../data/sampleData';

export const StackedBarChart = () => {
  return (
    <div style={{ width: '100%', height: 400 }}>
      <h3>月別売上構成(積み上げ棒グラフ)</h3>
      <ResponsiveContainer width="100%" height="100%">
        <BarChart
          data={monthlySalesData}
          margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
        >
          <CartesianGrid strokeDasharray="3 3" />
          <XAxis dataKey="month" />
          <YAxis />
          <Tooltip />
          <Legend />
          <Bar dataKey="sales" stackId="a" fill="#8884d8" name="売上" />
          <Bar dataKey="profit" stackId="a" fill="#82ca9d" name="利益" />
        </BarChart>
      </ResponsiveContainer>
    </div>
  );
};

円グラフ(PieChart)の実装

円グラフは、全体に対する各要素の割合を表現するのに適しています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// src/components/CategoryPieChart.tsx
import { PieChart, Pie, Cell, Tooltip, Legend, ResponsiveContainer } from 'recharts';
import { categoryData } from '../data/sampleData';

const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884D8'];

export const CategoryPieChart = () => {
  return (
    <div style={{ width: '100%', height: 400 }}>
      <h3>カテゴリ別売上構成(円グラフ)</h3>
      <ResponsiveContainer width="100%" height="100%">
        <PieChart>
          <Pie
            data={categoryData}
            cx="50%"
            cy="50%"
            labelLine={false}
            label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
            outerRadius={120}
            fill="#8884d8"
            dataKey="value"
          >
            {categoryData.map((_, index) => (
              <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
            ))}
          </Pie>
          <Tooltip />
          <Legend />
        </PieChart>
      </ResponsiveContainer>
    </div>
  );
};

ドーナツグラフの実装

円グラフの中央を空洞にしたドーナツグラフは、innerRadiusプロパティで実現できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// src/components/DonutChart.tsx
import { PieChart, Pie, Cell, Tooltip, Legend, ResponsiveContainer } from 'recharts';
import { categoryData } from '../data/sampleData';

const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884D8'];

export const DonutChart = () => {
  const total = categoryData.reduce((sum, item) => sum + item.value, 0);

  return (
    <div style={{ width: '100%', height: 400, position: 'relative' }}>
      <h3>カテゴリ別構成(ドーナツグラフ)</h3>
      <ResponsiveContainer width="100%" height="100%">
        <PieChart>
          <Pie
            data={categoryData}
            cx="50%"
            cy="50%"
            innerRadius={60}
            outerRadius={100}
            fill="#8884d8"
            dataKey="value"
          >
            {categoryData.map((_, index) => (
              <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
            ))}
          </Pie>
          <Tooltip />
          <Legend />
        </PieChart>
      </ResponsiveContainer>
      <div
        style={{
          position: 'absolute',
          top: '55%',
          left: '50%',
          transform: 'translate(-50%, -50%)',
          textAlign: 'center',
        }}
      >
        <div style={{ fontSize: '24px', fontWeight: 'bold' }}>{total}</div>
        <div style={{ fontSize: '12px', color: '#666' }}>合計</div>
      </div>
    </div>
  );
};

ツールチップ・凡例・レスポンシブ対応

カスタムツールチップの実装

Rechartsのデフォルトツールチップをカスタマイズして、より見やすい情報を表示できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// src/components/CustomTooltip.tsx
import { TooltipProps } from 'recharts';
import { NameType, ValueType } from 'recharts/types/component/DefaultTooltipContent';

interface CustomTooltipProps extends TooltipProps<ValueType, NameType> {}

export const CustomTooltip = ({ active, payload, label }: CustomTooltipProps) => {
  if (!active || !payload || payload.length === 0) {
    return null;
  }

  return (
    <div
      style={{
        backgroundColor: 'white',
        padding: '12px',
        border: '1px solid #ccc',
        borderRadius: '8px',
        boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)',
      }}
    >
      <p style={{ margin: 0, fontWeight: 'bold', marginBottom: '8px' }}>{label}</p>
      {payload.map((entry, index) => (
        <p
          key={index}
          style={{
            margin: 0,
            color: entry.color,
            fontSize: '14px',
          }}
        >
          {entry.name}: {Number(entry.value).toLocaleString()}
        </p>
      ))}
    </div>
  );
};

カスタムツールチップをグラフに適用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// src/components/ChartWithCustomTooltip.tsx
import {
  LineChart,
  Line,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  Legend,
  ResponsiveContainer,
} from 'recharts';
import { monthlySalesData } from '../data/sampleData';
import { CustomTooltip } from './CustomTooltip';

export const ChartWithCustomTooltip = () => {
  return (
    <ResponsiveContainer width="100%" height={400}>
      <LineChart data={monthlySalesData}>
        <CartesianGrid strokeDasharray="3 3" />
        <XAxis dataKey="month" />
        <YAxis />
        <Tooltip content={<CustomTooltip />} />
        <Legend />
        <Line type="monotone" dataKey="sales" stroke="#8884d8" name="売上" />
        <Line type="monotone" dataKey="profit" stroke="#82ca9d" name="利益" />
      </LineChart>
    </ResponsiveContainer>
  );
};

カスタム凡例の実装

凡例もカスタマイズして、クリックで系列の表示/非表示を切り替える機能を追加できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// src/components/ChartWithToggleLegend.tsx
import { useState } from 'react';
import {
  LineChart,
  Line,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  Legend,
  ResponsiveContainer,
} from 'recharts';
import { monthlySalesData } from '../data/sampleData';

interface VisibilityState {
  sales: boolean;
  profit: boolean;
}

export const ChartWithToggleLegend = () => {
  const [visibility, setVisibility] = useState<VisibilityState>({
    sales: true,
    profit: true,
  });

  const handleLegendClick = (dataKey: string) => {
    setVisibility((prev) => ({
      ...prev,
      [dataKey]: !prev[dataKey as keyof VisibilityState],
    }));
  };

  const renderLegend = () => {
    const items = [
      { dataKey: 'sales', color: '#8884d8', name: '売上' },
      { dataKey: 'profit', color: '#82ca9d', name: '利益' },
    ];

    return (
      <div style={{ display: 'flex', justifyContent: 'center', gap: '20px' }}>
        {items.map((item) => (
          <div
            key={item.dataKey}
            onClick={() => handleLegendClick(item.dataKey)}
            style={{
              cursor: 'pointer',
              opacity: visibility[item.dataKey as keyof VisibilityState] ? 1 : 0.3,
              display: 'flex',
              alignItems: 'center',
              gap: '4px',
            }}
          >
            <div
              style={{
                width: '12px',
                height: '12px',
                backgroundColor: item.color,
                borderRadius: '2px',
              }}
            />
            <span>{item.name}</span>
          </div>
        ))}
      </div>
    );
  };

  return (
    <div style={{ width: '100%', height: 400 }}>
      <h3>クリックで系列を切り替え</h3>
      <ResponsiveContainer width="100%" height="90%">
        <LineChart data={monthlySalesData}>
          <CartesianGrid strokeDasharray="3 3" />
          <XAxis dataKey="month" />
          <YAxis />
          <Tooltip />
          <Legend content={renderLegend} />
          {visibility.sales && (
            <Line type="monotone" dataKey="sales" stroke="#8884d8" name="売上" />
          )}
          {visibility.profit && (
            <Line type="monotone" dataKey="profit" stroke="#82ca9d" name="利益" />
          )}
        </LineChart>
      </ResponsiveContainer>
    </div>
  );
};

レスポンシブ対応

Rechartsでレスポンシブなグラフを作成するには、ResponsiveContainerコンポーネントを使用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// src/components/ResponsiveChart.tsx
import {
  BarChart,
  Bar,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  Legend,
  ResponsiveContainer,
} from 'recharts';
import { monthlySalesData } from '../data/sampleData';

export const ResponsiveChart = () => {
  return (
    <div style={{ width: '100%', maxWidth: '800px', margin: '0 auto' }}>
      <h3>レスポンシブ対応グラフ</h3>
      {/* 親要素にheightを指定する必要があります */}
      <div style={{ width: '100%', height: '300px' }}>
        <ResponsiveContainer width="100%" height="100%">
          <BarChart
            data={monthlySalesData}
            margin={{ top: 5, right: 20, left: 10, bottom: 5 }}
          >
            <CartesianGrid strokeDasharray="3 3" />
            <XAxis dataKey="month" tick={{ fontSize: 12 }} />
            <YAxis tick={{ fontSize: 12 }} />
            <Tooltip />
            <Legend wrapperStyle={{ fontSize: '12px' }} />
            <Bar dataKey="sales" fill="#8884d8" name="売上" />
          </BarChart>
        </ResponsiveContainer>
      </div>
    </div>
  );
};

ResponsiveContainerを使用する際の注意点として、親要素に明示的な高さを指定する必要があります。height: 100%のみでは正しく動作しません。

リアルタイムデータ更新の実装

useStateとuseEffectによるリアルタイム更新

リアルタイムでデータが更新されるグラフを実装します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// src/components/RealtimeChart.tsx
import { useState, useEffect, useCallback } from 'react';
import {
  LineChart,
  Line,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  ResponsiveContainer,
} from 'recharts';

interface RealtimeData {
  time: string;
  value: number;
}

const generateDataPoint = (): RealtimeData => {
  const now = new Date();
  return {
    time: now.toLocaleTimeString('ja-JP'),
    value: Math.floor(Math.random() * 100) + 50,
  };
};

export const RealtimeChart = () => {
  const [data, setData] = useState<RealtimeData[]>(() => {
    // 初期データを生成
    return Array.from({ length: 10 }, () => generateDataPoint());
  });
  const [isRunning, setIsRunning] = useState(true);

  const addDataPoint = useCallback(() => {
    setData((prevData) => {
      const newData = [...prevData, generateDataPoint()];
      // 最新の20件のみ保持
      return newData.slice(-20);
    });
  }, []);

  useEffect(() => {
    if (!isRunning) return;

    const intervalId = setInterval(addDataPoint, 1000);

    return () => clearInterval(intervalId);
  }, [isRunning, addDataPoint]);

  return (
    <div style={{ width: '100%', height: 400 }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
        <h3>リアルタイムデータ更新</h3>
        <button
          onClick={() => setIsRunning(!isRunning)}
          style={{
            padding: '8px 16px',
            backgroundColor: isRunning ? '#ff4444' : '#44aa44',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer',
          }}
        >
          {isRunning ? '停止' : '開始'}
        </button>
      </div>
      <ResponsiveContainer width="100%" height="85%">
        <LineChart data={data}>
          <CartesianGrid strokeDasharray="3 3" />
          <XAxis dataKey="time" tick={{ fontSize: 10 }} />
          <YAxis domain={[0, 150]} />
          <Tooltip />
          <Line
            type="monotone"
            dataKey="value"
            stroke="#8884d8"
            strokeWidth={2}
            dot={false}
            isAnimationActive={false}
          />
        </LineChart>
      </ResponsiveContainer>
    </div>
  );
};

このコンポーネントでは、以下のポイントに注意しています。

  • isAnimationActive={false}: リアルタイム更新時はアニメーションを無効化してパフォーマンスを向上
  • dot={false}: データ点のマーカーを非表示にして描画を軽量化
  • slice(-20): データ量を制限してメモリ使用量を抑制

WebSocketを使ったリアルタイム更新

実際のプロダクション環境では、WebSocketを使用してサーバーからリアルタイムデータを受信することが一般的です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// src/hooks/useWebSocketData.ts
import { useState, useEffect, useCallback } from 'react';

interface WebSocketData {
  time: string;
  value: number;
}

export const useWebSocketData = (url: string) => {
  const [data, setData] = useState<WebSocketData[]>([]);
  const [isConnected, setIsConnected] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const connect = useCallback(() => {
    const ws = new WebSocket(url);

    ws.onopen = () => {
      setIsConnected(true);
      setError(null);
    };

    ws.onmessage = (event) => {
      try {
        const newData: WebSocketData = JSON.parse(event.data);
        setData((prev) => [...prev.slice(-19), newData]);
      } catch (e) {
        console.error('Failed to parse WebSocket data:', e);
      }
    };

    ws.onerror = () => {
      setError('WebSocket接続エラーが発生しました');
    };

    ws.onclose = () => {
      setIsConnected(false);
    };

    return ws;
  }, [url]);

  useEffect(() => {
    const ws = connect();
    return () => ws.close();
  }, [connect]);

  return { data, isConnected, error };
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// src/components/WebSocketChart.tsx
import {
  LineChart,
  Line,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  ResponsiveContainer,
} from 'recharts';
import { useWebSocketData } from '../hooks/useWebSocketData';

export const WebSocketChart = () => {
  const { data, isConnected, error } = useWebSocketData('wss://your-api.example.com/realtime');

  if (error) {
    return <div style={{ color: 'red' }}>{error}</div>;
  }

  return (
    <div style={{ width: '100%', height: 400 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
        <h3>WebSocketリアルタイムデータ</h3>
        <span
          style={{
            width: '10px',
            height: '10px',
            borderRadius: '50%',
            backgroundColor: isConnected ? '#44aa44' : '#ff4444',
          }}
        />
        <span>{isConnected ? '接続中' : '切断'}</span>
      </div>
      <ResponsiveContainer width="100%" height="85%">
        <LineChart data={data}>
          <CartesianGrid strokeDasharray="3 3" />
          <XAxis dataKey="time" />
          <YAxis />
          <Tooltip />
          <Line
            type="monotone"
            dataKey="value"
            stroke="#8884d8"
            strokeWidth={2}
            dot={false}
            isAnimationActive={false}
          />
        </LineChart>
      </ResponsiveContainer>
    </div>
  );
};

実務での活用パターン

ダッシュボード構築

複数のグラフを組み合わせたダッシュボードを構築する例です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// src/components/Dashboard.tsx
import { SalesLineChart } from './SalesLineChart';
import { SalesBarChart } from './SalesBarChart';
import { CategoryPieChart } from './CategoryPieChart';
import { RealtimeChart } from './RealtimeChart';

export const Dashboard = () => {
  return (
    <div style={{ padding: '20px' }}>
      <h1>売上ダッシュボード</h1>
      <div
        style={{
          display: 'grid',
          gridTemplateColumns: 'repeat(auto-fit, minmax(400px, 1fr))',
          gap: '20px',
        }}
      >
        <div style={{ backgroundColor: '#f9f9f9', padding: '16px', borderRadius: '8px' }}>
          <SalesLineChart />
        </div>
        <div style={{ backgroundColor: '#f9f9f9', padding: '16px', borderRadius: '8px' }}>
          <SalesBarChart />
        </div>
        <div style={{ backgroundColor: '#f9f9f9', padding: '16px', borderRadius: '8px' }}>
          <CategoryPieChart />
        </div>
        <div style={{ backgroundColor: '#f9f9f9', padding: '16px', borderRadius: '8px' }}>
          <RealtimeChart />
        </div>
      </div>
    </div>
  );
};

APIデータとの連携

実際のAPIからデータを取得してグラフに表示するパターンです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// src/hooks/useSalesData.ts
import { useState, useEffect } from 'react';
import { SalesData } from '../data/sampleData';

interface UseSalesDataResult {
  data: SalesData[];
  isLoading: boolean;
  error: string | null;
}

export const useSalesData = (): UseSalesDataResult => {
  const [data, setData] = useState<SalesData[]>([]);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch('/api/sales');
        if (!response.ok) {
          throw new Error('データの取得に失敗しました');
        }
        const json = await response.json();
        setData(json);
      } catch (e) {
        setError(e instanceof Error ? e.message : '不明なエラー');
      } finally {
        setIsLoading(false);
      }
    };

    fetchData();
  }, []);

  return { data, isLoading, error };
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// src/components/ApiDataChart.tsx
import {
  LineChart,
  Line,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  Legend,
  ResponsiveContainer,
} from 'recharts';
import { useSalesData } from '../hooks/useSalesData';

export const ApiDataChart = () => {
  const { data, isLoading, error } = useSalesData();

  if (isLoading) {
    return <div>読み込み中...</div>;
  }

  if (error) {
    return <div style={{ color: 'red' }}>エラー: {error}</div>;
  }

  return (
    <ResponsiveContainer width="100%" height={400}>
      <LineChart data={data}>
        <CartesianGrid strokeDasharray="3 3" />
        <XAxis dataKey="month" />
        <YAxis />
        <Tooltip />
        <Legend />
        <Line type="monotone" dataKey="sales" stroke="#8884d8" name="売上" />
        <Line type="monotone" dataKey="profit" stroke="#82ca9d" name="利益" />
      </LineChart>
    </ResponsiveContainer>
  );
};

複合グラフの実装

棒グラフと折れ線グラフを組み合わせた複合グラフを作成します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// src/components/ComposedChart.tsx
import {
  ComposedChart,
  Bar,
  Line,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  Legend,
  ResponsiveContainer,
} from 'recharts';
import { monthlySalesData } from '../data/sampleData';

export const SalesComposedChart = () => {
  return (
    <div style={{ width: '100%', height: 400 }}>
      <h3>売上と顧客数の推移(複合グラフ)</h3>
      <ResponsiveContainer width="100%" height="90%">
        <ComposedChart data={monthlySalesData}>
          <CartesianGrid strokeDasharray="3 3" />
          <XAxis dataKey="month" />
          <YAxis yAxisId="left" />
          <YAxis yAxisId="right" orientation="right" />
          <Tooltip />
          <Legend />
          <Bar yAxisId="left" dataKey="sales" fill="#8884d8" name="売上" />
          <Line
            yAxisId="right"
            type="monotone"
            dataKey="customers"
            stroke="#ff7300"
            strokeWidth={2}
            name="顧客数"
          />
        </ComposedChart>
      </ResponsiveContainer>
    </div>
  );
};

期待される結果

本記事の実装を完了すると、以下の成果が得られます。

表示されるグラフ

  • 売上推移を示す折れ線グラフが描画され、マウスホバーでツールチップが表示されます
  • 月別売上を比較できる棒グラフが表示されます
  • カテゴリ別の構成比を示す円グラフ/ドーナツグラフが表示されます
  • 1秒ごとに更新されるリアルタイムグラフが動作します

インタラクティブ機能

  • ツールチップにより、データポイントの詳細情報が確認できます
  • 凡例クリックで系列の表示/非表示を切り替えられます
  • ブラウザのウィンドウサイズに応じてグラフが自動的にリサイズされます

開発体験

  • TypeScriptの型定義により、データ構造の不整合がコンパイル時に検出されます
  • コンポーネントの分離により、グラフの再利用と保守が容易になります

まとめ

本記事では、ReactとTypeScriptを使用してRechartsでインタラクティブなグラフを作成する方法を解説しました。

Rechartsは宣言的なコンポーネント設計により、Reactの開発スタイルに自然に馴染みます。折れ線グラフ、棒グラフ、円グラフといった基本的なグラフから、ツールチップや凡例のカスタマイズ、レスポンシブ対応、リアルタイムデータ更新まで、実務で必要となる機能を簡潔なコードで実装できます。

データ可視化はユーザーに情報を効果的に伝えるための重要な手段です。Rechartsを活用して、見やすく操作性の高いグラフをReactアプリケーションに組み込んでください。

参考リンク