WordPress / プログラミング

AmbientのデータをJavaScript(Vue)でWordPressに表示させる

こんにちは、Lineaです。

前回でM5Stickと土壌水分センサを使ったガーデニング水やりセンサが一応の完成をみて運用し始めました。

しばらく使ってみていて、感じたことは様子を見るために毎回Ambientのページにログインして閲覧しないといけない点です。ログインが必要なので手間を感じると同時に、他の人に様子を共有することも難しいです。
特に実際にガーデニングでいじるのは彼女の方なので簡単にチェックできるようにする必要がありました。

Ambientにも公開チャンネルというログイン不要で公開する機能はあるのですが、折角なので自分でこのサイトにグラフ表示してみようというのが今回の目的です。
WordPressで独自にコンテンツを表示するのにはJavaScriptを使う必要があるのですが、勉強も兼ねて最近流行りっぽいフレームワークのVueを使ってみました。

完成したグラフはIoTの部屋という固定ページを作成して、表示してみました。良ければ見てみてください。

Vue(JS)コード

VueはJavaScriptのフレームワークです。ユーザー操作やデータ変更などで動的に表示が変化するコンテンツを簡単に作ることができます。特徴としてはテンプレート構文によって簡単にHTMLの要素とバインド(紐付け)ができることと、コンポーネントによって機能、役割の分割が行いやすいことです。
小さい要素から大きくて複雑なアプリレベルまで拡張や分割がしやすいのかなと感じました。とはいえ自分は触り始めたばかりなのでまだまだよく分かっていないですが…
とりあえず、HTML操作が簡単にできるというだけでも使う価値はあったかなと思います。公式のドキュメントも日本語化されているのもありがたかったです。

それでは今回作成したJavaScriptコードです

console.log('ambient chart');

function  convertDate(date){
  return{year:date.getFullYear(),month:date.getMonth(),day:date.getDate()};
}

const app = Vue.createApp({
  data() {
    return{
    ambient:[{value:0,label:'ambientデータ'}],
    listShow:false,
    am:null,
    selectDate:{year:2021,month:1,day:1},
    }
  },
  computed:{
    dateStr(){
      try{
      var ld = this.selectDate;
      return ld.year+ "年" + (ld.month+1) + "月" + ld.day + "日";
      }
      catch{
        return "Error:" + ld;
      }
    },
  },
  methods:{
    onGetLastData(lastData){
      if(lastData.status == 200){
        var date = new Date();
        date.setTime(Date.parse(lastData.data[0].created));
        this.selectDate = convertDate(date);
        this.am.read({date:date.toUTCString()},this.onGetAmbient);
      }
    },
    onGetAmbient(response){
      this.ambient.splice(0);
      console.log('try get ambient data');
      if(response.status == 200){
        //新しい方が先頭になってるので反転
        response.data.reverse().forEach((element) => {
          var dataDate = new Date();
          dataDate.setTime(Date.parse(element.created));
          let labelDate = dataDate.getHours()+':'+dataDate.getMinutes();
          this.ambient.push({value:element.d1, label:labelDate, date:element.created});
        });
        this.$refs.chartComp.createChart();
      }
      else{
        this.ambient.push('cannnot get ambient data');
      }
    },
    onClickDateChange(dir){
      if(this.selectDate != null){
        var cDate = this.selectDate; 
        var date = new Date();
        date.setFullYear(cDate.year);
        date.setMonth(cDate.month);
        date.setDate(cDate.day+dir);
        this.selectDate = convertDate(date);
        this.am.read({date:date.toUTCString()},this.onGetAmbient);
      }
    },
  },
  mounted(){
    this.am = new Ambient('チャンネルID', 'ライトキー', 'リードキー');
    this.am.read({n:1}, this.onGetLastData);
  },
  template:`
    <div v-on:click="listShow = !listShow">
        Ambient取得
        <ul v-if="listShow">
        <li v-for="data in ambient">
            Ambient:{{data}}
        </li>
        </ul>
    </div>
    <div>
        <button v-on:click="onClickDateChange(-1)" style="display:inline;">◀</button>
        <h2 style="display: inline; padding-left:4px; padding-right:4px;">{{dateStr}}のグラフ</h2>
        <button v-on:click="onClickDateChange(1)" style="display:inline;">▶</button>
    </div>
    <chart-view ref="chartComp" :chart-data="ambient"></chart-view>
  `
});

app.component('chart-view', {
  data() {
    return {
      count: 0,
    }
  },
  props: ["chartData","options"],
  computed:{
    chartValues:function(){
   //グラフ表示用のデータ形式に整形する
      return this.chartData.map(item => { return {x:item.date, y:item.value}});
    },
  },
  methods:{
    createChart() {
      console.log(this.chartData);
      var ctx = this.$refs.my_chart.getContext('2d');
      var myChart = new Chart(ctx, {
        type: 'line',//線グラフ指定
        data: {
          datasets: [{ //データの指定
              label: '水分量', //データ系列のラベル
              data: this.chartValues //データ {x:hoge, y:huga}のオブジェクト配列で渡す
          }]
        },
        options:{
          scales: {
            xAxes: [{
              type: 'time', //X軸を時間形式に指定する
              time: { //時間の扱い指定
                  unit: 'hour', //表示区切り時間の単位
                  displayFormats: {
                    hour: 'HH:mm' //時間表示の形式指定
                  },
              }
            }],
            yAxes: [{
                ticks: {
                    beginAtZero: true //軸の下限を0にする
                }
            }]
          },
          tooltips:{ //値をマウスオーバーした際のツールチップ
            callbacks:{ //コールバック関数を定義して表示を変更させる
              title:function(tooltipItem, data){
                return dayjs(tooltipItem[0].xLabel).format('YYYY年M月D日 HH:mm');
              },
            }
          }
        } 
      });
    },
  },
  template: `
    <canvas ref="my_chart"></canvas>
    <button v-on:click="count++">
      You clicked me {{ count }} times.
    </button>`
});

app.mount('#ambient-chart');

HTMLのほうは最終的にWordPressの方に書くのですが、テスト用の対応したHTMLを作っておくと確認が楽です。以下のものを作りました。

<!DOCTYPE html>
<head title="Test">
</head>
<body>
    <div>
        テスト表示
    </div>
    <div id="ambient-chart">
    </div>
</body>

<script src="https://unpkg.com/vue@3.2.6/dist/vue.global.prod.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://unpkg.com/ambient-lib@1.0.3/lib/ambient-lib.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.10.6/dayjs.min.js"></script>
<script src="../js/ambient-chart.js"></script>

全部を説明すると大変なのでポイントと思ったところをいくつか紹介します。

Vueの使い方

Vueでは基本となるアプリに、パーツとなるコンポーネントを追加して作ります。最後にアプリをHTMLの要素にマウントすることでページ上での描画位置を指定します。ここではHTMLのidに紐づけてマウントしています。
マウント時にはアプリ内のmounted()イベントが呼ばれます。ページ表示時のスタートイベントとして使っています。

const app = Vue.createApp({
  data() {
    //メンバ変数
  },
  computed:{
    //メンバ変数のように使えるが処理が走る値 C#でいうプロパティ
  },
  methods:{
    //メンバ関数
  },
  mounted(){
    //アプリがマウントされた際に呼ばれるイベント関数
  },
  template:
    //挿入されるHTML
});

//コンポーネントの追加
app.component('chart-view', {
  data() {
  },
  props: //コンポーネントがデータを受け取るための変数
  computed:{
  },
  methods:{
  },
  template: 
});
//アプリをマウント id=ambient-chartのHTML要素内に描画される
app.mount('#ambient-chart');

アプリやコンポーネントには用途に応じた値や関数を定義できます。クラスのようなものと考えると理解しやすいかと思います。
中でもtemplateは自分が追加された箇所に挿入するHTMLを書くことができる点で特殊です。ですが、同じファイルに表示部分を書くことができるので一々HTML側に移動する手間がなくなれると便利に感じました。

表示周りではdatacomputedのような値をHTML上でバインドさせるために、以下のようにテンプレート(HTML)上で{{ }}で表示したい変数名を囲みます。これだけで勝手に値が紐付いて表示されます。
また、v-on属性ではボタンイベントなど入力に紐付いた処理をバインドできます。面白い点として、バインドの文字列で直接JavaScript式で処理を書ける点です。短い記述で表示を紐付けられるので便利ですね

data() {
    return {
      count: 0, //ボタンクリック回数の変数
    }
  },
template: `
    <button v-on:click="count++"> //v-on内でJS式でカウンタ変数を更新している
      You clicked me {{ count }} times. //{{ }}で囲った変数がHTMLに描画される
    </button>`

Ambientデータの読み込み

AmbientはJavaScript用のライブラリがあるのでそれを読み込む必要があります。以下のように読み込みますが依存するaxiosライブラリも読み込む必要があります。

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://unpkg.com/ambient-lib@1.0.3/lib/ambient-lib.js"></script>

Ambientからデータを取得するには以下のようにAmbientオブジェクトを作成します。作成時にチャンネルIDとキーを引数に渡しますが、読み込むだけの場合ライトキーは空白でも問題ありませんでした。
取得はread()で行います。引数の渡し方次第で取得範囲を様々に指定できます。第2引数は読み込み時のコールバック関数を指定できます。無名関数も使えるのですがVueでのthisの挙動と相性が悪かったので別途関数を作っています。
取得したデータはコールバック関数の引数にオブジェクトとして渡されます。

    this.am = new Ambient('チャンネルID', 'ライトキー', 'リードキー');
    this.am.read({n:1}, this.onGetLastData); //最新1個を読み込む
    this.am.read({date:Date().toUTCString()},this.onGetAmbient); //Date型のUTC文字列で指定した日のデータを取得できる

グラフ表示

グラフの表示にはChart.jsライブラリを使用しています。VueでChart.jsを使いやすくしたライブラリもあるのですが、今回使ったVue3には対応していなかったので素のChart.jsを使用しています。

Chart.jsで日付データを横軸にプロットするためには日付ライブラリを含んだバージョンを読み込む必要がありました。bundleが付くものが対応したバージョンになります。

//bundleつきでないと日付に合わせて表示できない
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.bundle.min.js"></script>

グラフの表示にはChartオブジェクトを作成します。このとき表示コンテキストの取得のためにHTML要素を直接取得する必要があります。$refsを使用するとHTMLでref=を指定している要素を取得できます。
Chartオブジェクトは作成時に表示するデータや形式を指定します。データだけでなく軸の設定や表示書式まで細かく設定できますが、そのおかげで行数は増えがちです…
細かい指定はコード全文の方を参照してください

    createChart() {
   //$refsで描画するHTML要素を取得する
      var ctx = this.$refs.my_chart.getContext('2d');
     //コンテキストを指定してグラフ作成
      var myChart = new Chart(ctx, {
    //ここにデータや表示の指定を書く
      });
    },

WordPressページでの表示

JavaScriptのほうが準備できたらいよいよWordPressでの表示適用です。と言っても意外とこれが面倒でした。
WordPressのページ上で表示するにはjsファイルとHTMLファイルをアップロードして終わりというわけには行きません。以下の準備が必要になります。

  • 子テーマの準備
  • phpでのライブラリ、jsファイル読み込み処理

まず、WordPressのfunctions.phpを触ることになるのですが、これはテーマのフォルダ内に存在します。テーマ本体でfunctions.phpを触ると何かあった際に致命的な表示の乱れや、最悪ページが表示されなくなる可能性があります。そこで、編集に失敗しても本テーマに影響しないように、テーマを継承した子テーマを用意したほうが良いでしょう。

子テーマの作り方はまた長くなるのでここでは割愛します(余裕があれば記事にしたいと思います)
子テーマが用意できたら子テーマのfunctions.php内に必要なライブラリの登録処理を追加します。

/**
 * IOT部屋だけスクリプト読ませる
 */
function load_scripts_ambient() {
  if ( is_page( 'iot_room' ) ){
  wp_enqueue_script('vue','https://unpkg.com/vue@3.2.6/dist/vue.global.prod.js');
	wp_enqueue_script('axios','https://unpkg.com/axios/dist/axios.min.js');
  wp_enqueue_script('ambient','https://unpkg.com/ambient-lib@1.0.3/lib/ambient-lib.js');
  wp_enqueue_script('chartJs','https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.bundle.min.js');
  wp_enqueue_script('dayjs','https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.10.6/dayjs.min.js');
  wp_enqueue_script( 'ambient-chart', get_stylesheet_directory_uri() . '/js/ambient-chart.js',array('vue'),false,true);
  }
}
add_action( 'wp_enqueue_scripts', 'load_scripts_ambient' );

このとき特定のページでのみグラフを表示するので、そのページでのみライブラリを読み込むよう分岐しています。

ここまでくれば後は最後!表示したいページでカスタムHTMLでアプリのマウント先の要素を書いて追加するだけです。

というわけでサイトに自作JavaScriptでコンテンツを表示できました。今回は機能優先でCSSでの見た目などは整えていないのですが、今後追加していこうと思います。

他にも色々ネタが溜まっているのでなるべく早く書いていきたいです。
それではまた!

投稿者

Linea
本業はゲームプログラマー プログラミングやIT系の記事を書いていきます アイコンは愛車のシトロエン DS3です

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA