React + react-chartjs-2 + Chart.js を使って、Pie chart を表示してみた

Reactアプリで Pie chart (円グラフ)を表示したくなったので調べたところ、以下の記事がありました。

   
上記では色々なライブラリが紹介されていましたが、今回はWebに情報の多そうな Chart.js を使ってみることにしました。
Chart.js | Open source HTML5 Charts for your website

 
次に、Chart.jsをReactで扱う方法を調べたところ、 react-chartjs-2 がありました。
react-chartjs-2 | react-chartjs-2

 
そこで、 Chart.js + react-chartjs-2 を使って React で Pie chart を表示してみたことから、メモを残します。

 
目次

 

環境

  • React 18.2.0
  • Chart.js 4.4.2
  • react-chartjs-2 5.2.0
  • TanStack Router 1.29.2

 

ViteでReactアプリを作る

Vite公式ドキュメントに従い、ViteでReactアプリを作ります。
Getting Started | Vite

$ npm create vite@latest
✔ Project name: … react_chart_example
✔ Select a framework: › React
✔ Select a variant: › TypeScript

 

react-chartjs-2 の環境を構築する

リポジトリのREADMEに従い、 chart.js react-chartjs-2 をインストールします。

$ npm install --save chart.js react-chartjs-2

 

TanStack Router によるファイルベースルーティングの設定を追加する

今回はいくつかサンプルコードを作るので、ルーティングライブラリを入れておきます。

ただ、パスを考えるのが手間なので、ファイルベースルーティングができるライブラリを使いたくなりました。

以前は Generouted + React Router を使っていました。
ファイルベースルーティング: Generouted + React Router | Reactにて、useStateやuseEffectを使っていたところをTanstack Queryに置き換えてみた - メモ的な思考的な

 
そんな中、 TanStack Routerでもファイルベースルーティングができるようになったと知りました。

 
そこで今回は、TanStack Routerのファイルベースルーティングを試してみることにしました。
TanStack Router Docs

 

TanStack Router のインストール

TanStack Routerのドキュメントに従い、tanstack router と viteのプラグインをインストールします。
Vite Plugin | TanStack Router Docs

$ npm install @tanstack/react-router @tanstack/router-vite-plugin

 
devtools もインストールしておきます。
Devtools | TanStack Router Docs

$ npm install @tanstack/router-devtools --save

 

vite.config.ts への追加

公式ドキュメントに従い、 vite.config.ts へ設定を追加します。
https://tanstack.com/router/latest/docs/framework/react/guide/file-based-routing#vite-configuration

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import {TanStackRouterVite} from "@tanstack/router-vite-plugin";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react(),
    TanStackRouterVite()  // 追加
  ],
})

 
続いて、 npm run dev し、 routeTree.gen.ts を生成しておきます。

♻️  Generating routes...
✅ Processed route in 148ms

 

main.tsx の修正

TanStack Router を組み込むため、 main.tsx を修正します。

import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'
import {createRouter, RouterProvider} from "@tanstack/react-router"
import {routeTree} from "./routeTree.gen"

const router = createRouter({ routeTree })

declare module '@tanstack/react-router' {
  interface Register {
    router: typeof router
  }
}

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>,
)

 

__root.tsx の追加

ルートとなるファイル __root.tsx を作成します。

import {createRootRoute, Outlet} from "@tanstack/react-router";
import {TanStackRouterDevtools} from "@tanstack/router-devtools";

export const Route = createRootRoute({
  component: () => (
    <>
      <Outlet />
      <TanStackRouterDevtools />
    </>
  )
})

 
以上で TanStack Router の準備ができました。

 

Chart.js で Pie chart を作成する

はじめての Pie chart

Chart.js の exampleを参考に、Pie chart を作ります。

 
また、ファイルベースルーティングできるよう、Pie chartのコンポーネントとルーティングを src/routes/pie_chart/first_pie_chart.lazy.tsx に作ります。

import {createLazyRoute} from "@tanstack/react-router"
import {ArcElement, Chart as ChartJS, Legend, Tooltip} from 'chart.js'
import {Pie} from 'react-chartjs-2'


const Component = () => {
  ChartJS.register(ArcElement, Tooltip, Legend)

  const data = {
    labels: ['奥州ロマン', 'シナノゴールド', 'ピンクレディ', 'ブラムリー'],
    datasets: [
      {
        label: '購入数',
        data: [1, 5, 3, 2],
        backgroundColor: [
          'firebrick', 'gold', 'pink', 'mediumseagreen'
        ],
        borderColor: [
          'firebrick', 'gold', 'pink', 'mediumseagreen'
        ],
        borderWidth: 1
      }
    ]
  }

  return (
    <Pie data={data} />
  )
}

export const Route = createLazyRoute('/pie_chart/first_pie_chart')({
  component: Component
})

 
実装ができたので、 npm run dev で起動します。

その後、 http://localhost:5173/pie_chart/first_pie_chart へアクセスすると、Pie chartが表示されました。

 

Legend (凡例) をカスタマイズする

今回は Legend の表示位置を Pie chart の右側に表示するようカスタマイズします。

カスタマイズ方法を調べたところ、 Chart.js のドキュメントの Legend に情報がありました。 Legend | Chart.js

また、ドキュメントには Warning として

The doughnut, pie, and polar area charts override the legend defaults. To change the overrides for those chart types, the options are defined in Chart.overrides[type].plugins.legend .

と書かれていました。

 
そこで、

  • Chart.overrides.pie.plugins.legend を指定
  • 表示位置のオプション positionright を指定
  • ファイル名は pie_chart_with_legend_on_the_right.lazy.tsx

として実装しました。

const Component = () => {
  ChartJS.register(ArcElement, Tooltip, Legend)

  // 以下を追加
  ChartJS.overrides.pie.plugins.legend.position = 'right'
// ... 
// 他は同じ
}

 
実装が終わったので、ブラウザで http://localhost:5173/pie_chart/pie_chart_with_legend_on_the_right を開くと、Legend が右側に表示されました。

 

Pie chartの上にラベルをを常に表示する

今まで見てきた通り、Chart.js の場合、デフォルトではマイスオーバーすることで、各領域の内訳が表示されます。

ただ、マウスオーバーしなくてもPie chart上に表示する方法がないかを調べたところ、以下の記事がありました。
【Vue.js】Vue.js + Chart.js ドーナツグラフのちょっとした小技【vue-chart.js】| blog(スワブロ) | スワローインキュベート

これを参考に、 pie_chart_with_always_text.lazy.tsx として実装してみました。

実装したときのメモは以下です。

  • alwaysTooltipPlugin という名前で Chart.js のプラグインを作る
    • その中の afterDraw コールバックで、各領域の上にテキストを描画する
  • ChartJS.register() の引数に、追加したプラグイン alwaysTooltipPlugin を指定する
import {ArcElement, Chart as ChartJS, Legend, Tooltip} from "chart.js";
import {Pie} from "react-chartjs-2";
import {createLazyRoute} from "@tanstack/react-router";

const alwaysTooltipPlugin = {
  id: 'alwaysShowTooltip',
  afterDraw(chart: ChartJS) {
    const {ctx} = chart
    chart.data.datasets.forEach((_dataset, i) => {
      chart.getDatasetMeta(i).data.forEach((datapoint, index) => {
        const {x, y} = datapoint.tooltipPosition(false)

        const text = chart.data.labels ?
          (chart.data.labels[index] ?? '' + ': ' + chart.data.datasets[i].data[index]).toString() :
          ''

        ctx.textAlign = 'center'
        ctx.textBaseline = 'middle'
        ctx.fillText(text, x, y)
      })
    })
  }
}

const Component = () => {
  ChartJS.register(ArcElement, Tooltip, Legend, alwaysTooltipPlugin)
  ChartJS.overrides.pie.plugins.legend.position = 'right'

  const data = {
    labels: ['奥州ロマン', 'シナノゴールド', 'ピンクレディ', 'ブラムリー'],
    datasets: [
      {
        label: '購入数',
        data: [1, 5, 3, 2],
        backgroundColor: [
          'firebrick', 'gold', 'pink', 'mediumseagreen'
        ],
        borderColor: [
          'firebrick', 'gold', 'pink', 'mediumseagreen'
        ],
        borderWidth: 1
      }
    ]
  }

  return (
    <Pie data={data} />
  )
}

export const Route = createLazyRoute('/pie_chart/pie_chart_with_always_text')({
  component: Component
})

 
実装が終わったので、ブラウザで http://localhost:5173/pie_chart/pie_chart_with_always_text を開くと、各領域の上にラベルが表示されました。

なお、各領域のラベルの位置は調整していないため、やや重なっています。ただ、今回はサンプルコードなので気にしないこととします。

 
ちなみに、マウスオーバーすると、今まで通り Tooltip が表示されます。

 

Pie Chartの上にTooltipを常に表示する

ラベルが常に表示できるとすれば、Tooltipも常に表示できるかもしれないと考えました。

そこで、

  • 各領域の上に Tooltip を常に表示する
  • マウスオーバーしても、デフォルトの Tooltip は表示しない

の方法を調べたところ、Chart.js の公式 Youtube に情報がありました。
How to Always Show Tooltip on Pie Chart in Chart js - YouTube

 
そこで、Youtubeの内容を参考にしながら pie_chart_with_always_tooltip.lazy.tsx を実装してみました。

実装したときのメモは以下です。

  • ctx.beginPath() 以降の実装で、Tooltipを描画する
  • Pieコンポーネントの options に対して、マウスオーバーしたときのTooltipを表示しないように設定する
import {ArcElement, Chart as ChartJS, Legend, Tooltip} from "chart.js";
import {Pie} from "react-chartjs-2";
import {createLazyRoute} from "@tanstack/react-router";

const alwaysTooltipPlugin = {
  id: 'alwaysShowTooltip',
  afterDraw(chart: ChartJS) {
    const {ctx} = chart
    ctx.save()

    chart.data.datasets.forEach((_dataset, i) => {
      chart.getDatasetMeta(i).data.forEach((datapoint, index) => {
        const {x, y} = datapoint.tooltipPosition(false)

        const text = chart.data.labels ?
          (chart.data.labels[index] ?? '' + ': ' + chart.data.datasets[i].data[index]).toString() :
          ''

        const textWidth = ctx.measureText(text).width
        ctx.fillStyle = 'rgba(0, 0, 0, 0.8)'

        ctx.fillRect(x - (textWidth + 10) / 2, y - 25, textWidth + 10, 20 )

        // triangle
        ctx.beginPath()
        ctx.moveTo(x, y)
        ctx.lineTo(x - 5, y - 5)
        ctx.lineTo(x + 5, y - 5)
        ctx.fill()
        ctx.restore()

        // text
        ctx.font = '12px Arial'
        ctx.fillStyle = 'white'
        ctx.fillText(text, x - (textWidth / 2), y - 10)
        ctx.restore()
      })
    })
  }
}

const options = {
  plugins: {
    tooltip: {
      enabled: false
    }
  }
}

const Component = () => {
  ChartJS.register(ArcElement, Tooltip, Legend, alwaysTooltipPlugin)
  ChartJS.overrides.pie.plugins.legend.position = 'right'

  const data = {
    labels: ['奥州ロマン', 'シナノゴールド', 'ピンクレディ', 'ブラムリー'],
    datasets: [
      {
        label: '購入数',
        data: [1, 5, 3, 2],
        backgroundColor: [
          'firebrick', 'gold', 'pink', 'mediumseagreen'
        ],
        borderColor: [
          'firebrick', 'gold', 'pink', 'mediumseagreen'
        ],
        borderWidth: 1
      }
    ]
  }

  return (
    <Pie data={data} options={options} />
  )
}

export const Route = createLazyRoute('/pie_chart/pie_chart_with_always_tooltip')({
  component: Component
})

 
実装が終わったので、ブラウザで http://localhost:5173/pie_chart/pie_chart_with_always_tooltip を開くと、各領域の上にラベルが表示されました。

なお、各領域のTooltipの位置は調整していないため、やや重なっています。ただ、今回はサンプルコードなので気にしないこととします。

 

ソースコード

Githubに上げました。
https://github.com/thinkAmi-sandbox/react_chartjs-example

今回のプルリクはこちら。
https://github.com/thinkAmi-sandbox/react_chartjs-example/pull/1