【GAS】AppSheetでグラフを描くために数値データを線形補間する

AppSheet

はじまり

リサちゃん
リサちゃん

あーなんだ! このグラフはぁぁぁ

135ml
135ml

おやおや、グラフに良くないことが起きたようだね

リサちゃん
リサちゃん

気に入らん!

135ml
135ml

気に入るようにしてみよう

AppSheetっていうアプリ作成サービスがあります

「AppSheet」という、ノーコードでアプリを開発できるサービスがあります。

AppSheet: ノーコード アプリ開発 | Google Cloud
Google Cloud の AppSheet はコードのいらないアプリ開発プラットフォームで、既存のデータを使って素早くモバイルアプリやデスクトップ アプリを構築できます。

このAppSheetは、なんてったって、Googleスプレッドシートをデータベースにしてアプリを開発することが出来ます!

グラフも作れちゃいます!

グラフをAppSheetで描きたいのだが・・・

グラフを描いてみる

早速、AppSheetでグラフを描画してみます。

これは毎日測る、体重と血管年齢のグラフです。ちょっと血管年齢が上がっていますね・・・

次に、このようなデータからAppSheetでグラフを描画してみます。

このように描けました。

うーむ・・・

なんか気に入らない・・・

気に入らないポイント

何が気に入らないのかと言うと、

このグラフは日々、スクワットをした回数をメモったデータなんですけど、

スクワットは毎日やるわけでは有りません。ちゃんと太ももを休ませる日も作る必要があります。

そのため、スクワットをしない日は、空欄、もしくは0と入力してました。

しかしそうすると、空欄もしくは0までグラフを描画するので、スクワットの回数の遷移を表すグラフとしては見にくいものになってしまいます。

0を描画することは納得なのですが、”空欄”まで0として描画してしまうAppSheetの仕様に、困りました。

未来日のレコード(執筆時点は2024/01/31)を見ると分かりやすいです。空欄が0として入力されていることがわかります。

描くための対策

そこで、データが0もしくは空欄になっている部分を、線形補間して入力してあげることで、
データの遷移が見やすいグラフを作ることにしました。

そこで、GoogleスプレッドシートをGoogle Apps Scriptで読み込んで、線形補間したデータを表示する処理を作りました。

線形補間する処理

これがデータを加工する処理のスクリプトです。

/**
 * @description Get linear-interpolated values by argued values.
 * @param {string[][]} values
 * @return {string[][]}
*/
function getLinearInterpolateByColumns(values){
  let baseIndices = [];
  let nodes = [];
  let numOfNodes = 0;
  let difference = 0;
  let remain = 0;
  for(let i = 0; i < values.length; i++){
    if(values[i][0] !== 0 && values[i][0] !== ""){
      baseIndices.push(i);
    }
  }
  console.log(`${baseIndices}`);
  console.log(`${baseIndices.length}`);
  console.log(`getLinearInterpolateByColumns: 111111111111111111111111111111111111111111111`);
  if(baseIndices.length < 2){
    console.log(`getLinearInterpolateByColumns: Not linear-interpolated.`);
    console.log(`getLinearInterpolateByColumns: 2222222222222222222222222222222222222222222`);
    return values;
  }
  for(let i = 0; i < baseIndices.length - 1; i++){
    if(i === 0){
      for(let j = 0; j < baseIndices[i]; j++){
        nodes.push(0);
      }
      console.log(nodes);
      console.log(`getLinearInterpolateByColumns: 33333333333333333333333333333333333333333333333`);
    }
    numOfNodes = baseIndices[i + 1] - baseIndices[i];
    difference = values[baseIndices[i + 1]][0] - values[baseIndices[i]][0];
    console.log(numOfNodes);
    console.log(difference);
    console.log(`getLinearInterpolateByColumns: 4444444444444444444444444444444444444444444444444`);
    for(let j = 0; j < numOfNodes; j++){
      console.log(values[baseIndices[i]][0]);
      console.log(difference * j / numOfNodes);
      nodes.push(values[baseIndices[i]][0] + difference * j / numOfNodes);
    }
    console.log(`getLinearInterpolateByColumns: 555555555555555555555555555555555555555555555555555`);
    if(i === baseIndices.length - 2){
      // remain = baseIndices[i + 1] - baseIndices[i];
      remain = values.length - 1 - baseIndices[i + 1];
      nodes.push(values[baseIndices[i + 1]][0]);
      for(let j = 0; j < remain; j++){
        nodes.push(0);
      }
    }
  }
  let roundedNodes = nodes.map(node => {
    return Math.round(node);
  })
  let newValues = roundedNodes.map(node => {
    return [node];
  })
  console.log(newValues);
  console.log(`getLinearInterpolateByColumns: 999999999999999999999999999999999999999999999999999999`);

  return newValues;
}

/**
 * @description Get the statement line-broken to display values in the argued arrays.
 * @param {string[][]} arrays
 * @return {string[][]}
*/
function getStatementLineBrokenToDisplayArrays(arrays, opening){
  const sepFirst = ":<br>";
  const sep = "<br>";
  let outNumberOfStart = arrays[0];
  const startOfStatement = `${opening}${sepFirst}${outNumberOfStart}${sep}`; 
  let statement = arrays.reduce((prev, curr) => {
    return `${prev.toString()}${sep}${curr.toString()}`;
  });
  statement = statement.replace(`${outNumberOfStart}${sep}`, startOfStatement);
  return statement;
}

/**
 * @description Output linear-interpolated values by argued values.
 * @return {boolean}
*/
function outputLinearInterpolate(){
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  let values = sheet.getActiveRange().getValues();
  console.log(values);
  console.log(`${values}`);
  console.log(`outputLinearInterpolate: 111111111111111111111111111111111111111111111`);

  if(values[0].length > 1){
    throw new RangeError("Length of selected column must be 1.")
  }

  let newValues = getLinearInterpolateByColumns(values);
  console.log(`${newValues}`);
  console.log(`outputLinearInterpolate: 222222222222222222222222222222222222222222222`);

  let statementBeforeValues = getStatementLineBrokenToDisplayArrays(values, "Before interpolation:");
  let statementAfterValues = getStatementLineBrokenToDisplayArrays(newValues, "After interpolation:");
  displayHtml("index_html", "Linear-interpolation Terminated...", `<p>${statementBeforeValues}</p><p>${statementAfterValues}</p>`);
  console.log(`outputLinearInterpolate: 999999999999999999999999999999999999999999999`);

  return true;
}

スプレッドシート上で、線形補間したいセルを選択した状態で、outputLinearInterpolate()を実行するとスプレッドシート上にHTMLが表示されるというわけです。

(HTMLを表示する処理は、displayHtml()が担当しています。)

線形補間前のデータと、

線形補間後のデータを表示します。

表示されたデータをシートに貼り付けて、再描画したグラフを確認する

そして、表示したデータを貼り付けます。

表示されたテキストを選択して、

貼り付ける。

そして、AppSheet上のグラフを確認します。

んんっ。。

まあ、今回使ったスクワットのデータの上下差が大きすぎて少し見づらいですが、

測定を実施している期間のスクワットの回数が、0になっている日付はありません!

回数の遷移を純粋に描画したグラフとなったことでしょう!

おしまい

リサちゃん
リサちゃん

まあまあまあ、いいんじゃないでしょうか

135ml
135ml

スクワットへのモチベーションの上下の様子が伝わるグラフになったな。

以上になります!

コメント

タイトルとURLをコピーしました