PR

WordPressの投稿にVue3のSFCを読み込んでChart.jsのチャートを表示する

Vue3
スポンサーリンク
スポンサーリンク

 

 

 

環境

このページで使用しているフレームワークやライブラリのバージョンは、以下のとおりです。

vue.js3.2.31
vue3-sfc-loader0.8.4
chart.js3.6.0

この記事を書いた後、「vue-chartjs」のバージョン5がリリースされました。
「vue-chartjs」バージョン5では、最新の「Chart.js」バージョン4をサポートしています。
「Chart.js」バージョン4では、チャートのDataやOptionsが変更された場合、再描画するようになりました。なので、この記事で紹介した「Chart.js」を直接ラップする方法より、「vue-chartjs」バージョン5を利用した方が簡単で便利です。

Chart.jsとは?

Chart.js」は、チャート(グラフ)を表示するJavaScriptライブラリです。
機能の豊富さと表示の美しさで、この「Chart.js」を越えるJavaScriptライブラリはないと思います。
MITライセンスで、無償で利用できます。

2021年の4月に、メジャーバージョンが「2.x」から「3.x」に変わり、仕様も大きく変わっています
なので、今から「Chart.js」を利用する方は、バージョン「3.x」を使うことをお勧めします。

Vue3で「Chart.js」を使えるようにしたコンポーネント「vue-chartjs」もあります。
「vue-chartjs」のバージョン「4.x」から、「Chart.js」のバージョン「3.x」に対応しているので、Vueの環境で簡単に「Chart.js」を使いたい場合は、「vue-chartjs」を利用すると良いでしょう。

ただし、「vue-chartjs」のバージョン「4.0.5」では、ドキュメントに以下の記載があり、チャートのOptionsを変更しても、チャートが再描画されません。

Options
The options object is not currently implemented reactively. Therefore, if you dynamically change the chart options, they will not be recognized by the update data handler.

なので、この記事では「vue-chartjs」を使わず、「Chart.js」を直接ラップしたSFCを作成し、このコンポーネントでチャートを表示します。

WordPressの投稿にVueのSFCを読み込む方法については、以下の記事で紹介しています。

棒グラフを表示する

まずは、簡単な棒グラフを表示してみましょう。
このチャートは、「Chart.js」のドキュメントの最初の例に出てくる棒グラフです。

チャートが表示されている部分には、WordPressのブロックエディタで以下の「カスタム HTML」ブロックを配置しています。

<script src="https://cdn.jsdelivr.net/npm/vue@3.2.31/dist/vue.global.js"></script>

<div id="app"></div>

<script type="module">
    ////////////////////////////////////////////////////////////////////////////////
    // vue3-sfc-loader モジュール
    ////////////////////////////////////////////////////////////////////////////////
    import { loadModule } from "https://cdn.jsdelivr.net/npm/vue3-sfc-loader@0.8.4/dist/vue3-sfc-loader.esm.js";

    ////////////////////////////////////////////////////////////////////////////////
    // vue3-sfc-loader オプション
    // SFCファイルから外部のモジュールをimportできるオプション
    // 参考:https://github.com/FranckFreiburger/vue3-sfc-loader/issues/14#issuecomment-908849863
    ////////////////////////////////////////////////////////////////////////////////
    const vue3_sfc_loader_options = {
        moduleCache: { vue: Vue },
        getFile(url) {
            url = /.*?\.js|.mjs|.css|.less|.vue$/.test(url)
                ? url
                : `${url}.vue`;
            const type = /.*?\.js|.mjs$/.test(url)
                ? ".mjs"
                : /.*?\.vue$/.test(url)
                ? ".vue"
                : /.*?\.css$/.test(url)
                ? ".css"
                : ".vue";
            const getContentData = (asBinary) =>
                fetch(url).then((res) =>
                    !res.ok
                        ? Promise.reject(url)
                        : asBinary
                        ? res.arrayBuffer()
                        : res.text()
                );
            return { getContentData: getContentData, type: type };
        },
        addStyle(textContent) {
            let styleElement = document.createElement("style");
            document.head.insertBefore(
                Object.assign(styleElement, { textContent }),
                document.head.getElementsByTagName("style")[0] || null
            );
        },
        handleModule(type, getContentData, path, options) {
            switch (type) {
                case ".css":
                    return options.addStyle(getContentData(false));
                case ".less":
                    console.error(".......");
            }
        },
        log(type, ...args) {
            console.log(type, ...args);
        },
    };

    ////////////////////////////////////////////////////////////////////////////////
    // Vue.js アプリケーションインスタンス
    ////////////////////////////////////////////////////////////////////////////////
    const app = Vue.createApp({
        components: {
            "bar-chart": Vue.defineAsyncComponent(() =>
                loadModule(
                    "../wp-content/themes/cocoon-child-master/vue-components/bar_chart.vue",
                    vue3_sfc_loader_options
                )
            ),
        },
        template: `<bar-chart />`,
    });
    app.mount("#app");
</script>

コードの解説

前回の紹介記事「ブラウザ環境でVue3の単一ファイルコンポーネントを使う」からの差分を中心に解説します。

    ////////////////////////////////////////////////////////////////////////////////
    // Vue.js アプリケーションインスタンス
    ////////////////////////////////////////////////////////////////////////////////
    const app = Vue.createApp({
        components: {
            "bar-chart": Vue.defineAsyncComponent(() =>
                loadModule(
                    "../wp-content/themes/cocoon-child-master/vue-components/bar_chart.vue",
                    vue3_sfc_loader_options
                )
            ),
        },
        template: `<bar-chart />`,
    });
    app.mount("#app");

アプリケーションインスタンスの作成とマウントの方法は、前回の「ブラウザ環境でVue3の単一ファイルコンポーネントを使う」と同じ流れです。
違うのは、"bar_chart.vue"というSFCを"bar-chart"という名前のコンポーネントとして読み込んで、`<bar-chart />`で表示している点です。

"bar_chart.vue"の中身

"bar_chart.vue"の全体です。

<script setup>
////////////////////////////////////////////////////////////////////////////////
// Chart.js モジュール
////////////////////////////////////////////////////////////////////////////////
// Chart.jsモジュールのimportとregisterを行う
import { Chart, registerables } from "../chart.js-3.6.0/dist/chart.esm.js";
Chart.register(...registerables);

////////////////////////////////////////////////////////////////////////////////
// data
////////////////////////////////////////////////////////////////////////////////
let chart = {}; // チャートオブジェクト
const chart_height = 400; // チャートの高さ
let chart_options = { // チャートのオプション
    scales: {
        y: {
            beginAtZero: true
        }
    },
};
let chart_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
    }]
};

////////////////////////////////////////////////////////////////////////////////
// onMountedライフサイクルフック
////////////////////////////////////////////////////////////////////////////////
Vue.onMounted(() => {
    renderChart();
});

////////////////////////////////////////////////////////////////////////////////
// チャートを描画する
////////////////////////////////////////////////////////////////////////////////
const renderChart = () => {
    const ctx = document.getElementById("chart").getContext("2d");
    chart = new Chart(ctx, {
        type: "bar",
        data: chart_data,
        options: chart_options,
    });
};
</script>

<template>
    <div>
        <canvas id="chart" width="100%" v-bind:height="chart_height"></canvas>
    </div>
</template>

<style>
</style>

"bar_chart.vue"のセットアップは、

<script>
export default defineComponent({
    setup() {
        …
    },
});
</script>

を使わずに、

<script setup>
    …
</script>

の中でやっています。

<script setup>については、Vue3の公式ドキュメント「SFC<script setup>」を参照してください。
<script setup>の利便性については、以下の記事が参考になります。

////////////////////////////////////////////////////////////////////////////////
// Chart.js モジュール
////////////////////////////////////////////////////////////////////////////////
// Chart.jsモジュールのimportとregisterを行う
import { Chart, registerables } from "../chart.js-3.6.0/dist/chart.esm.js";
Chart.register(...registerables);

「Chart.js」ライブラリ一式をCDNサイト「https://www.jsdelivr.com/package/npm/chart.js」からダウンロードして、適当なフォルダに保存します。
「ちょいプラ素材」のブログは、WordPressのテーマに「Cocoon」を使っています。
今回は、「Cocoon」の子テーマ「cocoon-child-master」の下に"chart.js-3.6.0"というフォルダを作って保存しました。

SFC"bar_chart.vue"が保存されているフォルダからの相対パス"../chart.js-3.6.0/dist/chart.esm.js"で「Chart.js」のESモジュール"chart.esm.js"をインポートします。

「Chart.js」モジュールをインポートした後は、実際に利用するモジュールの登録(register)が必要です。1つ1つのモジュールを登録するのは面倒なので、"Chart.register(...registerables);"でまとめて登録しています。

////////////////////////////////////////////////////////////////////////////////
// data
////////////////////////////////////////////////////////////////////////////////
let chart = {}; // チャートオブジェクト
const chart_height = 400; // チャートの高さ
let chart_options = { // チャートのオプション
    …
};
let chart_data = { // チャートのデータ
    …
};

このSFCだけで使うデータを宣言しています。
それぞれのデータの意味は、コメントを見てください。

////////////////////////////////////////////////////////////////////////////////
// onMountedライフサイクルフック
////////////////////////////////////////////////////////////////////////////////
Vue.onMounted(() => {
    renderChart();
});

このSFCがマウントされたときに呼ばれる、ライフサイクルフックです。
SFCがマウントされたら、この後に説明する"renderChart();"を実行します。

////////////////////////////////////////////////////////////////////////////////
// チャートを描画する
////////////////////////////////////////////////////////////////////////////////
const renderChart = () => {
    const ctx = document.getElementById("chart").getContext("2d");
    chart = new Chart(ctx, {
        type: "bar",
        data: chart_data,
        options: chart_options,
    });
};

チャートを描画する処理です。
この後の<template>タグで配置される<canvas id="chart">要素に「Chart.js」の"bar"モジュール(棒グラフモジュール)を紐づけます。

また、"chart = new Chart();"で作成した「Chart.js」のモジュール(JavaScriptのオブジェクト)を変数"chart"に保存しておきます。
こうすることで、「Chart.js」のメソッド(関数)を使って、作成した"bar"モジュールを操作することができます。

<template>
    <div>
        <canvas id="chart" width="100%" v-bind:height="chart_height"></canvas>
    </div>
</template>

<template>タグで<canvas id="chart">要素を配置します。

ユーザーインターフェースで棒グラフを操作する

次に、先ほどの棒グラフを表示するSFCにユーザーインターフェースを追加して、チャートを操作してみましょう。
ユーザーインターフェースとデータの連携は、Vueが得意とするところです。

[データを変更]ボタンを押すと、データが0~50の範囲の整数にランダムに変化します。
[縦軸の最大値]スライダーを動かすと、チャートの縦軸の最大値を変更できます。

"bar_chart_with_ui.vue"の中身

新しく作成したSFC"bar_chart_with_ui.vue"の全体です。

<script setup>
////////////////////////////////////////////////////////////////////////////////
// Chart.js モジュール
////////////////////////////////////////////////////////////////////////////////
// Chart.jsモジュールのimportとregisterを行う
import { Chart, registerables } from "../chart.js-3.6.0/dist/chart.esm.js";
Chart.register(...registerables);

////////////////////////////////////////////////////////////////////////////////
// data
////////////////////////////////////////////////////////////////////////////////
let chart = {}; // チャートオブジェクト
const chart_height = 400; // チャートの高さ
let y_max = Vue.ref(50); // 縦軸の最大値
let chart_options = { // チャートのオプション
    scales: {
        y: {
            beginAtZero: true,
            min: 0,
            max: y_max.value,
        }
    },
};
let chart_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
    }]
};

////////////////////////////////////////////////////////////////////////////////
// onMountedライフサイクルフック
////////////////////////////////////////////////////////////////////////////////
Vue.onMounted(() => {
    renderChart();
});

////////////////////////////////////////////////////////////////////////////////
// チャートを描画する
////////////////////////////////////////////////////////////////////////////////
const renderChart = () => {
    const ctx = document.getElementById("chart2").getContext("2d");
    chart = new Chart(ctx, {
        type: "bar",
        data: chart_data,
        options: chart_options,
    });
};

////////////////////////////////////////////////////////////////////////////////
// チャートのデータを変更する
////////////////////////////////////////////////////////////////////////////////
const onChangeData = () => {
    chart_data.datasets[0].data = [
        Math.floor(Math.random() * 51),
        Math.floor(Math.random() * 51),
        Math.floor(Math.random() * 51),
        Math.floor(Math.random() * 51),
        Math.floor(Math.random() * 51),
        Math.floor(Math.random() * 51),
    ];
    chart.update(); // チャートを再描画する
};

////////////////////////////////////////////////////////////////////////////////
// 縦軸の最大値を変更する
////////////////////////////////////////////////////////////////////////////////
const onChangeYMax = () => {
    chart_options.scales.y.max = y_max.value;
    chart.update(); // チャートを再描画する
};
</script>

<template>
    <div>
        <canvas id="chart2" width="100%" v-bind:height="chart_height"></canvas>
        <form class="chart-ui-form">
            <input type="button" class="data-change-button" v-on:click="onChangeData" value="データを変更" />
            <div class="chart-range-container">
                <span style="margin-right: 0.2em">縦軸の最大値 10</span>
                <input type="range" id="view-time-start-range" class="y-max-range" v-model.number="y_max" min="10"
                    max="100" step="10" v-on:mouseup="onChangeYMax" v-on:touchend="onChangeYMax" />
                <span style="margin-left: 0.2em">100</span>
            </div>
        </form>
    </div>
</template>

<style>
.chart-ui-form {
    margin: 20px;
}

.chart-range-container {
    margin-left: 10px;
    margin-top: 10px;
}

.data-change-button {
    color: #fff;
    background-color: #3b83d8;
    font-weight: bold;
    border-radius: 4px;
    display: inline-block;
    cursor: pointer;
    line-height: normal;
    padding: 7px 13px;
    text-decoration: none;
    text-align: center;
    font-size: 14px;
    border: 2px solid transparent;
    position: relative;
}

.y-max-range {
    max-width: 40%;
    width: 40%;
}
</style>

SFCの変更点を解説します。

let y_max = Vue.ref(50); // 縦軸の最大値
let chart_options = { // チャートのオプション
    scales: {
        y: {
            beginAtZero: true,
            min: 0,
            max: y_max.value,
        }
    },
};

チャートのオプションを指定する"chart_options"の"scale.y"オブジェクトに、"min"と"max"を追加しました。
"min"、"max"を指定しない場合は、データの値に応じて、「Chart.js」が軸の表示を最適に調整してくれます。

"max"には、新しく追加した変数"y_max"の値を設定します。
また、変数"y_max"は、Vueの「refメソッド」でリアクティブな変数にしています。

////////////////////////////////////////////////////////////////////////////////
// チャートのデータを変更する
////////////////////////////////////////////////////////////////////////////////
const onChangeData = () => {
    chart_data.datasets[0].data = [
        Math.floor(Math.random() * 51),
        Math.floor(Math.random() * 51),
        Math.floor(Math.random() * 51),
        Math.floor(Math.random() * 51),
        Math.floor(Math.random() * 51),
        Math.floor(Math.random() * 51),
    ];
    chart.update(); // チャートを再描画する
};

[データを変更]ボタンが押されたときに呼ばれるイベントハンドラーです。
チャートのデータが0~50の範囲の整数にランダムに変化します。
実際の用途では、チャートに表示したいデータの設定処理を実装します。

その後、"chart.update();"でチャートを再描画します。

////////////////////////////////////////////////////////////////////////////////
// 縦軸の最大値を変更する
////////////////////////////////////////////////////////////////////////////////
const onChangeYMax = () => {
    chart_options.scales.y.max = y_max.value;
    chart.update(); // チャートを再描画する
};

[縦軸の最大値]スライダーを動かした後、マウスのボタンを離すと呼ばれるイベントハンドラーです。
チャートのオプション"chart_options.scales.y.max"の値を変更した後、"chart.update();"でチャートを再描画します。

<template>
    …
</template>

<style>
    …
</style>

<template>タグと<style>タグで、SFCの外観(ビュー)を定義します。

以上のように、VueのSFCを使うと、

  • ユーザーインターフェースの操作をトリガーにして、
  • 必要なデータやオプションを変更し、
  • 「Chart.js」のupdate()メソッドを呼び出すだけで、

チャートの表示を動的に、しかも簡単に操作できます。

まとめ

「Chart.js」を直接ラップしたSFCを作成し、コンポーネントでチャートを表示する方法を紹介しました。

チャートのデータやオプションを動的に変えることができるので、WordPressのデータベースのデータをビジュアルに表示したり、データが保存されているCSVファイルをアップロードして、CSVファイルのデータをチャートで確認したり、いろいろなシチュエーションで活用できるはずです。

次は、WordPressの投稿に「vue-good-table-next」のテーブルを表示してみましょう。

コメント