Google Apps Scriptでag-gridを使ってみる【GAS】

これまで、Google Apps ScriptやElectronでは大量のデータを高速に描画するライブラリとしては、Cheetah Gridを自分はよく使っていました。確かに莫大なデータを高速描画し、スクロールも滑らかなのですが、Vue.js対応がいまいち痒い所に手が届かない。結構ドキュメントを漁っても、通常のJS版と違って、Vue.js版は実装されてるのかされてないのかわからず、苦しむ場面も結構ありました。

そこで、今回無償でも利用可能でMITライセンスで提供されてるag-gridをGoogle Apps Scriptで使ってみたいと思います(Vue.js対応版はimport文で導入しないと使えないので、現時点でGoogle Apps Scriptでは利用できません)。

今回利用するライブラリとスプレッドシート

ag-gridは公式ドキュメントを見てもわかるように、通常はwebpackであったりvueといったファイルに固めて利用する、またライブラリのロードもimport文を使ってロードする仕組みなのですが、現在Google Apps Scriptではこのようなやり方ではロードが出来ません。

そこでCheetah Gridでもそうでしたが、CDNより該当のJSを通常のJSをロードを行う手法にて呼び出しています。

※vuetifyのdatatable等で大量のデータを表示するとあまりにも重すぎるので、このライブラリは非常に貴重な高速に大量のデータを表示できるライブラリです。MITライセンスであるため、商用利用も有償で可能になっています。

※サンプルデータは、Medisの標準マスターおよび社保診療報酬支払基金の医薬品マスタ、厚労省のジェネリック一覧を組み合わせて生成しています。

※ag-gridのVue.js3対応版での実装は以下のエントリーで紹介しています。

Google Apps ScriptでVuetify3を検証する【GAS】

導入の仕方とコード

CDNからライブラリをロード

CDNで公開されてるピュアJavaScript用のライブラリをHead内でロードしてあげます。今回のコードでは以下のようなJSとCSSをロードさせてあげています。また、リサイズ時に於けるag-gridのサイズの調整用等のために、Google Public CDNからjQueryもロードさせています。

<!-- jQueryをロードする -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

<!-- ag-gridをロードする -->
<script src="https://cdn.jsdelivr.net/npm/ag-grid-community@28.2.1/dist/ag-grid-community.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@ag-grid-community/styles@28.1.0/ag-grid.min.css">
<link rel="stylesheet" href="https://unpkg.com/@ag-grid-community/styles@28.1.0/ag-theme-balham.css">

ソースコード

GAS側コード

GAS側はシンプルです。HTML表示とスプレッドシートデータを取得し、JSONで返すコードです。ag-gridのrowDataはJSONの形式でデータを入れる必要があるため、これまでのように取得して配列で返すのではなく、keyも含めて入れる必要があります(keyはスプレッドシート1行目のタイトル行を利用しています)。

JSON形式で取得に関しては以下のエントリーを参考にしています。

GoogleスプレッドシートのデータをJSONで取得する【GAS】

function doGet(e){
  //paramを元にHTMLをレンダリング
  var html = HtmlService.createHtmlOutputFromFile("index")
          .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
    
  return html;
}

//スプレッドシートのデータをJSONで取得する
function backjson() {
  //スプレッドシートデータを取得する
  var id = "ここにスプレッドシートのIDを入れる"
  var sheet = SpreadsheetApp.openById(id).getSheetByName("data");
  var ss = sheet.getDataRange().getValues();
  おうに
  //タイトル行を取得する
  var title = ss.splice(0, 1)[0];
  
  //JSONデータを生成する
  return JSON.stringify(ss.map(function(row) {
    var json = {}
    row.map(function(item, index) {
      json[title[index]] = item;
    });
    return json;
  }));
}

ag-gridは非常に大量のデータを高速で表示でき、スクロールでもモッサリモッサリしないので、10万行(5列)のデータを一括で読み取って返すように今回作ってみましたが、およそ20秒くらい掛かります。初期のデータ量は調整したほうが良いでしょう(但しきちんと10万行のデータを取得する事は出来ました)。

HTML側コード

今回はVue.jsが使えない為、データを格納したら再描画するコード等も実装しなければなりません。

Body以下に記述したコード
<body>
  <h1>GASでag-gridを表示する</h1>
  
  <div id="progress" style="width: 100%; height: 500px; display:block">
    <center>
      <img src="https://officeforest.org/wp/library/loading.gif" border="0" alt="progress circle">
      <p><b>Now Loading ...</b></p>
    </center>
  </div>

  <div class="main" id="main" style="display:none">
    <div id="myGrid" style="height: 600px;width:100%;" class="ag-theme-balham"></div>
  </div>

  <script type="text/javascript">
    //スプレッドシートデータ
    var ssdata = [];

    //グリッドのヘッダを定義
    var columnDefs = [
      {headerName: "ID", field: "id"},
      {headerName: "JAN", field: "jan"},
      {headerName: "GS1", field: "gs1"},
      {headerName: "医薬品名", field: "drugname"},
      {headerName: "メーカー", field: "maker"}
    ];

    //表示するグリッドデータ
    var rowData = []

    // ag-gridのオプション
    var gridOptions = {
      columnDefs: columnDefs,
      rowData: rowData
    };

    //表示する場所を指定
    var eGridDiv = document.querySelector('#myGrid');

    //ag-gridを描画する
    new agGrid.Grid(eGridDiv, gridOptions);

    //スプレッドシートのデータを読み取って入れる
    google.script.run.withSuccessHandler(onSuccess).backjson();

  </script>    
</body>
  • idがmyGridの場所にag-gridを表示します。
  • columnDefsがag-gridのタイトル行の定義をする部分になります。ここのfieldとrowDataのfieldの値は一致してる必要があります。
  • gridOptionsがag-gridの様々なオプションを指定する場所でもあり、後で操作をする本体になります。
  • GAS側のbackjsonを叩いてonSuccessでデータを取得しています。
Head内に記述したコード
<script>
  //スプレッドシートデータを取得して格納する
  function onSuccess(data){
    //JSONデータを取得する
    var ssdata = JSON.parse(data);

    //データをリフレッシュ
    refreshdata(ssdata);

    //縦横サイズを自動調整
    setGridHeight();
  }
  
  //データを更新する
  function refreshdata(ssdata) {
    //データをセットする
    gridOptions.api.setRowData(ssdata);

    //loaderを非表示にする
    $("#main").css("display", "block");
    $("#progress").css("display", "none");
  }

  //リサイズ時にウィンドウにフィットさせる
  window.onresize = function(){
    setGridHeight();
  }
    
  //gridのサイズを自動でウィンドウにフィットする
  $(document).ready(function () {
    setGridHeight();
  });

  //gridの縦横の高さを調整する
  function setGridHeight() {
    var layoutHeight = $(window).height() - 100;
    $('#myGrid').css('height', layoutHeight + 'px');
  }
</script>
  • onSuccessでSpreadsheetのデータを受け取ったら、refreshdata関数でag-gridにセットする
  • refreshdata関数では、後からgridOptionsに対してsetRowDataにてデータをセットする事が出来ます。
  • リサイズ時にag-gridの縦のサイズを自動調整するようにコードを追加しています。

オプション指定について

gridOptionsで様々なag-gridの機能を指定する事が可能で、これがとっても豊富です。あまりにも多い為、使いこなすのがちょっと大変なレベルですが、ドキュメントはしっかりしてるので、1つずつきちんと理解出来れば、かなり良い感じでアプリ構築が可能になるのではないかと思います。この他にも、列や行向けのオプションも別途存在し、思うような表示を実現可能になっています。

基本的なオプションの追加についてまとめてみました。

ヘッダ列用のオプション

通常のオプション

グリッドのヘッダ用の項目で使うオプションです。

//グリッドのヘッダを定義
var columnDefs = [
  {headerName: "ID", field: "id", sortable: true, headerCheckboxSelection: true, headerCheckboxSelectionFilteredOnly: true, checkboxSelection: true},
  {headerName: "JAN", field: "jan", filter: true},
  {headerName: "GS1", field: "gs1", filter: true, sortable: true},
  {headerName: "医薬品名", field: "drugname", cellStyle: {color: 'red', 'background-color': '#b8ffc7', resizable: true}},
];
  • sortableがtrueでソート可能な列になります。
  • filterがtrueでフィルタ可能な列になります。
  • 両方つける事も可能です。
  • resizableがtrueで行の幅をユーザが変更する事が出来るようになります。また、width指定で列の既定の幅を指定可能。
  • cellStyleを指定する事で、文字色や背景色の指定をする事が可能です。
  • headerCheckboxSelection等を指定すると、チェックボックスで行選択を列に加える事が可能です。通常は先頭の列に対して設定します。このオプションを使う場合、通常はgridOptionsにも「rowSelection: 'multiple',」を追加しておき、複数選択を可にしておく必要があります。

図:ヘッダの設定や列の設定をする為のオプションです。

ヘッダグループの指定

マルチヘッダを実現したいケースがあります。この場合ヘッダグループを指定することで、またその下に通常のヘッダ項目を格納することで、2段のヘッダを作成する事が可能になります。

var columnDefs = [
  {headerName: "分類", children: [
    {headerName: "先行品", field: "senkou", filter: true},
    {headerName: "ジェネリック", field: "generic", filter: true},
  ]},
];

図:複数ヘッダを設けることも可能

列固定をする

何列目で列を固定化し、横スクロールした時でも常に表示してる状態にする場合に指定します。この機能はpinnedと呼ばれて、固定化する列に対して以下のようにオプション指定を追加するだけで可能です。leftは左固定、rightは右固定となります。

//2列目までを固定
var columnDefs = [
  {headerName: "ID", field: "id", sortable: true, pinned: 'left' },
  {headerName: "JAN", field: "jan", filter: true, pinned: 'left'},
  {headerName: "GS1", field: "gs1", filter: true, sortable: true},
  {headerName: "医薬品名", field: "drugname", cellStyle: {color: 'red', 'background-color': '#b8ffc7'}},
  {headerName: "メーカー", field: "maker"},
];

図:列固定も非常によく利用する機能です

カスタムボタンを列に表示

Cheetah Gridでも先頭の列に編集用ボタンを表示して、クリックしたら中身を取得してダイアログ表示みたいな機能が備わっていたのですが、ag-gridでも同様の機能を実装する事が可能です。また、ボタンをクリックして対象のレコードの中身を取得する事が可能なので、利用する機会は意外と多いのではないかなと思う機能です。

cellRendererで描画し、その際にparamsを引数に取れてここに1行分のデータが入っています。これをonClickイベントのeditman関数の引数に渡せば、あとは関数側でよしなに処理が可能になります。HTMLで記述が出来るので自由度が高いです(後でボタンを好きなデザインをCSSで定義出来る)

//クリック時に医薬品名を表示
function editman(param){
  //医薬品名を表示
  alert(param.drugname)
  
  //レコードの中身を表示
  console.log(param)
}

//ヘッダにボタンを定義
var columnDefs = [
  {headerName: '編集',pinned: 'left',width: 70,
      cellRenderer : function(params){
          html="<button class='testman' onClick='editman(" + JSON.stringify(params.data) + ")'>✍</button>"
              return html
          }
  }
];

図:レコード単位でボタンを用意出来る

データ行用のオプション

行(レコード)に対するオプションも非常に充実しています。

ページネーション

大量のデータを一括で全部表示するのではなく、いくつかに分割して表示し矢印ボタン等でページを付ける機能です。gridOptionsに対してオプションを追加する事で実現可能です。以下のオプションをgridOptionsに追加するだけです。

// ag-gridのオプション
var gridOptions = {
  columnDefs: columnDefs,
  rowData: rowData,
  paginationAutoPageSize: true,
  pagination: true,
};
  • pagenationAutoPageSizeがtrueの場合、表示されてるag-gridの大きさに応じて自動的に表示数調整するかどうかのオプションです。故にウィンドウの大きさを変更すると、ページネーションの数が変化します。
  • pagenationがtrueの場合、ページネーションが表示されスクロール表示ではなくなります。
  • 他にもグルーピングしてアコーディオン表示にすることも可能になっています。

図:右下にページネーションが追加された

レコードのドラッグ

レコードを掴んでドラッグさせることを許可するかどうかを指定できます。またmultiple指定をする事で、Ctrlキーを押しながら複数指定し、いっぺんにドラッグで移動といったことも可能になっています。

ヘッダ側にも指定が必要になるので注意。

//グリッドのヘッダを定義
var columnDefs = [
  {headerName: "ID", field: "id", sortable: true, rowDrag: true, },
  {headerName: "JAN", field: "jan", filter: true, pinned: 'left',resizable: true,width: 120},
  {headerName: "GS1", field: "gs1", filter: true, sortable: true, width: 130},
  {headerName: "医薬品名", field: "drugname", cellStyle: {color: 'red', 'background-color': '#b8ffc7'}, resizable: true,width: 200},
  {headerName: "メーカー", field: "maker"},
];

//グリッドオプション
var gridOptions = {
  columnDefs: columnDefs,
  rowData: rowData,
  rowDragManaged: true,
  animateRows: true,
  rowDragMultiRow: true,
  rowSelection: 'multiple',
};
  • columnDefsの一番最初の列に対して、rowDrag:trueを追加しておく必要があります。
  • グリッドオプションにrowDragManaged:trueを追加しておきます。
  • また、rowDragMultiRowとrowSelection:multipleを追加しておく事で複数レコードの移動が可能になります。
  • animateRowsは移動時のアニメーション表示の可否を指定します。

図:ドラッグ用の表示が追加される

レコード選択イベント

レコードをクリックすると、そのレコードの内容を取得して表示するといったイベントを発火させることが出来るようになります。別途選択時用の関数を用意しておく必要があります。

//レコードクリック時イベント
function onRowSelected(event) {
  window.alert(
    '医薬品名 ' + event.node.data.drugname + '\n 選択してる? = ' + event.node.isSelected()
  );
}

//グリッドオプション
var gridOptions = {
  columnDefs: columnDefs,
  rowData: rowData,
  onRowSelected: onRowSelected,
};
  • onRowSelectedのオプションを追加し、関数名を指定する事でイベントの内容を渡せます
  • 関数側はevent引数に色々情報が入ってるので、今回はevent.node.data.drugnameで医薬品名だけを取り出しています。
  • isSelectedで選択してる行かどうかの判定も行えます。

図:レコードをクリックすると発火する

チャート機能

概要

ag-gridは本当に多機能なのですが、ソレ以外の機能も豊富でこれ1つで殆ど賄えるのではないかというくらい、いろいろな機能が搭載されています。今回そのうちの1つであるチャート表示機能をテストしてみました。

簡単なチャートだけではありますが、必要十分な表示が可能(もし複雑な高度なチャートが使いたいならば、D3.js等を使うと良いでしょう)

データは単純な2列のデータ(年とコストのみ)で、ロードするライブラリもag-chartと呼ばれるものを利用します。

図:簡単なラインチャートを引いてみた

ソースコード

<!DOCTYPE html>
<html>
  <head>
    <!-- jQueryをロードする -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

    <script>
      //スプレッドシートデータを取得して格納する
      function onSuccess(data){
        //JSONデータを取得する
        var ssdata = JSON.parse(data);

        //縦横サイズを自動調整
        setGridHeight();

        //ag-chartのオプション指定
        const options = {
          container: document.getElementById('myChart'),
          autoSize: true,
          title: {
            text: '年間IT費用の推移',
          },
          subtitle: {
            text: '鰻登りになるITコストについて',
          },
          data: ssdata,
          series: [
            {
              xKey: 'year',
              yKey: 'cost',
            },
          ],
        };

        //チャートを表示
        agCharts.AgChart.create(options);

        //loaderを非表示にする
        $("#main").css("display", "block");
        $("#progress").css("display", "none");
      }
      

      //リサイズ時にウィンドウにフィットさせる
			window.onresize = function(){
				setGridHeight();
			}
			  
			//gridのサイズを自動でウィンドウにフィットする
			$(document).ready(function () {
				setGridHeight();
			});

      //gridの縦横の高さを調整する
      function setGridHeight() {
				var layoutHeight = $(window).height() - 100;
				$('#myChart').css('height', layoutHeight + 'px');
			}

    </script>
  </head>
  <body>
    <h1>GASでag-chartを表示する</h1>
    
    <div id="progress" style="width: 100%; height: 500px; display:block">
      <center>
        <img src="https://officeforest.org/wp/library/loading.gif" border="0" alt="progress circle">
        <p><b>Now Loading ...</b></p>
      </center>
    </div>

    <div class="main" id="main" style="display:none">
      <div id="myChart" style="height: 600px;width:100%;" class="ag-theme-balham"></div>
    </div>

    <!-- Chart用ライブラリをロード -->
    <script src="https://cdn.jsdelivr.net/npm/ag-charts-community@6.2.1/dist/ag-charts-community.min.js"></script>
    <script type="text/javascript">
      //GAS側からデータをロードする
      google.script.run.withSuccessHandler(onSuccess).backjson2()
    </script>    
  </body>
</html>
  • 他にもTreemapや、Bar、ヒストグラム、散布図、エリア、円グラフ、二元グラフなども構築可能です。
  • 表示のみだけではなく、オプション指定でグラフに様々な機能を追加する事が可能です。
  • チャートツールバーを追加する事で、動的にチャートの変更などが可能です。
  • ag-gridとchartを同時に表示する事も可能です。

サンプル表示

ag-gridのサンプル

6万件のデータを表示させてみています。

ag-chartのサンプル

関連リンク

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)