Wijmo×React×Bootstrapで売上データを可視化するダッシュボードを作成する

40以上のコントロールを備えるJavaScript UIライブラリ「Wijmo(ウィジモ)」には、表形式でデータ表示ができるデータグリッドコントロール「FlexGrid(フレックスグリッド)」や、様々なグラフの作成に対応したチャートコントロール「FlexChart(フレックスチャート)」、データの可視化に最適な「Gauge(ゲージ)」など、業務アプリケーション開発に必要なコントロールが豊富に搭載されています。

本記事ではWijmoの各種コントロールを使用して、ReactとBootstrapで売上データを可視化するダッシュボード画面を作成する方法をご紹介します。

Wijmo×React×Bootstrapで作成したダッシュボード

開発環境

  • React 19.1.1
  • Bootstrap 3.4.1
  • Wijmo 5.20251.40

Reactプロジェクトの作成

以下のコマンドを実行して、ViteでベースとなるReactアプリケーションを作成します。今回は「react-swc」のオプションを指定し、「SWC(Speedy Web Compiler)」を使用したReactのアプリケーションをスキャフォールドします。
※ 使用可能なテンプレートはこちらをご覧ください。
※ Windows環境でPowerShellを使用する場合、--演算子として使用されているので、コマンドプロンプトを使用するか、npm create vite@latest wijmo-react-quickstart --- --template react-swcとして実行してください。

npm create vite@latest wijmo-react-dashboard -- --template react-swc

プロジェクトが作成されたら、以下のコマンドでプロジェクトの配下に移動し、必要なライブラリをインストールします。

cd wijmo-react-dashboard
npm install

以下のコマンドを実行してアプリケーションを起動し、「http://localhost:5173/」にアクセスすると、ベースとなるReactのアプリケーションが確認できます。

ベースとなるReactのアプリケーション

Bootstrapのインストールと組み込み

ダッシュボードを作成するにあたり、グリッドシステムカードのUI、ならびにレスポンシブデザインを実現するためにBootstrapを使用します。さらに同ライブラリが提供するユーティリティクラスを各種スタイルの調整に使用します。「index.html」を以下のように追記し、Bootstrapの参照を追加します。

<!doctype html>
<html lang="ja">
・・・(中略)・・・
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css"
    integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous" />
  <title>Wijmoダッシュボード</title>
</head>
・・・(中略)・・・

Wijmoのインストール

次にWijmoのReact用パッケージをアプリケーションにインストールします。

npm install @mescius/wijmo.react.all

テストデータの定義

npmパッケージをインストールしたら、「src」フォルダの配下に「data」フォルダを作成し、「data.js」を追加してダッシュボードに表示するテストデータを定義します。

export const salesByStore = [
  { store: '東京本店', sales: 127250 },
  { store: '関西支店', sales: 107340 },
  { store: '中部支店', sales: 45360 },
  { store: '九州支店', sales: 66250 },
];

export const salesByProducts = [
  { name: '食品', sales: 5500 },
  { name: '家電', sales: 7250 },
  { name: '雑貨', sales: 8500 },
  { name: 'その他', sales: 10750 },
];

export const recentSales = [
  {
    client: '大和コーヒー',
    description: '高級コーヒー豆、国産はちみつ、特選ジャム',
    value: 6250,
    itemCount: 50,
  },
  {
    client: '日の出燃料',
    description: '高火力カセットコンロ、非常用カセットボンベ、携帯カイロ',
    value: 2265,
    itemCount: 20,
  },
  {
    client: '富士文具製紙',
    description: '再生トイレットペーパー、ティッシュペーパー、キッチンペーパー',
    value: 4700,
    itemCount: 10,
  },
  {
    client: '銀河キッチン',
    description: 'アルミホイル、食品用ラップ、冷凍保存袋',
    value: 21750,
    itemCount: 250,
  },
  {
    client: 'レストラン大京',
    description: 'ステーキナイフセット、ブランド箸、カトラリー、包丁研ぎ器',
    value: 5000,
    itemCount: 5,
  },
  {
    client: '黒川ストア',
    description:
      'スマートフォン用ガラスフィルム、急速充電器、モバイルバッテリー',
    value: 35000,
    itemCount: 25,
  },
  {
    client: '武田クリーン',
    description: 'ロボット掃除機、コードレス掃除機',
    value: 25000,
    itemCount: 10,
  },
  {
    client: '中富サービス',
    description: '防災セット、防災食、携帯ラジオ',
    value: 15000,
    itemCount: 50,
  },
  {
    client: '宙スタジオ',
    description: '小型高性能ドローン、ドローン用交換バッテリー、プロペラ',
    value: 25250,
    itemCount: 50,
  },
  {
    client: 'ニホンカメラ',
    description: '音波電動歯ブラシ、電動シェーバー',
    value: 33200,
    itemCount: 40,
  },
];

Wijmoの組み込みとダッシュボードの実装

次に「src/App.jsx」を以下のように修正し、Wijmoの組み込みを行なっていきます。

WijmoのReactモジュールをはじめとした必要なモジュールやCSS、日本語カルチャファイル、先ほどのテストデータ等をインポートし、ダッシュボードに表示するゲージやチャート、データグリッドの定義を行います。
※ ライセンスキーを設定しない場合トライアル版を示すメッセージが表示されます。ライセンスキーの入手や設定方法についてはこちらをご覧ください。

// React本体とクラスコンポーネント用Componentをインポート
import React, { Component } from 'react';
// アプリ全体のスタイルをインポート
import './App.css';

// Wijmoのスタイルと必要なコンポーネントをインポート
import '@mescius/wijmo.styles/wijmo.css';
import * as Wijmo from "@mescius/wijmo";
import { FlexGrid, FlexGridColumn } from '@mescius/wijmo.react.grid';
import { FlexChart, FlexPie, FlexChartSeries } from '@mescius/wijmo.react.chart';
import { RadialGauge } from '@mescius/wijmo.react.gauge';
import '@mescius/wijmo.cultures/wijmo.culture.ja';

import { recentSales, salesByStore, salesByProducts } from './data/data';

//Wijmo.setLicenseKey('ここにWijmoのライセンスキーを設定します');

// 売上チャートやゲージなどのパネル表示用コンポーネント
const ChartPanel = ({ title, children }) => {
  return (
    <div className="col-lg-4 col-md-6 col-sm-12 mt-1">
      <div className="card dashboardPanel h-60">
        <div className="card-body">
          <h5 className="card-title">{title}</h5>
          {children}
        </div>
      </div>
    </div>
  );
};

// 取引リストなどデータ表示用パネルコンポーネント
const DataPanel = ({ title, children }) => {
  return (
    <div className="col-sm-12">
      <div className="card dashboardRow">
        <div className="card-body">
          <h5 className="card-title">{title}</h5>
          {children}
        </div>
      </div>
    </div>
  );
};

// 本日の売上をゲージで表示するコンポーネント
const Gauge = ({ data }) => {
  return (
    <ChartPanel title="本日の売上">
      <div className="gauge">
        <RadialGauge
          min={0}
          max={500000}
          step={50}
          isReadOnly={true}
          thickness={0.15}
          format="n0"
          value={data}
        />
      </div>
    </ChartPanel>
  );
};

// 支店別売上を棒グラフで表示するコンポーネント
const SalesChart = ({ salesData }) => {
  return (
    <ChartPanel title="支店別売上">
      <FlexChart
        itemsSource={salesData}
        bindingX="country"
        style={{ height: '290px' }}
        palette={['rgba(171, 125, 246, 1)']}
      >
        <FlexChartSeries name="売上" binding="sales" />
      </FlexChart>
    </ChartPanel>
  );
};

// カテゴリ別売上を円グラフで表示するコンポーネント
const SalesPie = ({ salesData }) => {
  return (
    <ChartPanel title="カテゴリ別売上">
      <FlexPie
        itemsSource={salesData}
        binding="sales"
        bindingName="name"
        innerRadius={0.7}
        style={{ height: '290px' }}
        palette={[
          'rgba(171, 125, 246, 1)',
          'rgba(38, 193, 201, 1)',
          'rgba(129, 201, 38, 1)',
          'rgba(250, 202, 0, 1)',
        ]}
      />
    </ChartPanel>
  );
};

// 本日の取引一覧をグリッドで表示するコンポーネント
const TransactionList = ({ transactions }) => {
  return (
    <DataPanel title="本日の取引">
      <FlexGrid
        style={{ width: '100%' }}
        itemsSource={transactions}
        selectionMode="Row"
        alternatingRowStep={1}
      >
        <FlexGridColumn header="クライアント名" binding="client" width="2*" />
        <FlexGridColumn header="説明" binding="description" width="3*" />
        <FlexGridColumn header="合計" binding="value" width="1*" />
        <FlexGridColumn header="数量" binding="itemCount" width="1*" />
      </FlexGrid>
    </DataPanel>
  );
};

// ダッシュボード全体のメインコンポーネント
class App extends Component {
  constructor() {
    super();
    // 売上・取引データをstateで管理
    this.state = {
      recentSales: recentSales,
      salesByStore: salesByStore,
      salesByProducts: salesByProducts,
    };
  }

  // 本日の売上合計を計算
  calculateSales() {
    let totalSales = 0;
    this.state.recentSales.forEach((sale) => (totalSales += sale.value));
    return totalSales;
  }

  // ダッシュボード画面の描画
  render() {
    return (
      <div className="container">
        <div className="row">
          <Gauge data={this.calculateSales()} />
          <SalesChart salesData={this.state.salesByStore} />
          <SalesPie salesData={this.state.salesByProducts} />
        </div>
        <div className="row">
          <TransactionList transactions={this.state.recentSales} />
        </div>
      </div>
    );
  }
}

// Appコンポーネントをエクスポート
export default App;

以下で上記のコードの実装のポイントを解説していきます。

各種表示用のパネルの定義

以下のコードはWrapperとしてBootstrap関連の定型コードの大部分をひとつの場所にまとめたものです。ダッシュボードをカスタマイズしてパネルを追加する際にこれらのWrapperを使うことで、Wijmoコントロールを含むダッシュボードパネルを簡単に拡張できます。

・・・(中略)・・・
// 売上チャートやゲージなどのパネル表示用コンポーネント
const ChartPanel = ({ title, children }) => {
  return (
    <div className="col-lg-4 col-md-6 col-sm-12 mt-1">
      <div className="card dashboardPanel h-60">
        <div className="card-body">
          <h5 className="card-title">{title}</h5>
          {children}
        </div>
      </div>
    </div>
  );
};

// 取引リストなどデータ表示用パネルコンポーネント
const DataPanel = ({ title, children }) => {
  return (
    <div className="col-sm-12">
      <div className="card dashboardRow">
        <div className="card-body">
          <h5 className="card-title">{title}</h5>
          {children}
        </div>
      </div>
    </div>
  );
};
・・・(中略)・・・

ゲージ(Gauge)の定義

以下のコードでは本日の会社の売上高を示す円形ゲージ(RadialGauge)のパネルを作成します。先程定義したChartPanelでWijmoの円形ゲージをラップします。ダッシュボードに合わせて、いくつかのプロパティを渡してカスタマイズします。また、オプションとしてゲージの最小値(min)と最大値(max)、読み取り専用かどうか(isReadOnly)、ゲージの太さ(thickness)などを設定しています。このゲージコンポーネントにはdataというパラメータが1つあり、それをRadialGaugevalueプロパティに渡します。

・・・(中略)・・・
// 本日の売上をゲージで表示するコンポーネント
const Gauge = ({ data }) => {
  return (
    <ChartPanel title="本日の売上">
      <div className="gauge">
        <RadialGauge
          min={0}
          max={500000}
          step={50}
          isReadOnly={true}
          thickness={0.15}
          format="n0"
          value={data}
        />
      </div>
    </ChartPanel>
  );
};

・・・(中略)・・・
ゲージ

棒グラフ(FlexChart)の定義

以下の部分ではゲージを作成したときと同じように、FlexChartをChartPanelでラップし、いくつかのプロパティを設定しています。

styleにはCSSプロパティが格納されたオブジェクトを設定し、今回はチャートの高さのみを設定してパネル内にすっきり収まるようにします。
itemsSourceは、グラフの作成に使用されるデータ項目のリストを設定します。このプロパティには、JavaScriptの配列を渡す必要があります。配列内のオブジェクトには、x軸のラベルとなる文字列プロパティと、y軸の値を含む数値プロパティが1つずつ必要です。この値を使って、FlexChartは選択されたグラフの種類(棒、点、線など)に応じてグラフを描画します。
また、bindingXstoreに設定し、FlexChartが提供されたitemsSourceを処理してグラフを構築する際に、各オブジェクトのstoreプロパティをそのデータポイントのx軸ラベルとして使用するよう定義します。

・・・(中略)・・・
// 支店別売上を棒グラフで表示するコンポーネント
const SalesChart = ({ salesData }) => {
  return (
    <ChartPanel title="支店別売上">
      <FlexChart
        itemsSource={salesData}
        bindingX="store"
        style={{ height: '290px' }}
        palette={['rgba(171, 125, 246, 1)']}
      >
        <FlexChartSeries name="売上" binding="sales" />
      </FlexChart>
    </ChartPanel>
  );
};
・・・(中略)・・・
棒グラフ

ドーナツグラフ(FlexPie)の定義

以下の部分ではFlexPieを使用して、同様にドーナツチャートのコンポーネントを定義しています。FlexPieはFlexChartと同様のプロパティのセットを持ちますがFlexPieは単一のデータ系列の表示のみが可能です。
innerRadiusに0から1の間の値を設定すると、円グラフの中央がくり抜かれてドーナツチャートに変わります。

・・・(中略)・・・
// カテゴリ別売上を円グラフで表示するコンポーネント
const SalesPie = ({ salesData }) => {
  return (
    <ChartPanel title="カテゴリ別売上">
      <FlexPie
        itemsSource={salesData}
        binding="sales"
        bindingName="name"
        innerRadius={0.7}
        style={{ height: '290px' }}
        palette={[
          'rgba(171, 125,246, 1)',
          'rgba(38, 193, 201, 1)',
          'rgba(129, 201, 38, 1)',
          'rgba(250, 202, 0, 1)',
        ]}
      />
    </ChartPanel>
  );
};
・・・(中略)・・・
ドーナツチャート

データグリッド(FlexGrid)の定義

以下の部分では、本日の取引一覧をFlexGridで表示しています。FlexGridをDataPanelでラップし、さらにFlexGridの各種プロパティを設定しています。FlexGridの列や列ヘッダーは設定されたデータソースに応じて自動的に設定が可能ですが、今回は設定を細かく制御するためにFlexGridColumnを追加しています。widthにはスターサイズ指定オプションというFlexGridで動的にサイズを指定する方法で列の幅を定義しています。詳しくは製品ヘルプをご覧ください。

・・・(中略)・・・
// 本日の取引一覧をグリッドで表示するコンポーネント
const TransactionList = ({ transactions }) => {
  return (
    <DataPanel title="本日の取引">
      <FlexGrid
        style={{ width: '100%' }}
        itemsSource={transactions}
        selectionMode="Row"
        alternatingRowStep={1}
      >
        <FlexGridColumn header="クライアント名" binding="client" width="2*" />
        <FlexGridColumn header="説明" binding="description" width="3*" />
        <FlexGridColumn header="合計" binding="value" width="1*" />
        <FlexGridColumn header="数量" binding="itemCount" width="1*" />
      </FlexGrid>
    </DataPanel>
  );
};
・・・(中略)・・・
FlexGrid

スタイルの設定

最後に「arc/App.css」にスタイルを追加します。

/* 全体背景 */
html,
body {
  background: rgb(0 193 213 / 1);
}

/* 見出しのスタイル */
h5 {
  color: white !important;
  padding: 8px;
}

/* ダッシュボードのパネル・行の余白 */
.dashboardRow {
  margin-top: 20px;
}

.dashboardPanel {
  margin-top: 20px;
}

/* カードの枠線を消す */
.card {
  border: none;
}

/* カードの中身の背景色と余白 */
.card-body {
  background: rgb(19 124 144 / 1);
  padding: 0px;
}

/* カードタイトルの余白 */
.card-title {
  margin: 0.2rem;
}

/* Bootstrapのbg-lightを上書き(背景色変更) */
.bg-light {
  background-color: rgb(19 124 144 / 1) !important;
}

/* ゲージ */
.gauge {
  min-height: 290px;
  background: rgb(21 96 115 / 1);
  padding: 10px;
  padding-top: 40px;
}

.gauge .wj-value {
  fill: white;
  font-size: 64px;
  font-weight: 700;
}

.gauge :is(.wj-min, .wj-max) {
  fill: rgb(135 166 191 / 1);
}

.gauge .wj-pointer {
  fill: rgb(171 125 246 / 1);
}

.gauge .wj-face path {
  fill: rgb(135 166 191 / 1);
  stroke-width: 0px;
}

/* --- フレックスチャート --- */
.wj-flexchart,
.wj-flexpie {
  padding: 10px;
  margin: 0px;
  border: none !important;
  background: rgb(21 96 115 / 1) !important;
}

/* Wijmoテーマによるデフォルトの下余白を削除 */
.wj-control:is(.wj-flexchart, .wj-flexpie) {
  margin-bottom: 0 !important;
}

/* カード内でチャートの背景をクリップ */
.dashboardPanel .card-body {
  overflow: hidden;
}

/* Wijmoによって生成された白いプロット矩形/境界を削除 */
:is(.wj-flexchart, .wj-flexpie) svg .wj-plot-area > :is(path, rect) {
  stroke: none !important;
  fill: transparent !important;
}

/* SVGの背景を透明にしてカード色を反映 */
:is(.wj-flexchart, .wj-flexpie) svg {
  background: transparent !important;
}

.wj-flexchart .wj-axis-x .wj-line {
  stroke: rgb(135 166 191 / 1);
  stroke-width: 1px;
}

.wj-flexchart .wj-axis-y .wj-gridline {
  stroke: rgb(135 166 191 / 1);
  stroke-width: 1px;
}

/* ダーク背景でのラベルの視認性向上 */
:is(.wj-flexchart, .wj-flexpie, .wj-legend) .wj-label,
.wj-flexchart :is(.wj-axis-x, .wj-axis-y) .wj-label  {
  fill: #e6f4f8 !important; /* brighter text */
}

:is(.wj-flexchart, .wj-flexpie) .wj-label {
  fill: rgb(135 166 191 / 1);
}

.wj-flexchart .wj-axis-x .wj-line {
  stroke: rgb(135 166 191 / 1);
  stroke-width: 1px;
}

.wj-flexchart .wj-axis-y .wj-gridline {
  stroke: rgb(135 166 191 / 1);
  stroke-width: 1px;
}

.wj-legend .wj-label {
  fill: rgb(135 166 191 / 1);
}

/* --- フレックスグリッド --- */
.wj-flexgrid,
.wj-flexgrid :is(.wj-root, .wj-cells, .wj-colheaders, .wj-rowheaders, .wj-topleft) {
  background: rgb(21 96 115) !important;
}

.wj-flexgrid {
  border: none;
  border-radius: 0;
  color: #fff;
}

/* グリッド全体の文字色を白に強制 */
.wj-flexgrid .wj-cell {
  color: #fff !important;
}

/* ヘッダー部の背景色・文字色・太字(Wijmo/Bootstrapの上書き) */
.wj-flexgrid .wj-header,
.wj-flexgrid :is(.wj-colheaders, .wj-rowheaders, .wj-topleft) .wj-cell {
  background: rgb(19 88 109) !important;
  color: #fff !important;
  font-weight: 700;
}

/* 選択行・複数選択行の背景色 */
.wj-flexgrid .wj-state-selected {
  background: rgb(171 125 246) !important;
}

.wj-flexgrid .wj-state-multi-selected {
  background: rgb(171 125 246 / 0.3) !important;
}

/* 交互行の背景色(ダークベース上) */
.wj-flexgrid .wj-cell:not(:is(.wj-header, .wj-group, .wj-state-selected, .wj-state-multi-selected)) {
  background: rgb(255 255 255 / 0.2);
}

.wj-flexgrid .wj-alt:not(:is(.wj-header, .wj-group, .wj-state-selected, .wj-state-multi-selected)) {
  background: rgb(255 255 255 / 0.3);
}

動作確認

再度アプリケーションを実行すると、以下のようにゲージやチャート、データグリッドを含むダッシュボードを表示することができます。

Wijmo×React×Bootstrapで作成したダッシュボード

今回作成したアプリケーションは以下よりダウンロード可能です。

さいごに

Wijmoの各種コントロールを使用して、ReactとBootstrapでデータを可視化するダッシュボード画面を作成する方法をご紹介しました。

製品サイトでは、Wijmoの機能を手軽に体験できるデモアプリケーションやトライアル版も公開しておりますので、こちらもご確認ください。

また、ご導入前の製品に関するご相談、ご導入後の各種サービスに関するご質問など、お気軽にお問合せください。

\  この記事をシェアする  /