元々、はれんちさんが作ったbotに下の画像のようにグラフ出力機能を追加しました。
しずえさんbotの仕組み
![]() |
GASを利用したLINE bot |
- LINEメンバーにカブ価を午前・午後で入力してもらう。
- 「あきおの今週のカブ価」や「今日のカブ価」をLINEに投げる。
- LINE botがメッセージをGASに送る。
- GASで処理できる定型文であればスクリプトを起動する。
- スクリプトの結果がLINEに返ってくる。
どうやってGASでLINE botを実装するのかについては詳しく知らないので、はれんちさんのブログを待ちたいと思います(期待)。
実装したグラフ出力機能
集計しているデータ形式は次のような形です。
コードはこちらです。実行する場合はGASにMomentライブラリを追加してください。
わからないところがあれば、コメントで質問していただけたらと思います。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// シートに登録されている内容から今週のカブ価チャートを作成する | |
// crateChart('あきお') -> 'あきお'のカブ価チャートのURLを返す | |
function createChart(name='あきお') { | |
// データが集められているスプレッドシートを取得する | |
const id = "<your_sheet_id>"; | |
const spreadSheet = SpreadsheetApp.openById(id); | |
const sheetName = "フォームの回答 1"; | |
const sheet = spreadSheet.getSheetByName(sheetName); | |
// データの行数を取得する | |
const existValueLastRow = getExistValueLastRow(sheet); | |
// 全登録データを取得する | |
const range = sheet.getRange(`A2:D${existValueLastRow}`).getValues(); | |
// 選択された人のデータを抽出する | |
const filtered = range.filter(r => r.includes(name)); | |
// 抽出されたデータが0件ならURLを返さず、この関数の処理を終了する。 | |
if (filtered.length === 0) return; | |
// 抽出されたデータから、よりデータを扱いやすいRecordドメインの配列に詰め替える | |
const records = recordsFactory(filtered); | |
// 今日の曜日を取得する。今日が月曜日ならnowMomentは1、今日が土曜日ならnowMomentには6が入る | |
const nowMoment = Moment.moment().day(); | |
// 直近の日曜日の日付を作る | |
const lastSunday = Moment.moment().add('days', -nowMoment).set('hour', 0).set('minute', 0); | |
// 今週のカブ価を抽出する | |
const thisWeekRecord = records.filter(record => record.isAfter(lastSunday)); | |
// ここまででデータの抽出は終わり。ここからはチャート出力の処理 | |
// チャートに表示しやすいように今週のデータをつくる。詳しくはこの関数を見てください。 | |
let weeklyRecord = weeklyRecordFormat(thisWeekRecord, lastSunday, name); | |
// チャートデータを作り、何が入っているのかを教える。 | |
let chartData = Charts.newDataTable() | |
.addColumn(Charts.ColumnType.STRING, '日付') | |
.addColumn(Charts.ColumnType.NUMBER, 'ベル'); | |
// チャートデータをプロットする | |
weeklyRecord.map(record => chartData.addRow(record.getChartData())); | |
// チャートの見た目を整え、画像データにする | |
let chart = Charts.newLineChart() | |
.setDataTable(chartData) | |
.setTitle(`${name}のカブ価`) | |
.setColors(["#EF5F32"]) | |
.setPointStyle(Charts.PointStyle.MEDIUM) | |
.setOption('legend.position', 'in') | |
.setOption('chartArea', {left: 50, right: 50}) | |
.setDimensions(650, 300) | |
.build().getBlob(); | |
// 画像データを所定の位置に保存する | |
var folderId = '<your_folder_id>'; // Googleドライブの一時フォルダのID | |
var today = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'YYYY-MM-dd'); | |
var folder = DriveApp.getFolderById(folderId); | |
var file = folder.createFile(chart) | |
file.setName(today); | |
// 保存した画像を公開設定する | |
file.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.EDIT) | |
// 画像URLを返す | |
return file.getDownloadUrl(); | |
} | |
// 登録データの最後の行を取得する関数 | |
function getExistValueLastRow(sheet) { | |
const columnACellValues = sheet.getRange('A:A').getValues(); | |
return columnACellValues.filter(String).length; | |
} | |
// 登録データからRecordオブジェクトの配列を返す関数 | |
function recordsFactory(rawRecords) { | |
let records = []; | |
rawRecords.map(rawRecord => { | |
records = records.concat([recordFactory(rawRecord)]); | |
}); | |
return records; | |
} | |
// 登録データをRecordオブジェクトに詰め直す関数 | |
function recordFactory(rawRecord) { | |
//[Wed Apr 01 2020 01:40:25 GMT+0900 (日本標準時),Sun Mar 22 2020 11:00:00 GMT+0900 (日本標準時),あきお,105] | |
let price = rawRecord[3]; | |
if (rawRecord[3] === null || typeof rawRecord[3] === 'undefined' || rawRecord[3] === '') { | |
price = null; | |
} | |
// 登録日と実日があるので、実日があるか見て場合わけする | |
if (Moment.moment(rawRecord[1]).isValid()) { | |
return new Record(Moment.moment(rawRecord[1]), rawRecord[2], price); | |
} | |
return new Record(Moment.moment(rawRecord[0]), rawRecord[2], price); | |
} | |
// Recordオブジェクト | |
class Record { | |
constructor(date, name, price) { | |
this.date = date; | |
this.name = name; | |
this.price = price; | |
} | |
getName() { return this.name; } | |
getPrice() { return this.price; } | |
getDate() { return this.date.isValid() ? this.date.format('M/DD A') : null; } | |
getRawDate() { return this.date; } | |
getChartData() { return [this.getDate(), this.price]; } | |
isAM() { return this.date.isValid() ? this.getDate().includes('AM') : false; } | |
isSunday() { | |
return this.date.isValid() ? this.date.format('dddd曜日').includes('日曜日') : false; | |
} | |
isAfter(date) { | |
return this.date.isValid() ? this.date.isAfter(date) : false; | |
} | |
} | |
// チャートを作るために1週間分のデータ形式を作っている関数 | |
// もし、登録データがない日があった場合、チャートの日付がずれるのでこの関数で補正してる | |
function weeklyRecordFormat(thisWeekRecord, lastSunday, name) { | |
let dateAM = Moment.moment(lastSunday).set('hour', 0); | |
let datePM = Moment.moment(lastSunday).set('hour', 12); | |
let records = []; | |
// 1週間の記録は最大14回分ある | |
for (let step = 0; step < 14; step++) { | |
let isEvenStep = step % 2 === 0; | |
let featureDate = isEvenStep ? dateAM : datePM; | |
records = records.concat(fillRecord(featureDate, thisWeekRecord, name)); | |
isEvenStep ? dateAM.add('days', 1) : datePM.add('days', 1); | |
} | |
return records; | |
} | |
// 登録データがあるか見て、あれば使い、なければカブ価をnull(価なし、0ベルとは異なる)にしている | |
function fillRecord(date, thisWeekRecord, name) { | |
let fdate = date.format('M/DD A'); | |
let matchDate = thisWeekRecord.filter(record => record.getDate() === fdate); | |
// 日曜日は例外としてAMもPMも関係なく出力するようにする | |
let isSunday = date.day() === 0; | |
if (isSunday) { matchDate = thisWeekRecord.filter(record => record.getDate().includes(date.format('M/DD'))); } | |
if (matchDate.length === 0) return [new Record(Moment.moment(date), name, null)]; | |
let matching = matchDate.slice(-1)[0]; // 打ち間違いを考慮して、最後のレコードを採用する | |
return [new Record(Moment.moment(matching.getRawDate()), matching.getName(), matching.getPrice())]; | |
} | |
// テスト関数(本来の処理では使っていない) | |
// ここではweeklyRecordFormat()の実装が不安だったので、挙動が本当に意図通りか見るために書いている | |
function testWeeklyRecordFormat() { | |
let lastSunday = Moment.moment().add('days', -(Moment.moment().day())).set('hour', 0).set('minute', 0); | |
let test = | |
weeklyRecordFormat( | |
[new Record(Moment.moment(), 'あきお', 105), | |
new Record(Moment.moment().add('days', -1), 'あきお', 100), | |
new Record(Moment.moment().add('days', -2), 'あきお', 95), | |
], | |
lastSunday, | |
'あきお' | |
); | |
test.map(r => console.log(r.getChartData())); | |
} |
わからないところがあれば、コメントで質問していただけたらと思います。
ただちょっと懸念があります。
- 出力速度が遅い(大体4秒くらい)
- 所定のフォルダにグラフが溜まっていく
SheetのAPIを叩く回数が減れば出力速度も小さくなると思います。
上のコードを使った際は改善してみてください。