Grafanaでrechartsを使ってパネルプラグインを作ってみた

2023.09.15

こんにちは。CX事業本部Delivery部のakkyです。

Grafanaはプラグインで機能を拡張することができ、さまざまなプラグインが公開されています。 パネル(フロントエンド)プラグインでは独自の可視化が可能になり、バックエンドプラグインでは独自のデータソースへの接続が可能になります。 フロンエンドの部分はReactで書き、バックエンドは基本的にはgo言語で書きます(gRPC対応であれば他の言語も対応)。

今回は、パネルプラグイン開発のチュートリアルを参考に、recharts(グラフライブラリ)を組み合わせて独自にグラフが表示できることを試してみました。

Amazon Managed Grafanaでは独自のプラグインをインストールすることはできません。

使用ソフトウェア

Reactは17.0.2がインストールされました。

また、開発はWSL上のUbuntu22.04で行いました。

プラグインの開発

サンプルのビルド

Grafana公式のプラグイン作成チュートリアルを参考に行っていきます。

まずはテンプレートを作成します。適切な名前を付け、プラグインのタイプにpanelを選びます。

npx @grafana/create-plugin@latest
? What is going to be the name of your plugin? testpanel
? What is the organization name of your plugin? classmethod
? How would you describe your plugin?
? What type of plugin would you like? panel
? Do you want to add Github CI and Release workflows? No
? Do you want to add a Github workflow for automatically checking "Grafana API compatibility" on PRs? No

任意のパッケージマネージャー(今回はyarn)でモジュールを入れます。

cd classmethod-my-panel/
yarn install

次のコマンドでビルドします。

yarn run build

rechartsのインストールと開発

まずはrechartsを追加します。

yarn add recharts

プラグインを開発していきます。

パラメータの定義

まずは、パネルの設定に使うためのパラメータを定義していきます。今回は線の色だけ設定できるようにしてみました。

src/types.ts

export interface SimpleOptions {
  linecolor: string
}

設定画面の定義

ここの詳しいドキュメントがなかったのですが、addColorPickerなどのUIコンポーネントは@grafana/uiで定義されているものです。

Grafana design system

Colorpickerの説明はこちらです。UIコンポーネントにパラメーターを渡すには、settingsをつくって入れる必要がありました。 今回はenableNamedColors: trueとして、色の名前がそのまま渡されるようにしています。色名からカラーコードへの変更はパネル本体のコードで書きます。

src/module.ts

import { PanelPlugin } from '@grafana/data';
import { SimpleOptions } from './types';
import { SimplePanel } from './components/SimplePanel';

export const plugin = new PanelPlugin<SimpleOptions>(SimplePanel).setPanelOptions((builder) => {
  return builder
    .addColorPicker({
      path: "linecolor",
      name: "Select line name",
      defaultValue: "#F00",
      settings: {
        enableNamedColors: true
      }
    });
});

次のような設定項目になります

パネル本体

Reactそのままで書くことができるので、主にrechatsの知識が必要になります。まずはシンプルな折れ線グラフを作ってみました。

Grafana特有の知識としては以下の点があります - 設定値がoptionsに入ってくる - 画面テーマやコンポーネントは@grafana/uiを使う - 表示するデータのフォーマット

データフォーマットはDataFrames形式で渡されますので、これを適宜加工して使います。

Grafanaでは、値と時刻がそれぞれの配列で渡されますが、rechatsでは値と時刻をセットにしたobjectの配列が必要なので、系列ごとのデータを分解して構成します。

設定画面で登場したカラーピッカーから渡される色名はtheme.visualization.getColorByName()を使ってカラーコードに変換できました。

src/components/SimplePanel.tsx

import React from 'react';
import { FieldType, PanelProps } from '@grafana/data';
import { SimpleOptions } from 'types';
import { useTheme2 } from '@grafana/ui';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';

interface Props extends PanelProps<SimpleOptions> {}

export const SimplePanel: React.FC<Props> = ({ options, data }) => {
  const frame = data.series[0];

  const timeField = frame.fields.find((field) => field.type === FieldType.time);
  const valueField = frame.fields.find((field) => field.type === FieldType.number);

  const filed_name = valueField!.name;
  const formatted_data = [];

  for (const [index, value] of timeField!.values.entries()) {
    const date_str = new Date(value).toLocaleString("ja-JP");

    formatted_data.push({
      "time": date_str,
      [valueField!.name]: valueField!.values[index]
    });
  }

  // Convert color name to color code
  const theme = useTheme2();
  const colorcode = theme.visualization.getColorByName(options.linecolor);
  
  return (
  <ResponsiveContainer width="100%" height="100%">
        <LineChart
          data={formatted_data}
          margin={{
            top: 5,
            right: 30,
            left: 20,
            bottom: 5,
          }}>
          <CartesianGrid strokeDasharray="3 3" />
          <XAxis dataKey="time" />
          <YAxis type='number' domain={['auto', 'auto']}/>
          <Tooltip contentStyle={{backgroundColor: theme.colors.background.primary}} itemStyle={{ color: colorcode }} />
          <Legend />
          <Line type="linear" dataKey={filed_name} stroke={colorcode} isAnimationActive={false} dot={false}/>
        </LineChart>
      </ResponsiveContainer>
  );
};

ビルドと実行

先ほどと同様にyarn run buildでビルドを行い、コンテナで動作テストします。

podman(dockerでも動くと思います)で実行します。GF_DEFAULT_APP_MODEを設定しないと、開発中で署名されていないプラグインを使うことができません。

podman run -d -p 3000:3000 -v ~/classmethod-test-panel:/var/lib/grafana/plugins -e GF_DEFAULT_APP_MODE=development --name=grafana docker.io/grafana/grafana:10.0.5

Grafanaを開き、今回作ったパネルを選択すると、ただしくグラフが表示できました!

なお、開発中にプラグインを更新しても、キャッシュが強く効くため、通常の更新ではうまくいきません。 Chromeの場合はSuper Reloaderを入れるとうまくいきました。

おわりに

Grafanaで独自のパネルプラグインを開発してみました。

Reactコンポーネントとして開発できるので、当初の想定より手軽に開発できました。 Grafanaを使うと、認証やDBからの接続をすべて任せてグラフの開発に集中できて便利ですね。