集計した結果をグラフに表示する

どうもこんばんは、みかんです。

**！！！！とつぜんですがたいせつなおしらせ！！！！**

2020年1月27日
App Maker の提供終了に向けた対応のご案内
https://support.google.com/a/answer/9682494?p=am_announcement&visit_id=637157414793613981-426262357&rd=1


??!???!?!?!???!?

残念ながら2021年1月19日には終了してしまうそうです(涙)
移行先はAppSheetかApp EngineかGoogleフォームだそうです・・・

消すのは忍びないので記事は残して置きますが、何かの間違いじゃ無い限り役に立つことはないでしょう・・・


**！！！！おしらせはおわりです！！！！！**

今回は「集計した結果をグラフに表示する」方法を書いてみます。
下記ステップでやっていきます。

* 1 下準備
* 2 動きを確認する
* 3 計算モデルの確認
* 4 集計スクリプトを確認

# 1 下準備

昨日に引き続き、公式のサンプルを使って解説していきます。

[サンプル: 計算モデル](https://developers.google.com/appmaker/samples/calculated-model?hl=ja)

|![ap05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26085/0f68fd7f-7c20-9084-ee38-e8cf94954d8a.png)|
|:-:|

説明用に雑に日本語化。

|![ap06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26085/afa3ca74-cec4-73fc-92bf-e3d71911e3bb.png)|
|:-:|

# 2 動きを確認する

さっそくpreviewで動きを確認してみましょう。

|![gif001-compressor.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26085/1409bb19-a1d0-63aa-2fc9-ec6274bc4a59.gif)|
|:-:|

集計用のデータを手打ちする手間を省くために、データを自動生成する機能がついています。親切ですね。
ボタンを押すと100件生成されます。

生成した後、「統計を見る」を押します。すると画面が切り替わり、グラフが表示されます。

|![gif002-compressor.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26085/d4f48fab-5bce-bd39-9ea1-de3d5a5190d4.gif)|
|:-:|

# 3 計算モデルの確認

モデルを確認してみましょう。
2つあります。

1つ目は「Data」で、モデルの種類はGoogle Cloud SQL Tableです。
フィールドを見ると、Id、Name、Type、Ratingの4種類ですね。
1件のデータには、名前と種類とレーティングが記録される想定ということで、App StoreとかGoogle Playのアプリのデータだと思うとわかりやすいかもしれません。

|![ap03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26085/49b7f757-1fff-f690-c79d-9ad4036f3708.png)|
|:-:|

2つ目は「Aggregation」で、モデルの種類は計算モデル(Calculated)です。
このモデルの特徴は、実際にデータを取得する部分をサーバースクリプトで記述する必要があるところです。
データソースの「AggregationByType」を開くと、Query Server Scriptという枠があります。

|![ap02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26085/17bf2efa-b62d-ce7a-381c-42d6e2f53713.png)|
|:-:|

データを読み出すタイミングになった時、ここの処理が呼び出されます。
結果をスクリプトで取得し、最後にreturnする必要があります。ここでは、`getStatisticsByType_`という関数に処理を任せています。余談ですが、関数の末尾に`_`が付くとクライアントスクリプトからは呼び出せない関数になります。UIと直接繋がっていないという印にもなるので、コードを読むのが少しだけラクになります。

また、このデータソースというものは複数作成でき、データソースごとにスクリプトを変更できます。
UIなどからバインディングで指定するデータソースはこれのことで、モデルではありません。
(新しくモデルを作成すると、デフォルトで同名のデータソースが作成されるので、最初は混同しがちです。)

モデルは、あくまでどのようなフィールドを扱うか、イベントの前後に処理を挟むか、どのような権限でアクセスできるかといった概念を定義する存在です。
データソースはその名の通りモデルのデータの元、ということです。

|![ap01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26085/826569b7-3048-cc80-0114-b028731966c7.png)|
|:-:|

今回は「AggregationByType」と「AggregationByRating」という2つのデータソースが定義されているので、タイプで集計する場合とレーティングで集計する場合があるんだということが名前からもわかりますね。

# 4 集計スクリプトを確認

では、実際にどのように集計データを用意しているか、集計スクリプトを見てみましょう。
集計スクリプトは「CalculatedModels」サーバースクリプトに記述されています。

まずはAggregationByType用です。

```javascript
/**
 * タイプ別に集計する。AggregationByTypeデータソースで使用される。
 * @return {Array<Aggregation>} タイプ別集計結果の配列。
 */
function getStatisticsByType_() {
  var allRecords = app.models.Data.newQuery().run();
  var stats = {};
  for (var i = 0; i < allRecords.length; i++) {
    var recordType = allRecords[i].Type;
    if (!stats[recordType]) {
      stats[recordType] = 0;
    }
    stats[recordType]++;
  }

  var records = [];
  var properties = Object.getOwnPropertyNames(stats);
  for (var j = 0; j < properties.length; j++) {
    var record = app.models.Aggregation.newRecord();
    record.Name = properties[j];
    record.Count = stats[properties[j]];
    records.push(record);
  }
  records.sort(sortDataByName_);
  return records;
}
```

一番最初の`app.models.Data.newQuery().run();`で、Dataモデルをすべて読み込んでいます。
集計対象のデータなので当然ですね。
(オイオイ、集計にデータ全部読み出すなんて正気か？ と思ったそこの貴方！ [こちらをご覧ください (計算SQLモデル)](https://developers.google.com/appmaker/models/cloudsql?hl=ja#calculated_sql_models))

続いて、statsというオブジェクトに、種類ごとの数をどんどん加算しています。
こんな感じのデータになっています。

```json
{
  "Type1":40,
  "Type2":30
}
```

最後に、`Object.getOwnPropertyNames(stats)`で種類ごとに処理をする準備をした後、種類ごとに`app.models.Aggregation.newRecord()`でデータソース用のレコードを作成します。これを配列にしてまとめたものを最終的に返却することで、App Maker側ではそれをデータソースでロードしたデータとしてみなします。


次に、AggregationByRating用です。

```javascript
/**
 * レーティング別に集計する。AggregationByRatingデータソースで使用される。
 * @return {Array<Aggregation>} レーティング別集計結果の配列。
 */
function getStatisticsByRating_() {
  var allRecords = app.models.Data.newQuery().run();
  var stats = {};
  for (var i = 0; i < allRecords.length; i++) {
    var ratingBucket = Math.floor(allRecords[i].Rating);
    if (!stats[ratingBucket]) {
      stats[ratingBucket] = 0;
    }
    stats[ratingBucket]++;
  }

  var records = [];
  var properties = Object.getOwnPropertyNames(stats);
  for (var j = 0; j < properties.length; j++) {
    var record = app.models.Aggregation.newRecord();
    record.Name = properties[j] + ' to ' + (parseInt(properties[j], 10) + 1);
    record.Count = stats[properties[j]];
    records.push(record);
  }

  records.sort(sortDataByName_);
  return records;
}
```

ほとんどやっていることは同じですね。
レーティングの値はデータとしては「0.1」とか「2.5」という形ですが、その値ごとに集計しても無駄に数が多いので、
このサンプルでは、「0 to 1」 「1 to 2」のような単位でまとめられています。「~以上~未満」です。
0.1とか0.9は「0 to 1」に、1.0とか1.8は「1 to 2」に属します。

コードでは`Math.floor(allRecords[i].Rating)`で切り捨てを行い、1の位でまとまるようにしています。

`properties[j] + ' to ' + (parseInt(properties[j], 10) + 1);`で、文字列表現として「0 to 1」となるようにしていますね。

また、`records.sort(sortDataByName_)`に指定している関数は、下記のように定義されています。

```javascript
/* オブジェクトに含まれるNameフィールドの昇順でソートすることを可能にする関数。 */
function sortDataByName_(a, b) {
  if (a.Name < b.Name) {
    return -1;
  }
  if (a.Name > b.Name) {
    return 1;
  }
  return 0;
}
```

昇順ってどっちだっけ？ということを~~修行が足りないと~~毎回忘れてしまうやつですね。今回もググりました。

説明が漏れましたが、グラフの設定方法については[前日記事(グラフサンプルを触ってみる)](https://qiita.com/ExistMikan/items/294f66f1b99bf8e87e19)と同じなので割愛します！
どのグラフも、column (Data)に`@datasource.items..Count`を設定する感じです。

この計算モデル、今回はCloud SQL Tableモデルのデータを読み出して処理しましたが、Google Spreadsheetsや外部のAPIなどからデータを取得するコードを書いて、それを利用することも可能です。そういった外部連携的な使い方のほうが多くなると思います。

以上です。良いApp Makerライフを!