chartjs-chart-sankeyをNext.jsで動かしてみた

2022.04.04

こんにちは!DA(データアナリティクス)事業本部 サービスソリューション部の大高です。

chartjs-chart-sankeyというSankey Chartを描画できるライブラリがあります。

このSankey Chartが「Reactではうまく動くのに、Next.jsで動かそうとするとうまく動かない」という相談をチームメンバーから受けたので少し調べてみました。

エラー内容

まずはじめに、単純に動かそうとした場合に発生したエラー内容です。

TypeError: Cannot read properties of undefined (reading 'prototype')
    at TypedRegistry.isForType (/foo-bar/hello-chartjs-with-nextjs/node_modules/chart.js/dist/chart.js:6005:74)
    at Registry._getRegistryForType (/foo-bar/hello-chartjs-with-nextjs/node_modules/chart.js/dist/chart.js:6148:15)
    at /foo-bar/hello-chartjs-with-nextjs/node_modules/chart.js/dist/chart.js:6128:41
    at Array.forEach (<anonymous>)
    at Registry._each (/foo-bar/hello-chartjs-with-nextjs/node_modules/chart.js/dist/chart.js:6127:15)
    at Registry.add (/foo-bar/hello-chartjs-with-nextjs/node_modules/chart.js/dist/chart.js:6085:10)
    at Function.value [as register] (/foo-bar/hello-chartjs-with-nextjs/node_modules/chart.js/dist/chart.js:7407:16)
    at Sankey (webpack-internal:///./components/sankey.tsx:15:49)
    at renderWithHooks (/foo-bar/hello-chartjs-with-nextjs/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5448:16)
    at renderIndeterminateComponent (/foo-bar/hello-chartjs-with-nextjs/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5521:15)

prototypeundefinedなので読み込めないというエラーになっていますね。

単純なドーナツチャートを表示してみる

chartjs-chart-sankeyは、チャート表示のためのライブラリreact-chartjs-2のプラグインのようなライブラリです。問題の切り分けのために、まずはreact-chartjs-2で用意されている単純なドーナツチャートを表示してみます。

以下のコードでまずはNext.jsアプリを作成します。

$ yarn create next-app --typescript

作成したら以下のQuickstartドキュメントに従ってライブラリをインストールします。

$ yarn add chart.js react-chartjs-2

ここまで出来たら、新規に以下のファイルを作成します。

pages/doughnut-chart.tsx

import type { NextPage } from "next";
import { Chart as ChartJS, ArcElement, Tooltip, Legend } from "chart.js";
import { Doughnut } from "react-chartjs-2";

const DoughnutChart: NextPage = () => {
  ChartJS.register(ArcElement, Tooltip, Legend);

  const data = {
    labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
    datasets: [
      {
        label: "# of Votes",
        data: [12, 19, 3, 5, 2, 3],
        backgroundColor: [
          "rgba(255, 99, 132, 0.2)",
          "rgba(54, 162, 235, 0.2)",
          "rgba(255, 206, 86, 0.2)",
          "rgba(75, 192, 192, 0.2)",
          "rgba(153, 102, 255, 0.2)",
          "rgba(255, 159, 64, 0.2)",
        ],
        borderColor: [
          "rgba(255, 99, 132, 1)",
          "rgba(54, 162, 235, 1)",
          "rgba(255, 206, 86, 1)",
          "rgba(75, 192, 192, 1)",
          "rgba(153, 102, 255, 1)",
          "rgba(255, 159, 64, 1)",
        ],
        borderWidth: 1,
      },
    ],
  };

  return <Doughnut data={data} />;
};

export default DoughnutChart;

準備ができたらアプリを起動して http://localhost:3000/doughnut-chart にアクセスして動作を確認してみます。

$ yarn dev

これは問題なさそうですね。ここまでで「react-chartjs-2は問題なく動く」という切り分けができました。

chartjs-chart-sankey を動かそうとしてみる

次にchartjs-chart-sankeyをインストールして動かそうとしてみます。

まずは該当ライブラリを追加インストールします。

$ yarn add chartjs-chart-sankey

先程と同様に以下のファイルを追加作成します。

pages/sankey-chart.tsx

import type { NextPage } from "next";
import {
  CategoryScale,
  Chart as ChartJS,
  LinearScale,
  Tooltip,
} from "chart.js";
import { Chart as C } from "react-chartjs-2";
import { SankeyController, Flow } from "chartjs-chart-sankey";

const SankeyChart: NextPage = () => {
  ChartJS.register(SankeyController, Flow, LinearScale, CategoryScale, Tooltip);

  const colors: any = {
    a: "red",
    b: "green",
    c: "blue",
    d: "gray",
  };

  const getColor = (key: string) => colors[key];

  const data = {
    datasets: [
      {
        label: "My sankey",
        data: [
          { from: "a", to: "b", flow: 10 },
          { from: "a", to: "c", flow: 5 },
          { from: "b", to: "c", flow: 10 },
          { from: "d", to: "c", flow: 7 },
        ],
        colorFrom: (c: any) => getColor(c.dataset.data[c.dataIndex].from),
        colorTo: (c: any) => getColor(c.dataset.data[c.dataIndex].to),
        colorMode: "gradient" as "gradient", // or 'from' or 'to'
        /* optional labels */
        labels: {
          a: "Label A",
          b: "Label B",
          c: "Label C",
          d: "Label D",
        },
        /* optional priority */
        priority: {
          b: 1,
          d: 0,
        },
        /* optional column overrides */
        column: {
          d: 1,
        },
        size: "max" as "max", // or 'min' if flow overlap is preferred
      },
    ],
  };

  return <C type="sankey" data={data} />;
};

export default SankeyChart;

こちらも準備ができたらアプリを起動してhttp://localhost:3000/sankey-chartにアクセスして動作を確認してみます。

$ yarn dev

すると、冒頭に記載したエラーが発生します。

怪しいところは?

エラーメッセージで怪しいと感じるところは以下の箇所です。

    at renderIndeterminateComponent (/foo-bar/hello-chartjs-with-nextjs/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5521:15)

レンダリングをしようとしているところでエラーが出ていそうです。Next.jsはSSRを行うので、恐らくこのあたりがReactとの差異でエラーが出ていそうと予想しました。

Dynamic Import をしてみる

ということで、Dynamic Importをしてみます。

まずは先程のファイルをコンポーネント化しました。

components/sankey.tsx

import {
  CategoryScale,
  Chart as ChartJS,
  LinearScale,
  Tooltip,
} from "chart.js";
import { Chart as C } from "react-chartjs-2";
import { SankeyController, Flow } from "chartjs-chart-sankey";

const Sankey = () => {
  ChartJS.register(SankeyController, Flow, LinearScale, CategoryScale, Tooltip);

  const colors: any = {
    a: "red",
    b: "green",
    c: "blue",
    d: "gray",
  };

  const getColor = (key: string) => colors[key];

  const data = {
    datasets: [
      {
        label: "My sankey",
        data: [
          { from: "a", to: "b", flow: 10 },
          { from: "a", to: "c", flow: 5 },
          { from: "b", to: "c", flow: 10 },
          { from: "d", to: "c", flow: 7 },
        ],
        colorFrom: (c: any) => getColor(c.dataset.data[c.dataIndex].from),
        colorTo: (c: any) => getColor(c.dataset.data[c.dataIndex].to),
        colorMode: "gradient" as "gradient", // or 'from' or 'to'
        /* optional labels */
        labels: {
          a: "Label A",
          b: "Label B",
          c: "Label C",
          d: "Label D",
        },
        /* optional priority */
        priority: {
          b: 1,
          d: 0,
        },
        /* optional column overrides */
        column: {
          d: 1,
        },
        size: "max" as "max", // or 'min' if flow overlap is preferred
      },
    ],
  };

  return <C type="sankey" data={data} />;
};

export default Sankey;

次に先程のページを修正して、このコンポーネントをDynamic Importして利用するように変更します。

pages/sankey-chart.tsx

import type { NextPage } from "next";
import dynamic from "next/dynamic";

const SankeyChart: NextPage = () => {
  const Sankey = dynamic(() => import("../components/sankey"));
  return <Sankey />;
};

export default SankeyChart;

この状態で、再度アプリを起動してhttp://localhost:3000/sankey-chartにアクセスしてみます。

$ yarn dev

今度はちゃんとSankey Chartが表示できました!

まとめ

以上、chartjs-chart-sankeyをNext.jsで動かしてみました。

以前に試したReact Leafletもそうなのですが、時折こういったDynamic Importする必要があるライブラリがあるので、似たようなケースが起きたときにはすぐに思い出せるようにしておきたいと思います。

どなたかのお役に立てば幸いです。それでは!