22
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

GoogleAppsScriptで業務システム作るならvis.jsが良いんじゃないかな?

Last updated at Posted at 2018-05-03

ガチPGじゃない情シスの悩み

上司から色々なレポートを頼まれてSpreadsheetでなんとかお茶お濁せるうちはまだ良いのよ。
でもGoogleDrive上の複数シートを横断的に集計したり、しかもGoogleCalendarと連携してたり、その上複雑奇怪な表現のレポートを求められても魔改造Spreadsheetでは限界があるわけで、だからといって個別のレポートに対してWebサービス作ってたらキリがないよぅ…ふえぇ…。

というそんなアナタ!
GoogleAppsScriptでちょっとした業務システム作るなら vis.js のTimelineが良いんじゃないかな?

作ったヤツ

キャプチャ.PNG
DEMO: https://script.google.com/macros/s/AKfycbxT6jrO68LS79H1XDhZN3Gx_as5eUSYIc87-Tpi5Odkb7nnvZk/exec

ソースの説明

GAS側のソース

code.gs
/**
* WebAPIとして呼ばれる場合のエントリポイント
*/
function doGet() {
    var template = HtmlService.createTemplateFromFile('visjs_timeline.html');
    return template.evaluate().setSandboxMode(HtmlService.SandboxMode.IFRAME);
}

/**
*
* @return {Array<string>} data jsonデータを返す
*/
function getJSONFromSheet(sheet) {
    console.time('getJSONFromSheet("' + sheet + '")');
    var result = convSheet('13BOOY-ygYM8oPTdX4RiEtcapZ5ufJMIxV-q5AYrDinw', sheet);
    console.timeEnd('getJSONFromSheet("' + sheet + '")');

    return JSON.stringify(result);
}

/**
* sheetidとシート名からをデータ読み取り、オブジェクトデータとして返す
*/
function convSheet(spreadsheetId, sheetName) {
    var ss = SpreadsheetApp.openById(spreadsheetId);
    var sheet = ss.getSheetByName(sheetName);

    return getSheetDataJSON(sheet);
}

/**
* 該当のsheetを読み取り、オブジェクトデータとして返す
* データの1行目の各列の文字列を要素名とする事を期待しているため、空文・重複は禁止
* シート全体を読み込んで処理するので、余ったカラムやセルは念のため詰めておいてください
* セル内にコンマ( , )が入っていた場合、split(',')してarrayとして返します
*/
function getSheetDataJSON(sheet) {

    var values = sheet.getDataRange().getValues();
    var columns = values[0].length;
    var rows = values.length;

    var jsonArray = [];

    for (var i = 1; i < rows; i++) { // 先頭行はIndexなので2行目から
        var json = {};
        for (var j = 0; j < columns; j++) {
            var str = new String(values[i][j]);
            if (str.length == 0) continue;

            if (str.indexOf(',') == -1) {
                json[values[0][j]] = str;
            } else {
                json[values[0][j]] = str.split(",");
            }
        }
        jsonArray.push(json);
    }

    return jsonArray;
}

GASのコードの主な役割は、vis.js用のグループ設定や、アイテムの配列をシートから読み込んでJSONとして返すだけです。
(面倒なのでシートID直打ちしてますが、実際使う際はこのIDは自分のSpreadsheetでやってくださいおながいします)
例外とかも綺麗サッパリやってないので、ガチ勢の方はよしなにお願いします。

あ、シートも共有しときますんで、設定内容はそちらでご確認ください。
https://docs.google.com/spreadsheets/d/13BOOY-ygYM8oPTdX4RiEtcapZ5ufJMIxV-q5AYrDinw/edit?usp=sharing

シートの1行目のヘッダ文字列は、そのままvis.jsが必要とする項目名を入力しています。
データの詳細はvis.jsのDataFormatの部分を見てもらうとして、動作するために最小限必要な情報は

グループの設定
{
   id:      String or Number
   content: String
}
アイテムの設定
{
   content: String
   start:   Date or number
            string or Moment
}

くらいのもので、残りの設定項目はオプションです。
今回はスケジュールとかでよく使いそうな項目をそれっぽく設定してます。

HTML側のソース

visjs_timeline.html
<!DOCTYPE html>
<html>

<head>
    <title>GASでVis.jsのTimelineを使う</title>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.1/moment-with-locales.min.js"></script>
    <!-- 必ずvis.jsを読み込む前に moment-with-locales.js を読み込むこと -->

    <script src="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis.min.js"></script>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis-timeline-graph2d.min.css" rel="stylesheet" type="text/css"
    />
</head>

<body onresize="/*timeline.checkResize();*/">

    <div id="mytimeline"></div>

    <script>
        // specify options
        var options = {
            stack: true,
            stackSubgroups: true,

            start: new Date('2018/4/1'),
            zoomMax: 24 * 60 * 60 * 1000 * 30 * 7,
            zoomMin: 24 * 60 * 60 * 1000 * 30,
            verticalScroll: true,

            // デフォルトではグループにソート機能は無いので
            // 独自プロパティを用意してソート用のキーにする。
            groupOrder: function (a, b) {
                return a._order - b._order;
            },

            locale: 'ja',  // vis.jsのデフォルトにはjaは無いがmoment-with-locales.jsを読むと使える
            format: {
                minorLabels: {
                    millisecond: 'SSS',
                    second: 's',
                    minute: 'HH:mm',
                    hour: 'HH:mm',
                    weekday: 'M/D(dd)',
                    day: 'M/D(dd)',
                    week: 'w',
                    month: 'MMM',
                    year: 'YYYY'
                },
                majorLabels: {
                    millisecond: 'HH:mm:ss',
                    second: 'M/D HH:mm',
                    minute: 'M/D(dd)',
                    hour: 'M/D(dd)',
                    weekday: 'YYYY年',
                    day: 'YYYY年M月',
                    week: 'YYYY年M月',
                    month: 'YYYY年',
                    year: ''
                }
            }
        };

        // とりあえず中身無しでTimelineを作成
        var container = document.getElementById('mytimeline');
        var timeline = new vis.Timeline(container, null, options);

        // スプレッドシートから、グループとitemの情報を読み出し
        google.script.run.withSuccessHandler(setGroups).getJSONFromSheet('groups');
        google.script.run.withSuccessHandler(setItems).getJSONFromSheet('items');

        function setGroups(SSValues) {
            var g_groups = JSON.parse(SSValues);
            var groups = new vis.DataSet(g_groups);
            timeline.setGroups(groups);
        }

        function setItems(SSValues) {
            var g_items = JSON.parse(SSValues)
            var items = new vis.DataSet(g_items);
            timeline.setItems(items);
        }
    </script>

</body>

</html>

ここも例外やエラー処理をすっ飛ばしているのでよしなに。
以下細な補足事項などを。

GAS使ってサービス公開する時の注意

多分こんな記事タイトルに惹かれて読んでるGAS勢の方々知ってることとは思いますが念のため補足します。

外部サイトのJavascriptライブラリやCSS、WebFontなんかをGASで使う場合、XDR(Cross-Domain Requests)に対処できていないサイトからは読み込めません。
マイナーなライブラリなんかは作者さんのWebsiteとかでホストしてたりする事もあるけど、公開サイトがhttps化されてなかったりするとエラーになります。
まぁ最近はcloudflareやgoogleのCDNに有名どこのリソースはホストされていると思うので、そっちを使うのがベター。
(もしこの記事の公開以後にvis.jsやmoment.jsのCDN読めなくなってたら、適当にソースを変更してください)

外部読み込みが使えない場合、GAS上にソースを丸ごと上げて読み込ませる事もできるので、最悪それでもOK。
現行環境ならclaspが使えるようになったので、Webpackでチンしてポンという手もあります。

code.gs側の関数を使ってシート情報を得る部分

GAS特有の構文
    // スプレッドシートから、グループとitemの情報を読み出し
    google.script.run.withSuccessHandler(setGroups).getJSONFromSheet('groups');
    google.script.run.withSuccessHandler(setItems).getJSONFromSheet('items');

HTML側のGAS要素はここだけです。
ここの部分を他の一般的なAJAXに置き換えれば、別のプラットホームでも動きます。

ローカライズに関して

vis.jsのTimelineの日本語化に関しては日本語情報が無かったので補足しときます。

日付表示の部分だけど、そもそも
weekday.PNG
こんな表示で分かるわけないじゃん!アメリカ語怖い!
このまま社内展開なんかしたら「日本語で日付表示して!」と突っ込まれることは250%明らか!
日本人なら 2018年5月1日(火)とキチっと表示させるべきである!

ってなわけで細かくはvis.jsのローカライズを読んでもらうとして、要は moment.js の moment-with-locales.min.js を読み込むと多国語対応できるらしいので、vis.jsが読み込まれる前に読み込み。
あとは

locale設定
            locale: 'ja',  // vis.jsのデフォルトにはjaは無いがmoment-with-locales.jsを読むと使える
            format: {
                minorLabels: {
                    millisecond: 'SSS',
                    second: 's',
                    minute: 'HH:mm',
                    hour: 'HH:mm',
                    weekday: 'M/D(dd)',
                    day: 'M/D(dd)',
                    week: 'w',
                    month: 'MMM',
                    year: 'YYYY'
                },
                majorLabels: {
                    millisecond: 'HH:mm:ss',
                    second: 'M/D HH:mm',
                    minute: 'M/D(dd)',
                    hour: 'M/D(dd)',
                    weekday: 'YYYY年',
                    day: 'YYYY年M月',
                    week: 'YYYY年M月',
                    month: 'YYYY年',
                    year: ''
                }
            }

みたいな感じで、お気に召すままのフォーマットを組めば、めでたく「月月火水木金金」の曜日表示が出るようになります。

まとめ

他にもちょっとした注意点もあるけど、かなりシンプルな例にしたのでソースのコメントをご参照ください。

社内ツール作成する際に、横線で管理するタイプのスケジューラやガントチャート的なものが必要になる事が多く、最初はガントチャートのライブラリを漁りまくってたのですが、扱いやすい手ごろな規模のモノが無く途方に暮れていました。
そんな中vis.jsのTimelineのサンプルを見つけた時「これだ!!」……と。

実のところ、以前からvis.jsの存在は認識していたのですが、Web上のvis.js解説はネットワーク図の記事か多く、Timelineの汎用性に気付いていませんでした。
個人的には他のガチ・ガントチャートライブラリは融通が利きにくい印象で、しかも便利そうな機能が有償だったしてぐぬぬってなります。
その点、vis.jsは適度にユルい設計でライセンスも優しいため、自分にはこのくらいの規模感がシックリ来ました。

今回はかなりシンプルな作例を記事にしましたが、カスタマイズに関してまだ書き残した部分も多いので、気が向いたらもう少し書きます。

22
28
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?