NiftyCloud
monaca
NCMB

「すべてはにゃんこのため」にゃんこ用体重計のデータをモバイルで参照する

体重推移グラフのフロントエンドを作成

以前作った体重計が完成してからおよそ3ヶ月間使ってきているが単3電池4本でこれまで十分に使用できている。
このぶんならおそらく一回の充電で4〜5ヶ月使えると実用にも十分な気がしてきた。
そこで、そろそろ病院で体重の話をする時もうちの2にゃん分の体重をCSVやNiftyMobileBackendのデータストアをみせるわけにもいかないのでフロントエンドを作ることにした。
実際のグラフ画面はこんな感じ。
スクリーンショット 2018-01-09 4.07.13.png

フロントエンドはMonacaで

バックエンドにNifty Mobile Backendを使っているので当然の選択といえるかもしれないが、ここは一番簡単にモバイルで実現出来る方法で行うことにする。
やりたいことは次の通り。
-認証画面を作成
-グラフ表示(2頭居るので2頭分同時に表示)

ということで、Monacaでつくるならばchart.jsを使って簡単に作れそうだということから早速作る。
作成にあたってはこちらを参考にさせていただいた。
monacaクラウドIDEとChart.jsでグラフを描画する
また、会員認証の機能などは古いが公式のドキュメントがある

コンポーネントのアップデートと追加コンポーネントの読み込み

公式のドキュメントのサンプルをインポートしてプロジェクトを作成するが、コンポーネントのバージョンが古いのでできるだけ最新のコンポーネントにアップデートする。
その上でncmbとChart.jsのコンポーネントを追加する。
スクリーンショット 2018-01-09 3.01.09.png

Chart.jsを追加する際はローダーに追加するファイルでchart.min.jsのみを選択します。
※これ以外のファイルを追加すると、Monacaの画面表示に影響が出ます

すべてのコンポーネントを読み込むとMonaca上でこのように見えるはず。
スクリーンショット 2018-01-09 3.09.00.png

サンプルファイルの修正(HTML)

会員認証のサンプルプロジェクトはID/PW認証、メール認証、匿名認証の機能が実装されているが、そこまで要らないのでメール認証の機能だけ残して後はバッサリ切ることにした。
ひとまず自分のためだけなのでバックエンドの会員管理については認証さえできればいいので管理画面から会員レコードを登録しても良い。

修正するにあたり、認証後の詳細ページからボタンでグラフ表示ページに推移することにした。

また、ヘッダにログアウトボタンをつけているがiPhoneの場合一番上の行に被ってしまうためヘッダを下記のようにした。

index.html
<header data-role="header" data-position="fixed" data-theme="c">
<BR><img src="images/budo.jpg" width=50 height=50 align="left" />にゃんこ体重計会員<BR>ステータス表示
<BR><a href="#yesNoDialog_logout" class="ui-btn-right" style="top:20px" data-rel="popup" data-position-to="window" data-role="button" data-inline="false" data-theme="d">ログアウト</a>
</header>

詳細ページセクションの表の後にボタンを追加しグラフ表示に推移するようにidを「DisplayGraphBtn」と書く

index.html
<a href="#" id="DisplayGraphBtn" data-role="button" data-inline="false" data-theme="b">グラフ表示</a>

なお、footerはバッサリ消す。

出来たらGraphページを追加する。

index.html
        <!-- Graphページ -->
        <div data-role="page" id="GraphPage">
            <header data-role="header" data-position="fixed" data-theme="c">
                <BR><img src="images/budo.jpg" width=50 height=50 align="left" />にゃんこ体重計<BR>グラフ表示
            </header>
            <section data-role="content">
                <!--Detail show-->
                <p><center>過去30回計測のグラフです</center></p>
                <!--CurrentUserList-->
                <center>                
                    <canvas id="lineChart" height="300" width="480"></canvas>
                </center>
                <a href="#" id="backDetailPage" data-role="button" data-inline="false" data-theme="b">戻る</a>
                <!--Dialog box confirming logout-->
                <div data-role="popup" id="yesNoDialog_logout" data-overlay-theme="a" data-theme="c" data-dismissible="false" style="max-width:400px;" class="ui-corner-all">
                    <div data-role="header" data-theme="c" class="ui-corner-top" style="top: 0 !important;">
                        <h1>確認</h1>
                    </div>
                    <div align="middle" data-role="content" data-theme="d" class="ui-corner-bottom ui-content">
                        <h3 class="ui-title"></h3>
                        <p>ログアウトしてもよろしいですか?</p>
                        <a href="#" id="YesBtn_logout" data-role="button" data-inline="true" data-transition="flow" data-theme="b">はい</a>
                        <a href="#" data-role="button" data-inline="true" data-rel="back" data-transition="flow" data-theme="b">いいえ</a>
                    </div>
                </div>
            </section>
        </div>

基本コピペだが、グラフ表示用のcanvasを追加するのとヘッダを少し変更しているくらいで特別なことはしない。
編集が終わったら、app.jsの編集を行う。

サンプルファイルの修正(Javascript)

サンプルにapp.jsがあるのでそちらを編集して機能を実装する。

公式のドキュメントに従ってAPPキーを入れたら下記のソースを追加する。

app.js
function displayLineChart() {       
// loading の表示
$.mobile.loading('show');
//降順で取得
var petName1 = currentLoginUser.get("petName1");
var petName2 = currentLoginUser.get("petName2");
var graph_data = {
    labels: [],
    datasets: [
        {
            label: petName1,
                    backgroundColor: "rgba(179,0,198,0.2)",
                    borderColor: "rgba(179,181,198,1)",
                    pointBackgroundColor: "rgba(179,181,198,1)",
                    pointBorderColor: "#fff",
                    pointHoverBackgroundColor: "#fff",
                    pointHoverBorderColor: "rgba(179,181,198,1)",

                    fillColor: "rgba(179,0,198,0.2)",
                    strokeColor: "rgba(254,230,170,1)",
                    pointColor: "rgba(254,230,170,1)",
                    pointStrokeColor: "#fff",
                    pointHighlightFill: "#fff",
                    pointHighlightStroke: "rgba(220,220,220,1)",
            data: []
        },
        {
            label: petName2,
                    backgroundColor: "rgba(0,179,198,0.2)",
                    borderColor: "rgba(179,181,198,1)",
                    pointBackgroundColor: "rgba(179,181,198,1)",
                    pointBorderColor: "#fff",
                    pointHoverBackgroundColor: "#fff",
                    pointHoverBorderColor: "rgba(179,181,198,1)",

                    fillColor: "rgba(0,179,198,0.2)",
                    strokeColor: "rgba(2,230,170,1)",
                    pointColor: "rgba(2,230,170,1)",
                    pointStrokeColor: "#ff0",
                    pointHighlightFill: "#ff0",
                    pointHighlightStroke: "rgba(120,120,120,1)",
            data: []
        }
    ]
};
var uid1 = currentLoginUser.get("uid_1");//ペットid 1
var uid2 = currentLoginUser.get("uid_2");//ペットid 2

console.log(uid1);
var P_Weight = ncmb.DataStore("weight");
P_Weight.equalTo("uid",uid1)
        .order("createDate", true)
        .limit(30)
        .fetchAll()
        .then(function(results){
            console.log("Successfully retrieved " + results.length + " scores.");
            graph_data.datasets[0].label = petName1;
            console.log(results[0]);
            var length_count = results.length;
            for (var i = 0; i < results.length; i++) {
                var object = results[length_count-i-1];
                graph_data.labels[i] = i+1;
                graph_data.datasets[0].data[i] =  object.weight;
            }
        })
        .catch(function(error){
                 /* 処理失敗 */
                 alert("グラフデータ生成でエラーが発生しました:" + error);
                 console.log("グラフデータ生成でエラーが発生しました:" + error);
                 // loading の表示終了
                 $.mobile.loading('hide');
        });



P_Weight.equalTo("uid",uid2)
        .order("createDate", true)
        .limit(30)
        .fetchAll()
        .then(function(results){
            console.log("Successfully retrieved " + results.length + " scores.");
            graph_data.datasets[1].label = petName2;
            console.log(results[0]);
            var length_count = results.length;
            for (var i = 0; i < results.length; i++) {
                var object = results[length_count-i-1];
                graph_data.datasets[1].data[i] =  object.weight;
            }
            var ctx = document.getElementById("lineChart").getContext("2d");
            var options = { };
            var lineChart = new Chart(ctx,{
               type : "line",
               data : graph_data,
               scaleShowVerticalLines: false,
               scaleShowHorizontalLines: false,
               scaleOverride : true,
               scaleSteps : 10,
               scaleStepWidth : 0.1,
               scaleStartValue : 25.5, 
            });
            $.mobile.changePage('#GraphPage');
        })
        .catch(function(error){
                 /* 処理失敗 */
                 alert("グラフ表示でエラーが発生しました:" + error);
                 console.log("グラフ表示でエラーが発生しました:" + error);
                 // loading の表示終了
                 $.mobile.loading('hide');
        });

}

データストアの使い方はこちらを参考にしていただくとして基本的にはデータセットを指定数分用意してそれをChart.jsに食わせるということですね。

chart.js
var graph_data = {
    labels: [],
    datasets: [
        {
                       label
                       data[]
                },
        {
                       label
                       data[]
                }
        ]
};

・・・折れ線グラフでチャートObjectを定義
            var ctx = document.getElementById("lineChart").getContext("2d");
            var options = { };
            var lineChart = new Chart(ctx,{
               type : "line",
               data : graph_data,
               scaleShowVerticalLines: false,
               scaleShowHorizontalLines: false,
               scaleOverride : true,
               scaleSteps : 10,
               scaleStepWidth : 0.1,
               scaleStartValue : 25.5, 
            });

データストアの検索部分で重要な考え違いをしていて、createDateがJS中でデータとして取得できると思っていたがそれは違うらしく、あくまで検索時にソートオーダーを書けるときに使えるだけで例えばresults[0].createData のような使い方はできなかった。受け取ったJSONを自分でparseすれば撮れたかもしれないが、ncmbを使うと単純に取得とはいかないらしい。
※追記 2017/1/10
単純にデータストアから受け取ったresultsに対してプロパティを指定すれば拾えた。
受け取ったデータ群を別の変数にコピーする際にユーザ作成のデータのみコピーされる仕様だったらしい。
なのでこの場合、下記のようにすればいい。

var strAry = results[length_count-i-1].createDate.split('T');
graph_data.labels[i] = strAry[0];
graph_data.datasets[0].data[i] =  object.weight;

予め日付のフィールドを作成しておいて、測定時に書き込んでおかないとダメなようなので今回は日付取得はやらないことにした。
上記のおかげで日付出力もできた。

実際のデータストア取得はペット番号で検索(.equalTo)し、結果を降順(.order)で取得する。
これで30件分のデータを取得しグラフを時系列にするためにdatasetの登録時に逆順にする。
実際の取得は下記のようになる。

app.js
P_Weight.equalTo("uid",uid2)
        .order("createDate", true)
        .limit(30)
        .fetchAll()
        .then(function(results){
            console.log("Successfully retrieved " + results.length + " scores.");
            graph_data.datasets[1].label = petName2;
            console.log(results[0]);
            var length_count = results.length;
            for (var i = 0; i < results.length; i++) {
                var object = results[length_count-i-1];
                graph_data.datasets[1].data[i] =  object.weight;
            }

後は起動時の関数登録とグラフ表示の戻るボタンの関数を書いて終わり。

app.js
function backDetail() {
    // loading の表示
    $.mobile.loading('show');
    $("#currentUserData").empty(); // UserDataのクリア
    $.mobile.changePage('#DetailPage');
}
/********** 共通 **********/
// 「ログアウト」ボタン押下後確認アラートで「はい」押下時の処理
function onLogoutBtn() {  
    // [NCMB] ログアウト
    ncmb.User.logout();
    console.log("ログアウトに成功しました");
    // ログイン中の会員情報を空に
    currentLoginUser = null;
    // currentUserDataリストを空に
    $("#currentUserData").empty();
    // 【ID / PW】ログインページへ移動
    $.mobile.changePage('#emailLoginPage'); //IDのログインページからemailページに変更
}

//---------------------------------------------------------------------------

// アプリ起動時
$(function() {
    $.mobile.defaultPageTransition = 'none';
    /* メール / PW */
    $("#emailLoginBtn").click(onEmailLoginBtn);
    $("#YesBtn_mailAddress").click(onEmailRegisterBtn);
    $("#NoBtn_mailAddress").click(onDeleteField);  
    $("#backDetailPage").click(backDetail);  
    /* 共通 */
    $("#YesBtn_logout").click(onLogoutBtn);
    /* グラフ表示 */
    $("#DisplayGraphBtn").click(displayLineChart);
});

ログイン画面、詳細ページに行き、グラフが表示される。
スクリーンショット 2018-01-09 4.06.32.png
スクリーンショット 2018-01-09 4.06.58.png
スクリーンショット 2018-01-09 4.07.13.png

Chart.jsなので2つのデータセットを同時でも個別でも表示できるようになる。

スクリーンショット 2018-01-09 4.07.30.png

また時間が出来たときに機能を追加したり出来るようにしよう。
今回はここまで。